Surn commited on
Commit
5bc2001
·
1 Parent(s): a5c35d5

0.1.2 Minor updates for challenges

Browse files
Files changed (5) hide show
  1. CLAUDE.md +51 -107
  2. wrdler/__init__.py +1 -1
  3. wrdler/ui.py +68 -19
  4. wrdler/words/english.txt +81 -7
  5. wrdler/words/sports.txt +136 -8
CLAUDE.md CHANGED
@@ -307,31 +307,67 @@ docker run -p 8501:8501 wrdler
307
  pytest tests/
308
  ```
309
 
310
- ### Environment Variables (for Challenge Mode)
311
- Challenge Mode requires HuggingFace Hub access for remote storage. Create a `.env` file in the project root:
 
312
 
313
  ```bash
314
- # Required for Challenge Mode
315
- HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx # or HF_TOKEN
316
- HF_REPO_ID=YourUsername/YourRepo # Target HF dataset repo
317
- SPACE_NAME=YourUsername/Wrdler # Your HF Space name
 
 
 
318
 
319
  # Optional
320
- CRYPTO_PK= # Reserved for future signing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  ```
322
 
323
- **How to get your HF_API_TOKEN:**
324
  1. Go to https://huggingface.co/settings/tokens
325
  2. Create a new token with `write` access
326
  3. Add to `.env` file as `HF_API_TOKEN=hf_...`
327
 
328
- **HF_REPO_ID Structure:**
329
  The dataset repository will contain:
330
  - `shortener.json` - Short URL mappings
331
  - `games/{uid}/settings.json` - Per-game challenge data
332
  - `games/{uid}/result.json` - Optional detailed results
333
 
334
- **Note:** The app will work without these variables but Challenge Mode features (sharing, leaderboards) will be disabled.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
  ## Git Configuration & Deployment
337
  **Current Branch:** main
@@ -438,100 +474,8 @@ The dataset repository will contain:
438
  - ✅ PWA injection via Docker build script (`inject-pwa-head.sh`)
439
  - ✅ AI word generation via `word_loader_ai.py` with Hugging Face integration
440
  - ✅ Utility modules provide reusable functions for storage and file ops
441
-
442
- ### Key Implementation Details
443
- - **No radar field in Puzzle dataclass** - removed in Sprint 3
444
- - **No vertical word placement** - horizontal only ("H" direction)
445
- - **Fixed grid dimensions** - always 8x6 (grid_cols=8, grid_rows=6)
446
- - **One word per row** - exactly 6 words total
447
- - **Word length requirement** - exactly 2 four-letter words, 2 five-letter words, and 2 six-letter words per puzzle
448
- - **Free letters tracked** - `free_letters` set and `free_letters_used` counter
449
- - **Auto-completion** - words auto-marked when all letters revealed
450
- - **Incorrect guess limit** - maximum 10 per game
451
- - **AI word generation** - generates 75 words per topic, saves to local files
452
- - **Utility modules** - shared functions from OpenBadge project
453
-
454
- ### WSL Environment Python Versions
455
- The development environment is WSL (Windows Subsystem for Linux) with access to both native Linux and Windows Python installations:
456
-
457
- **Native WSL (Linux):**
458
- - `python3` → Python 3.10.12 (`/usr/bin/python3`)
459
- - `python3.10` → Python 3.10.12
460
-
461
- **Windows Python (accessible via WSL):**
462
- - `python311.exe` → Python 3.11.9 (`/mnt/c/Users/cfettinger/AppData/Local/Programs/Python/Python311/`)
463
- - `python3.13.exe` → Python 3.13.1 (`/mnt/c/ProgramData/chocolatey/bin/`)
464
-
465
- **Note:** Windows Python executables (`.exe`) can be invoked directly from WSL and are useful for testing compatibility across Python versions. The project targets Python 3.12.8 specifically but requires >=3.12, <3.13 per pyproject.toml.
466
-
467
- ## Documentation Structure
468
-
469
- This file (CLAUDE.md) serves as a **living context document** for AI-assisted development:
470
-
471
- - **[specs/specs.md](specs/specs.md)** - Game rules, requirements, and feature specifications
472
- - **[specs/requirements.md](specs/requirements.md)** - Implementation requirements and acceptance criteria
473
- - **[GAMEPLAY_GUIDE.md](GAMEPLAY_GUIDE.md)** - User guide with tips and strategies
474
- - **[INSTALL_GUIDE.md](INSTALL_GUIDE.md)** - PWA installation instructions
475
- - **[README.md](README.md)** - User-facing documentation, installation guide, and complete changelog
476
- - **[Dockerfile](Dockerfile)** - Container deployment configuration with PWA injection
477
- - **[pyproject.toml](pyproject.toml)** - Python project metadata and dependencies
478
-
479
- **When to use each:**
480
- - **specs.md** - Understanding game rules and scoring system
481
- - **requirements.md** - Implementation status and acceptance criteria
482
- - **CLAUDE.md** - Quick reference for codebase and development context
483
- - **GAMEPLAY_GUIDE.md** - How to play the game
484
- - **README.md** - Public-facing info, setup instructions, and complete changelog
485
- - **INSTALL_GUIDE.md** - Installing Wrdler as a PWA
486
- - **Dockerfile** - Deployment configuration and container setup
487
-
488
- ## Challenge Mode & Remote Storage
489
-
490
- - ✅ Challenge Mode allows sharing games via short links (`?game_id=<sid>`)
491
- - ✅ Results stored in Hugging Face dataset repos via `game_storage.py`
492
- - ✅ Leaderboard sorted by: highest score → fastest time → highest difficulty
493
- - ✅ Multi-user challenges with top 5 display
494
- - ✅ Optional sharing (controlled by "Show Challenge Share Links" toggle, default OFF)
495
- - ✅ Word list difficulty calculation (v0.2.29)
496
- - ✅ iframe embedding support with `&iframe_host=` parameter (v0.2.23)
497
-
498
- ## Known Issues
499
-
500
- ### Active
501
- - ❓ Word list loading bug: the app may not select the proper word lists in some environments. Investigate `word_loader.get_wordlist_files()` / `load_word_list()` and sidebar selection persistence to ensure the chosen file is correctly used by the generator.
502
-
503
- ### Resolved
504
- - ✅ Duplicate rendering call bug (fixed in v0.0.2)
505
- - ✅ Music looping on congratulations screen (fixed in v0.2.12)
506
- - ✅ Sound effect and music volume wiring (fixed in v0.2.18, v0.2.19)
507
- - ✅ Sonar grid alignment (removed in Sprint 3)
508
- - ✅ Challenge mode link issues (fixed in v0.2.22)
509
-
510
- ## Dependencies
511
-
512
- From `requirements.txt`:
513
- - streamlit>=1.51.0 (primary framework)
514
- - matplotlib>=3.8 (visualization)
515
- - requests>=2.31.0 (HTTP requests)
516
- - huggingface_hub>=0.20.0 (remote storage)
517
- - python-dotenv>=1.0.0 (environment variables)
518
- - transformers (AI word generation)
519
- - gradio_client (HF Space API)
520
-
521
- From `pyproject.toml`:
522
- - Python >=3.12, <3.13 (strict version requirement)
523
-
524
- ## Version History Summary
525
-
526
- - **v0.1.1** (Current) - Enhanced AI word generation with intelligent saving, retry logic, file size limits
527
- - **v0.1.0** (Previous) - AI word generation, utility modules, version bump
528
- - **v0.0.4** - Documentation sync, version update
529
- - **v0.0.2-0.0.3** - All 7 sprints complete, core Wrdler features
530
- - **v0.2.20-0.2.29** - Challenge Mode, PWA, remote storage (inherited from BattleWords)
531
- - **v0.1.x** - Initial BattleWords releases before Wrdler fork
532
-
533
- ---
534
-
535
- **Last Updated:** 2025-01-31
536
- **Current Version:** 0.1.1
537
- **Status:** Production Ready - AI Enhanced ✅
 
307
  pytest tests/
308
  ```
309
 
310
+ ### Environment Variables
311
+
312
+ Wrdler uses environment variables for optional features. Create a `.env` file in the project root:
313
 
314
  ```bash
315
+ # ============================================
316
+ # Challenge Mode (Remote Storage)
317
+ # ============================================
318
+ # Required for Challenge Mode features (game sharing, leaderboards)
319
+ HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx # or HF_TOKEN - HuggingFace API token with write access
320
+ HF_REPO_ID=YourUsername/YourRepo # Target HF dataset repo for challenge storage
321
+ SPACE_NAME=YourUsername/Wrdler # Your HF Space name (for deployment)
322
 
323
  # Optional
324
+ CRYPTO_PK= # Reserved for future challenge signing features
325
+
326
+ # ============================================
327
+ # AI Word Generation
328
+ # ============================================
329
+ # Controls AI-powered word list generation
330
+ USE_HF_WORDS=false # Enable HF Space API for word generation
331
+ # - true: Use Hugging Face Space API (primary)
332
+ # - false: Use local transformers models (fallback)
333
+
334
+ HF_WORD_LIST_REPO_ID=YourUsername/WordRepo # HF dataset repo for AI-generated word lists
335
+ # (Required if USE_HF_WORDS=true)
336
+
337
+ IS_LOCAL= # Override environment detection
338
+ # - true: Force local development mode
339
+ # - false: Force production mode
340
+ # - (empty): Auto-detect based on environment
341
  ```
342
 
343
+ #### Getting Your HF_API_TOKEN
344
  1. Go to https://huggingface.co/settings/tokens
345
  2. Create a new token with `write` access
346
  3. Add to `.env` file as `HF_API_TOKEN=hf_...`
347
 
348
+ #### HF_REPO_ID Structure (Challenge Mode)
349
  The dataset repository will contain:
350
  - `shortener.json` - Short URL mappings
351
  - `games/{uid}/settings.json` - Per-game challenge data
352
  - `games/{uid}/result.json` - Optional detailed results
353
 
354
+ #### HF_WORD_LIST_REPO_ID Structure (AI Word Generation)
355
+ The dataset repository will contain:
356
+ - `words/{topic}.txt` - AI-generated word lists by topic
357
+ - Auto-managed by `word_loader_ai.py`
358
+ - Maximum 1000 words per file
359
+ - Sorted by length then alphabetically
360
+
361
+ #### Environment Variable Behavior
362
+ **Without these variables:**
363
+ - Challenge Mode features (sharing, leaderboards) will be disabled
364
+ - AI word generation will fall back to local transformers models
365
+ - Core game functionality remains fully operational
366
+
367
+ **With these variables:**
368
+ - Challenge Mode enables game sharing via short URLs
369
+ - AI generation can use HF Space API (faster, more reliable)
370
+ - Remote storage for multi-user leaderboards
371
 
372
  ## Git Configuration & Deployment
373
  **Current Branch:** main
 
474
  - ✅ PWA injection via Docker build script (`inject-pwa-head.sh`)
475
  - ✅ AI word generation via `word_loader_ai.py` with Hugging Face integration
476
  - ✅ Utility modules provide reusable functions for storage and file ops
477
+ - ⚠️ **IMPORTANT: Use Python syntax (colons `:`) NOT JavaScript syntax (curly braces `{}`)**
478
+ - Python: `if condition:` followed by indented block
479
+ - NOT: `if condition {` - this is JavaScript/C-style syntax
480
+ - Python: `try:` / `except Exception:` / `else:`
481
+ - NOT: `try {` / `} catch (Exception) {` / `} else {`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
wrdler/__init__.py CHANGED
@@ -8,5 +8,5 @@ Key differences from BattleWords:
8
  - 2 free letter guesses at game start
9
  """
10
 
11
- __version__ = "0.1.1"
12
  __all__ = ["models", "generator", "logic", "ui", "word_loader"]
 
8
  - 2 free letter guesses at game start
9
  """
10
 
11
+ __version__ = "0.1.2"
12
  __all__ = ["models", "generator", "logic", "ui", "word_loader"]
wrdler/ui.py CHANGED
@@ -482,7 +482,7 @@ border-radius: 50% !important;
482
  text-align: center;
483
  }
484
  div[data-testid="stButton"] button { max-width: 100%; aspect-ratio: 16 / 11; border-radius: 0; background: #1d64c8; color: #ffffff; font-weight: 700; padding: 0.5rem 0.75rem; min-height: 2.5rem; min-width: 1.75rem;}
485
- .st-key-new_game_btn, .st-key-sort_wordlist_btn { margin: 0 auto; aspect-ratio: unset; }
486
  .st-key-new_game_btn > div[data-testid="stButton"] button, .st-key-sort_wordlist_btn > div[data-testid="stButton"] button { aspect-ratio: unset; text-align:center; height: auto;}
487
 
488
  div[data-testid="column"], .st-emotion-cache-zh2fnc { width: auto !important; flex: 1 1 auto !important; min-width: 100% !important; max-width: 100% !important; }
@@ -671,9 +671,9 @@ def _init_session() -> None:
671
  if "show_incorrect_guesses" not in st.session_state:
672
  st.session_state.show_incorrect_guesses = True
673
 
674
- # NEW: Initialize Show Challenge Share Links (default OFF)
675
  if "show_challenge_share_links" not in st.session_state:
676
- st.session_state.show_challenge_share_links = False
677
 
678
  # NEW: Initialize free letters tracking
679
  if "free_letters" not in st.session_state:
@@ -695,7 +695,7 @@ def _new_game() -> None:
695
  mode = st.session_state.get("game_mode", "classic")
696
  show_grid_ticks = st.session_state.get("show_grid_ticks", False)
697
  show_incorrect = st.session_state.get("show_incorrect_guesses", True)
698
- show_challenge_share_links = st.session_state.get("show_challenge_share_links", False) # preserve
699
 
700
  shared_settings = st.session_state.get("shared_game_settings")
701
 
@@ -737,8 +737,8 @@ def _new_game() -> None:
737
  st.session_state.free_letters = set()
738
  st.session_state.free_letters_used = 0
739
 
740
- # Preserve preferences
741
- st.session_state.game_mode = mode
742
  st.session_state.show_grid_ticks = show_grid_ticks
743
  st.session_state.show_incorrect_guesses = show_incorrect
744
  st.session_state.initialized = True # Prevent _init_session from overwriting
@@ -789,6 +789,37 @@ def _render_header():
789
  is_challenge_mode = ( "shared_game_settings" in st.session_state and "game_id" in params ) or st.session_state.get("share_sid")
790
 
791
  if is_challenge_mode:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792
  with st.expander("🎯 Challenge Mode (click to expand/collapse)", expanded=True):
793
  shared_settings = st.session_state.get("shared_game_settings")
794
  if shared_settings:
@@ -811,8 +842,8 @@ def _render_header():
811
  # Build leaderboard HTML
812
  leaderboard_rows = []
813
  for i, user in enumerate(sorted_users[:5], 1): # Top 5
814
- u_mins, u_secs = divmod(user["time"], 60)
815
- u_time_str = f"{u_mins:02d}:{u_secs:02d}"
816
  medal = ["🥇", "🥈", "🥉"][i-1] if i <= 3 else f"{i}."
817
  # show optional difficulty if present
818
  diff_str = ""
@@ -832,10 +863,11 @@ def _render_header():
832
  # NEW: Only render share link when setting enabled
833
  if sid and st.session_state.get("show_challenge_share_links", False):
834
  share_url = get_shareable_url(sid)
835
- share_html = f"<div style='margin-top:1rem;margin-bottom:0.5rem;font-size: 0.9rem;'><a href='{share_url}' target='_blank' style='color:#FFF;text-decoration:underline;'><strong>🔗 Share this challenge</a></strong<br/><br/><span style='font-size:0.85em;color:#ddd;'>{share_url}</span>"
836
 
837
  st.markdown(
838
  f"""
 
839
  <div style="
840
  background: linear-gradient(90deg, #1d64c8 0%, #165ba8 100%);
841
  color: white;
@@ -845,6 +877,10 @@ def _render_header():
845
  text-align: center;
846
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
847
  ">
 
 
 
 
848
  🎯 <strong>CHALLENGE MODE</strong> 🎯<br/>
849
  <span style="font-size: 0.9rem;">
850
  Beat the best: <strong>{best_score} points</strong> in <strong>{best_time_str}</strong> by <strong>{best_user['username']}</strong>
@@ -861,6 +897,7 @@ def _render_header():
861
  else:
862
  st.markdown(
863
  """
 
864
  <div style="
865
  background: linear-gradient(90deg, #1d64c8 0%, #165ba8 100%);
866
  color: white;
@@ -870,6 +907,10 @@ def _render_header():
870
  text-align: center;
871
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
872
  ">
 
 
 
 
873
  🎯 <strong>CHALLENGE MODE</strong> 🎯<br/>
874
  <span style="font-size: 0.9rem;">
875
  Be the first to complete this challenge!
@@ -989,9 +1030,9 @@ def _render_sidebar():
989
  st.session_state.show_incorrect_guesses = True
990
  st.checkbox("Show incorrect guesses", value=st.session_state.show_incorrect_guesses, key="show_incorrect_guesses")
991
 
992
- # NEW: Add Show Challenge Share Links option - default OFF
993
  if "show_challenge_share_links" not in st.session_state:
994
- st.session_state.show_challenge_share_links = False
995
  st.checkbox("Show Challenge Share Links", value=st.session_state.show_challenge_share_links, key="show_challenge_share_links")
996
 
997
  # NEW: Initialize Enable Free Letters (default OFF)
@@ -1677,7 +1718,7 @@ def _game_over_content(state: GameState) -> None:
1677
  color: #fff;
1678
  padding: 16px;
1679
  }
1680
- .bw-dialog-header { display:flex; justify-content: space-between; align-items:center; }
1681
  .bw-dialog-title, .st-emotion-cache-11elpad p { margin: 0; font-weight: bold;font-size: 1.5rem;text-align: center;flex: auto;filter:drop-shadow(1px 1px 2px #003);}
1682
  .text-success { color: #20d46c;font-weight: bold;font-size: 1.5rem;text-align: center;flex: auto;filter:drop-shadow(1px 1px 2px #003); }
1683
  .st-key-new_game_btn_dialog, .st-key-close_game_over { width: 50% !important;
@@ -1689,6 +1730,9 @@ def _game_over_content(state: GameState) -> None:
1689
  .st-key-new_game_btn_dialog button, .st-key-close_game_over button {
1690
  height: 50px !important;
1691
  }
 
 
 
1692
  .st-key-new_game_btn_dialog:hover, .st-key-close_game_over:hover{
1693
  /*background: linear-gradient(-45deg, #1d64c8, #ffffff, #1d64c8, #666666);*/
1694
  background: #1d64c8 !important;
@@ -1904,8 +1948,8 @@ def _game_over_content(state: GameState) -> None:
1904
 
1905
  # Dialog actions
1906
  if st.button("Close", key="close_game_over"):
1907
- st.session_state["show_gameover_overlay"] = False
1908
- st.session_state["remount_background_audio"] = True # <-- set flag
1909
  st.rerun()
1910
 
1911
  # Prefer st.dialog/experimental_dialog; fallback to st.modal if unavailable
@@ -1926,7 +1970,8 @@ else:
1926
  _game_over_content(state)
1927
 
1928
  def _render_game_over(state: GameState):
1929
- visible = bool(st.session_state.get("show_gameover_overlay", True)) and is_game_over(state)
 
1930
  music_dir = _get_music_dir()
1931
  if visible:
1932
  # Mount congratulations music (play once, do not loop) only if music is enabled
@@ -2000,7 +2045,7 @@ def run_app():
2000
  st.session_state["shared_game_loaded"] = True # Prevent repeated attempts
2001
  except Exception as e:
2002
  st.error(f"❌ Error loading shared game: {e}")
2003
- st.session_state["shared_game_loaded"] = True # Prevent repeated attempts
2004
 
2005
  _init_session()
2006
  st.markdown(ocean_background_css, unsafe_allow_html=True)
@@ -2024,12 +2069,15 @@ def run_app():
2024
  #st.divider()
2025
  _render_score_panel(state)
2026
  with left:
 
 
 
 
2027
  # Show free letter selection if enabled and not complete
2028
  if st.session_state.get("enable_free_letters", False) and state.free_letters_used < 2:
2029
  _render_free_letters(state)
2030
 
2031
  _render_grid(state, st.session_state.letter_map, show_grid_ticks=st.session_state.get("show_grid_ticks", True))
2032
- st.button("New Game", width=125, on_click=_new_game, key="new_game_btn")
2033
 
2034
  # End condition (only show overlay if dismissed)
2035
  state = _to_state()
@@ -2039,7 +2087,7 @@ def run_app():
2039
  def _on_game_option_change() -> None:
2040
  """
2041
  Unified callback for game option changes.
2042
- If currently in a loaded challenge, break the link by resetting challenge state
2043
  and removing the game_id query param. Then start a new game with the updated options.
2044
  """
2045
  try:
@@ -2049,7 +2097,7 @@ def _on_game_option_change() -> None:
2049
  # st.query_params may be a Mapping; pop safely if supported
2050
  try:
2051
  if "game_id" in qp:
2052
- qp.pop("game_id")
2053
  except Exception:
2054
  # Fallback: clear all params if pop not supported
2055
  try:
@@ -2059,6 +2107,7 @@ def _on_game_option_change() -> None:
2059
  except Exception:
2060
  pass
2061
 
 
2062
  # Clear challenge session flags and links
2063
  if st.session_state.get("loaded_game_sid") is not None:
2064
  st.session_state.loaded_game_sid = None
 
482
  text-align: center;
483
  }
484
  div[data-testid="stButton"] button { max-width: 100%; aspect-ratio: 16 / 11; border-radius: 0; background: #1d64c8; color: #ffffff; font-weight: 700; padding: 0.5rem 0.75rem; min-height: 2.5rem; min-width: 1.75rem;}
485
+ .st-key-new_game_btn, .st-key-sort_wordlist_btn { margin: 0 auto; aspect-ratio: unset; z-index:9999;}
486
  .st-key-new_game_btn > div[data-testid="stButton"] button, .st-key-sort_wordlist_btn > div[data-testid="stButton"] button { aspect-ratio: unset; text-align:center; height: auto;}
487
 
488
  div[data-testid="column"], .st-emotion-cache-zh2fnc { width: auto !important; flex: 1 1 auto !important; min-width: 100% !important; max-width: 100% !important; }
 
671
  if "show_incorrect_guesses" not in st.session_state:
672
  st.session_state.show_incorrect_guesses = True
673
 
674
+ # NEW: Initialize Show Challenge Share Links (default ON)
675
  if "show_challenge_share_links" not in st.session_state:
676
+ st.session_state.show_challenge_share_links = True
677
 
678
  # NEW: Initialize free letters tracking
679
  if "free_letters" not in st.session_state:
 
695
  mode = st.session_state.get("game_mode", "classic")
696
  show_grid_ticks = st.session_state.get("show_grid_ticks", False)
697
  show_incorrect = st.session_state.get("show_incorrect_guesses", True)
698
+ show_challenge_share_links = st.session_state.get("show_challenge_share_links", True) # preserve (default True)
699
 
700
  shared_settings = st.session_state.get("shared_game_settings")
701
 
 
737
  st.session_state.free_letters = set()
738
  st.session_state.free_letters_used = 0
739
 
740
+ # Preserve preferences - but do NOT set widget-bound keys like game_mode
741
+ # game_mode is managed by the selectbox widget in _render_sidebar()
742
  st.session_state.show_grid_ticks = show_grid_ticks
743
  st.session_state.show_incorrect_guesses = show_incorrect
744
  st.session_state.initialized = True # Prevent _init_session from overwriting
 
789
  is_challenge_mode = ( "shared_game_settings" in st.session_state and "game_id" in params ) or st.session_state.get("share_sid")
790
 
791
  if is_challenge_mode:
792
+ # Add exit challenge mode button/link styling
793
+ st.markdown(
794
+ """
795
+ <style>
796
+ .bw-challenge-header {
797
+ display: flex;
798
+ justify-content: space-between;
799
+ align-items: center;
800
+ margin-bottom: 0.5rem;
801
+ }
802
+ .bw-exit-challenge {
803
+ # background: rgba(255, 75, 75, 0.2);
804
+ # border: 1px solid rgba(255, 75, 75, 0.5);
805
+ color: #fff;
806
+ padding: 0.25rem 0.75rem;
807
+ border-radius: 0.25rem;
808
+ text-decoration: none !important;
809
+ font-size: 0.85rem;
810
+ cursor: pointer;
811
+ transition: all 0.2s ease;
812
+ }
813
+ .bw-exit-challenge:hover {
814
+ background: rgba(255, 75, 75, 0.4);
815
+ border-color: rgba(255, 75, 75, 0.8);
816
+ text-decoration: none;
817
+ }
818
+ </style>
819
+ """,
820
+ unsafe_allow_html=True
821
+ )
822
+
823
  with st.expander("🎯 Challenge Mode (click to expand/collapse)", expanded=True):
824
  shared_settings = st.session_state.get("shared_game_settings")
825
  if shared_settings:
 
842
  # Build leaderboard HTML
843
  leaderboard_rows = []
844
  for i, user in enumerate(sorted_users[:5], 1): # Top 5
845
+ u_mins, uSecs = divmod(user["time"], 60)
846
+ u_time_str = f"{u_mins:02d}:{uSecs:02d}"
847
  medal = ["🥇", "🥈", "🥉"][i-1] if i <= 3 else f"{i}."
848
  # show optional difficulty if present
849
  diff_str = ""
 
863
  # NEW: Only render share link when setting enabled
864
  if sid and st.session_state.get("show_challenge_share_links", False):
865
  share_url = get_shareable_url(sid)
866
+ share_html = f"<div style='margin-top:1rem;margin-bottom:0.5rem;font-size: 0.9rem;'><a href='{share_url}' target='_blank' style='color:#FFF;text-decoration:underline;'><strong>🔗 Share this challenge</a></strong><br/><br/><span style='font-size:0.85em;color:#ddd;'>{share_url}</span></div>"
867
 
868
  st.markdown(
869
  f"""
870
+
871
  <div style="
872
  background: linear-gradient(90deg, #1d64c8 0%, #165ba8 100%);
873
  color: white;
 
877
  text-align: center;
878
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
879
  ">
880
+ <div class="bw-challenge-header">
881
+ <div style="flex: 1;"></div>
882
+ <a href="/" target="_self" class="bw-exit-challenge" title="Exit challenge mode and start a new game">❎ Exit</a>
883
+ </div>
884
  🎯 <strong>CHALLENGE MODE</strong> 🎯<br/>
885
  <span style="font-size: 0.9rem;">
886
  Beat the best: <strong>{best_score} points</strong> in <strong>{best_time_str}</strong> by <strong>{best_user['username']}</strong>
 
897
  else:
898
  st.markdown(
899
  """
900
+
901
  <div style="
902
  background: linear-gradient(90deg, #1d64c8 0%, #165ba8 100%);
903
  color: white;
 
907
  text-align: center;
908
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
909
  ">
910
+ <div class="bw-challenge-header">
911
+ <div style="flex: 1;"></div>
912
+ <a href="/" target="_self" class="bw-exit-challenge" title="Exit challenge mode and start a new game">❎ Exit</a>
913
+ </div>
914
  🎯 <strong>CHALLENGE MODE</strong> 🎯<br/>
915
  <span style="font-size: 0.9rem;">
916
  Be the first to complete this challenge!
 
1030
  st.session_state.show_incorrect_guesses = True
1031
  st.checkbox("Show incorrect guesses", value=st.session_state.show_incorrect_guesses, key="show_incorrect_guesses")
1032
 
1033
+ # NEW: Add Show Challenge Share Links option - default ON
1034
  if "show_challenge_share_links" not in st.session_state:
1035
+ st.session_state.show_challenge_share_links = True
1036
  st.checkbox("Show Challenge Share Links", value=st.session_state.show_challenge_share_links, key="show_challenge_share_links")
1037
 
1038
  # NEW: Initialize Enable Free Letters (default OFF)
 
1718
  color: #fff;
1719
  padding: 16px;
1720
  }
1721
+ .bw-dialog-header { display:flex; justify-content: space-between; align-items: center; }
1722
  .bw-dialog-title, .st-emotion-cache-11elpad p { margin: 0; font-weight: bold;font-size: 1.5rem;text-align: center;flex: auto;filter:drop-shadow(1px 1px 2px #003);}
1723
  .text-success { color: #20d46c;font-weight: bold;font-size: 1.5rem;text-align: center;flex: auto;filter:drop-shadow(1px 1px 2px #003); }
1724
  .st-key-new_game_btn_dialog, .st-key-close_game_over { width: 50% !important;
 
1730
  .st-key-new_game_btn_dialog button, .st-key-close_game_over button {
1731
  height: 50px !important;
1732
  }
1733
+ .st-key-new_game_btn button {
1734
+ transition: none !important;
1735
+ }
1736
  .st-key-new_game_btn_dialog:hover, .st-key-close_game_over:hover{
1737
  /*background: linear-gradient(-45deg, #1d64c8, #ffffff, #1d64c8, #666666);*/
1738
  background: #1d64c8 !important;
 
1948
 
1949
  # Dialog actions
1950
  if st.button("Close", key="close_game_over"):
1951
+ st.session_state["hide_gameover_overlay"] = True # Changed from show_gameover_overlay = False
1952
+ st.session_state["remount_background_audio"] = True
1953
  st.rerun()
1954
 
1955
  # Prefer st.dialog/experimental_dialog; fallback to st.modal if unavailable
 
1970
  _game_over_content(state)
1971
 
1972
  def _render_game_over(state: GameState):
1973
+ # Use hide_gameover_overlay (same key as run_app) with inverted logic
1974
+ visible = not st.session_state.get("hide_gameover_overlay", False) and is_game_over(state)
1975
  music_dir = _get_music_dir()
1976
  if visible:
1977
  # Mount congratulations music (play once, do not loop) only if music is enabled
 
2045
  st.session_state["shared_game_loaded"] = True # Prevent repeated attempts
2046
  except Exception as e:
2047
  st.error(f"❌ Error loading shared game: {e}")
2048
+ st.session_state["shared_game_loaded"] = True # Prevent repeated attempts
2049
 
2050
  _init_session()
2051
  st.markdown(ocean_background_css, unsafe_allow_html=True)
 
2069
  #st.divider()
2070
  _render_score_panel(state)
2071
  with left:
2072
+ # New Game button moved above grid to prevent unnecessary rerenders on grid clicks
2073
+ # Using on_click callback ensures _new_game runs before widgets are instantiated
2074
+ st.button("New Game", width=125, on_click=_new_game, key="new_game_btn")
2075
+
2076
  # Show free letter selection if enabled and not complete
2077
  if st.session_state.get("enable_free_letters", False) and state.free_letters_used < 2:
2078
  _render_free_letters(state)
2079
 
2080
  _render_grid(state, st.session_state.letter_map, show_grid_ticks=st.session_state.get("show_grid_ticks", True))
 
2081
 
2082
  # End condition (only show overlay if dismissed)
2083
  state = _to_state()
 
2087
  def _on_game_option_change() -> None:
2088
  """
2089
  Unified callback for game option changes.
2090
+ If currently in a loaded challenge, break the link by resetting challenge mode
2091
  and removing the game_id query param. Then start a new game with the updated options.
2092
  """
2093
  try:
 
2097
  # st.query_params may be a Mapping; pop safely if supported
2098
  try:
2099
  if "game_id" in qp:
2100
+ qp.pop("game_id")
2101
  except Exception:
2102
  # Fallback: clear all params if pop not supported
2103
  try:
 
2107
  except Exception:
2108
  pass
2109
 
2110
+
2111
  # Clear challenge session flags and links
2112
  if st.session_state.get("loaded_game_sid") is not None:
2113
  st.session_state.loaded_game_sid = None
wrdler/words/english.txt CHANGED
@@ -1,7 +1,7 @@
1
  # AI-generated word list
2
  # Topic: English
3
- # Last updated: 2025-11-28 10:56:13
4
- # Total words: 357
5
  # Format: one word per line, sorted by length then alphabetically
6
  #
7
  ALES
@@ -13,21 +13,30 @@ BALE
13
  BANE
14
  BANK
15
  BARN
 
16
  BASE
 
17
  BEAN
18
  BELL
19
  BEND
 
20
  BOOK
21
  BORE
22
  BORN
 
23
  BUNK
24
  BURN
 
25
  CAKE
26
  CAME
27
  CARE
 
 
28
  CLIP
29
  CODE
30
  COPY
 
 
31
  DARE
32
  DEAR
33
  DENT
@@ -42,6 +51,9 @@ EELS
42
  ELMY
43
  EMIT
44
  ENDS
 
 
 
45
  FELL
46
  FILE
47
  FINE
@@ -49,8 +61,11 @@ FIRE
49
  FISH
50
  FLAP
51
  FLAT
 
 
52
  FUEL
53
  GAIN
 
54
  GAME
55
  GEMS
56
  GENT
@@ -58,18 +73,23 @@ GIRD
58
  GIVE
59
  GLOB
60
  HALE
 
61
  HELP
62
  HONE
 
63
  HOSE
 
64
  HUES
65
  IRON
66
  JUMP
67
  LAKE
 
68
  LAND
69
  LANE
70
  LARK
71
  LASH
72
  LATE
 
73
  LEAD
74
  LEAN
75
  LEAP
@@ -77,6 +97,7 @@ LEET
77
  LEFT
78
  LEND
79
  LENS
 
80
  LIFE
81
  LINE
82
  LION
@@ -86,6 +107,7 @@ LOBE
86
  LOCO
87
  LODE
88
  LOON
 
89
  LOST
90
  LURE
91
  MAIN
@@ -109,7 +131,10 @@ OREO
109
  PACE
110
  PAGE
111
  PAIN
 
 
112
  PENS
 
113
  POET
114
  POLY
115
  POPE
@@ -143,30 +168,41 @@ REPS
143
  REST
144
  RIDE
145
  RILE
146
- RHYME
147
  RINE
148
  RINK
149
  RISE
150
  RITE
151
  ROAM
152
  ROPS
 
153
  SEAL
154
  SEAM
 
155
  SINE
 
156
  SIRE
157
  SLAB
158
  SLIT
159
  SLUG
160
  SORE
 
 
 
161
  STEM
162
  STEP
163
  TAGS
164
  TAIL
165
  TALE
 
 
 
166
  TEXT
167
  TIME
 
168
  TYPE
 
169
  WALK
 
170
  WINE
171
  WIRE
172
  WISH
@@ -253,6 +289,7 @@ DODGE
253
  DOLLS
254
  DOZEN
255
  DRAWS
 
256
  DRILL
257
  DROPS
258
  DUELS
@@ -266,15 +303,12 @@ EDGES
266
  EJECT
267
  ELITE
268
  ELOPE
269
- EMIT
270
  ENEMY
271
  ENSUE
272
  ENVOI
273
- EPIC
274
  EPOCH
275
  ERASE
276
  ERUPT
277
- EULOGY
278
  EVOKE
279
  EXACT
280
  EXALT
@@ -317,7 +351,6 @@ FLING
317
  FLOES
318
  FLOOD
319
  FLOOR
320
- FLOP
321
  FLOWE
322
  FLOWS
323
  FLUNG
@@ -506,28 +539,69 @@ WRITE
506
  ASSIST
507
  AUTHOR
508
  BEACON
 
 
 
509
  BRIDGE
510
  BRIGHT
511
  CASTLE
512
  DINNER
513
  EMPIRE
 
 
 
514
  FANOUT
515
  FAXING
 
 
 
 
 
516
  FLIGHT
517
  FLOWER
518
  FOREST
519
  FRAMES
520
  GENTLE
 
521
  HAMMER
522
  ISLAND
 
 
 
 
 
523
  LENGTH
524
  LETTER
525
  PHRASE
526
  PRAISE
 
 
527
  REASON
528
  REFORM
529
  REYLES
 
530
  SCRIPT
 
 
 
 
 
 
 
 
 
 
 
 
 
531
  SPRINT
 
 
 
 
 
 
 
 
532
  SURVEY
533
  SYMBOL
 
1
  # AI-generated word list
2
  # Topic: English
3
+ # Last updated: 2025-12-04 10:32:04
4
+ # Total words: 601
5
  # Format: one word per line, sorted by length then alphabetically
6
  #
7
  ALES
 
13
  BANE
14
  BANK
15
  BARN
16
+ BARS
17
  BASE
18
+ BEAD
19
  BEAN
20
  BELL
21
  BEND
22
+ BIRD
23
  BOOK
24
  BORE
25
  BORN
26
+ BROW
27
  BUNK
28
  BURN
29
+ CAGE
30
  CAKE
31
  CAME
32
  CARE
33
+ CASE
34
+ CITE
35
  CLIP
36
  CODE
37
  COPY
38
+ CORD
39
+ DANS
40
  DARE
41
  DEAR
42
  DENT
 
51
  ELMY
52
  EMIT
53
  ENDS
54
+ EONS
55
+ EPIC
56
+ FANE
57
  FELL
58
  FILE
59
  FINE
 
61
  FISH
62
  FLAP
63
  FLAT
64
+ FLAY
65
+ FLOP
66
  FUEL
67
  GAIN
68
+ GALE
69
  GAME
70
  GEMS
71
  GENT
 
73
  GIVE
74
  GLOB
75
  HALE
76
+ HANG
77
  HELP
78
  HONE
79
+ HORN
80
  HOSE
81
+ HUED
82
  HUES
83
  IRON
84
  JUMP
85
  LAKE
86
+ LAMB
87
  LAND
88
  LANE
89
  LARK
90
  LASH
91
  LATE
92
+ LAZE
93
  LEAD
94
  LEAN
95
  LEAP
 
97
  LEFT
98
  LEND
99
  LENS
100
+ LENT
101
  LIFE
102
  LINE
103
  LION
 
107
  LOCO
108
  LODE
109
  LOON
110
+ LORE
111
  LOST
112
  LURE
113
  MAIN
 
131
  PACE
132
  PAGE
133
  PAIN
134
+ PANS
135
+ PATE
136
  PENS
137
+ PLED
138
  POET
139
  POLY
140
  POPE
 
168
  REST
169
  RIDE
170
  RILE
 
171
  RINE
172
  RINK
173
  RISE
174
  RITE
175
  ROAM
176
  ROPS
177
+ SAGE
178
  SEAL
179
  SEAM
180
+ SHOE
181
  SINE
182
+ SING
183
  SIRE
184
  SLAB
185
  SLIT
186
  SLUG
187
  SORE
188
+ SPAN
189
+ SPIN
190
+ STAM
191
  STEM
192
  STEP
193
  TAGS
194
  TAIL
195
  TALE
196
+ TEAL
197
+ TEEN
198
+ TEND
199
  TEXT
200
  TIME
201
+ TONS
202
  TYPE
203
+ WAGE
204
  WALK
205
+ WAYS
206
  WINE
207
  WIRE
208
  WISH
 
289
  DOLLS
290
  DOZEN
291
  DRAWS
292
+ DREAM
293
  DRILL
294
  DROPS
295
  DUELS
 
303
  EJECT
304
  ELITE
305
  ELOPE
 
306
  ENEMY
307
  ENSUE
308
  ENVOI
 
309
  EPOCH
310
  ERASE
311
  ERUPT
 
312
  EVOKE
313
  EXACT
314
  EXALT
 
351
  FLOES
352
  FLOOD
353
  FLOOR
 
354
  FLOWE
355
  FLOWS
356
  FLUNG
 
539
  ASSIST
540
  AUTHOR
541
  BEACON
542
+ BLEATS
543
+ BRAINS
544
+ BREACH
545
  BRIDGE
546
  BRIGHT
547
  CASTLE
548
  DINNER
549
  EMPIRE
550
+ EULOGY
551
+ FALTER
552
+ FANATE
553
  FANOUT
554
  FAXING
555
+ FLAKED
556
+ FLAKES
557
+ FLAMES
558
+ FLANGE
559
+ FLARED
560
  FLIGHT
561
  FLOWER
562
  FOREST
563
  FRAMES
564
  GENTLE
565
+ GRATES
566
  HAMMER
567
  ISLAND
568
+ KNIGHT
569
+ KNITWE
570
+ KNOWED
571
+ KNOWER
572
+ LANDER
573
  LENGTH
574
  LETTER
575
  PHRASE
576
  PRAISE
577
+ RAISED
578
+ RAISES
579
  REASON
580
  REFORM
581
  REYLES
582
+ SCREEN
583
  SCRIPT
584
+ SHADED
585
+ SHAPES
586
+ SHELVE
587
+ SHOUTS
588
+ SLATED
589
+ SLATES
590
+ SLAYER
591
+ SLICES
592
+ SLIDES
593
+ SLINGS
594
+ SLIVER
595
+ SPACES
596
+ SPRAYS
597
  SPRINT
598
+ STABLE
599
+ STAGED
600
+ STAINS
601
+ STATED
602
+ STEADS
603
+ STEALS
604
+ STEELS
605
+ STUNED
606
  SURVEY
607
  SYMBOL
wrdler/words/sports.txt CHANGED
@@ -1,42 +1,170 @@
1
  # AI-generated word list
2
- # Topic: SPORTS
3
- # Last updated: 2025-12-01 11:06:55
4
- # Total words: 36
5
  # Format: one word per line, sorted by length then alphabetically
6
  #
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  BALLS
8
  BENCH
 
 
 
9
  CLUBS
 
 
10
  DARTS
11
  DENIM
12
- FENCY
13
  FILMS
14
  FINES
 
 
 
 
 
 
 
 
 
 
 
15
  GAMES
 
 
16
  GOLFS
 
 
 
 
 
17
  HEATS
 
18
  HOOPS
 
 
 
 
 
19
  LANCE
20
  LINES
 
21
  LOOPS
 
22
  POLES
 
 
 
23
  RALLY
24
  REELS
 
 
 
 
25
  SCORE
26
  SHANK
 
 
 
27
  SHOOT
28
  SKATE
 
29
  SLICE
 
30
  SLIPS
 
31
  SOLES
32
- SPOTS
 
 
 
 
33
  SPURN
34
- SWIMM
35
- TACKL
36
- TAMES
 
 
 
 
 
37
  TEAMS
38
  TRACK
39
  TRAMP
40
  TURNS
41
  WREST
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  SOCCER
 
 
 
 
 
 
 
 
 
 
 
1
  # AI-generated word list
2
+ # Topic: sporTS
3
+ # Last updated: 2025-12-04 10:41:42
4
+ # Total words: 164
5
  # Format: one word per line, sorted by length then alphabetically
6
  #
7
+ ARMS
8
+ BALL
9
+ BASK
10
+ BOWS
11
+ BULL
12
+ DART
13
+ DASH
14
+ DUEL
15
+ FAME
16
+ FINE
17
+ FLAP
18
+ FLIT
19
+ GAGE
20
+ GAIN
21
+ GALE
22
+ GENT
23
+ GILD
24
+ HALE
25
+ HARE
26
+ HINT
27
+ HOOK
28
+ HOOP
29
+ HOPS
30
+ HUES
31
+ JAWS
32
+ JUMP
33
+ KICK
34
+ LANE
35
+ LODE
36
+ LURE
37
+ PINT
38
+ RACE
39
+ RACK
40
+ RAIL
41
+ RIDE
42
+ RIME
43
+ RISE
44
+ RODE
45
+ RUSH
46
+ SHIP
47
+ SHOE
48
+ SOAR
49
+ SOFA
50
+ SPAN
51
+ SWIM
52
+ TALK
53
+ WING
54
+ BAILS
55
  BALLS
56
  BENCH
57
+ BENDS
58
+ BOWL
59
+ CHAMP
60
  CLUBS
61
+ CURLS
62
+ DANCE
63
  DARTS
64
  DENIM
 
65
  FILMS
66
  FINES
67
+ FLAIL
68
+ FLAKE
69
+ FLAPS
70
+ FLARE
71
+ FLAWS
72
+ FLICK
73
+ FLINT
74
+ FLUTE
75
+ FLUKE
76
+ GAINS
77
+ GAMER
78
  GAMES
79
+ GATES
80
+ GLOBE
81
  GOLFS
82
+ GOUTS
83
+ GRAFT
84
+ GROAN
85
+ GUNS
86
+ HAILS
87
  HEATS
88
+ HEELS
89
  HOOPS
90
+ HORNS
91
+ HORSE
92
+ JINX
93
+ JOLTS
94
+ KNITS
95
  LANCE
96
  LINES
97
+ LIONS
98
  LOOPS
99
+ PITCH
100
  POLES
101
+ PUNCH
102
+ RACER
103
+ RACES
104
  RALLY
105
  REELS
106
+ ROLES
107
+ ROWER
108
+ RUGBY
109
+ SAILS
110
  SCORE
111
  SHANK
112
+ SHARE
113
+ SHARP
114
+ SHOES
115
  SHOOT
116
  SKATE
117
+ SKIER
118
  SLICE
119
+ SLIDE
120
  SLIPS
121
+ SLOPE
122
  SOLES
123
+ SOLO
124
+ SPITE
125
+ SPOKE
126
+ SPOON
127
+ SPORT
128
  SPURN
129
+ STAGE
130
+ STARS
131
+ STEPS
132
+ STERN
133
+ STICK
134
+ STORM
135
+ SWIMS
136
+ TABLE
137
  TEAMS
138
  TRACK
139
  TRAMP
140
  TURNS
141
  WREST
142
+ BASKET
143
+ BIKERS
144
+ BOWLER
145
+ BOXING
146
+ DAZZLE
147
+ FENCER
148
+ GOLFER
149
+ HITTER
150
+ HORSES
151
+ JUDGES
152
+ JUMPER
153
+ KICKER
154
+ PISTON
155
+ POCKET
156
+ ROLLER
157
+ RUNNER
158
+ SCORES
159
+ SLALOM
160
  SOCCER
161
+ SPIKES
162
+ SPORTS
163
+ SPRINT
164
+ STABLE
165
+ STANCE
166
+ STITCH
167
+ SURFER
168
+ SURVEY
169
+ TAILOR
170
+ WINNER