Surn commited on
Commit
082223e
Β·
1 Parent(s): 1a984ea

Increase leaderboard display limit to top 25 entries

Updated leaderboard system to display the top 25 entries instead of the top 20. Introduced `MAX_DISPLAY_ENTRIES` environment variable for configurable display limits, defaulting to 25. Adjusted qualification logic, sorting, and rendering to reflect the new limit.

Made username input mandatory for leaderboard submissions, replacing "Anonymous" with "Your Name" as the placeholder. Added validation to prevent submissions without a valid username. Updated privacy policy to require usernames while avoiding PII storage.

Improved leaderboard rendering with dynamic table height calculation. Limited historical weekly periods displayed to 6. Updated documentation, constants, and storage structure to align with the new configuration. Incremented version numbers to reflect changes.

.gitignore CHANGED
@@ -491,3 +491,4 @@ secrets.*
491
  /package.json
492
  /package-lock.json
493
  /.claude
 
 
491
  /package.json
492
  /package-lock.json
493
  /.claude
494
+ /.github
CLAUDE.md CHANGED
@@ -57,7 +57,7 @@ Wrdler is a simplified vocabulary puzzle game based on BattleWords:
57
  ### Daily & Weekly Leaderboards
58
  - **Settings-Based Separation:** Each unique settings combo creates separate leaderboard
59
  - Settings: `game_mode`, `wordlist_source`, `show_incorrect_guesses`, `enable_free_letters`, `puzzle_options` (spacer, may_overlap)
60
- - **Auto Score Submission:** Checks qualification for top 20 after game completion
61
  - **Storage:** Folder-based discovery at `games/leaderboards/{daily|weekly}/{period}/{file_id}/settings.json`
62
  - **File ID Format:** `{wordlist_source}-{game_mode}-{sequence}` (e.g., `classic-classic-0`)
63
  - **Leaderboard Page:** Four tabs (Today, Daily, Weekly, History) accessible via `?page=today|daily|weekly|history`
@@ -203,6 +203,7 @@ HF_WORD_LIST_REPO_ID=YourUsername/WordRepo # Dataset repo for AI word lists
203
 
204
  # OAuth Admin Access (NEW)
205
  ADMIN_USERS=username1,username2 # Comma-separated list of admin usernames
 
206
  ```
207
 
208
  ### HF_REPO_ID Structure
 
57
  ### Daily & Weekly Leaderboards
58
  - **Settings-Based Separation:** Each unique settings combo creates separate leaderboard
59
  - Settings: `game_mode`, `wordlist_source`, `show_incorrect_guesses`, `enable_free_letters`, `puzzle_options` (spacer, may_overlap)
60
+ - **Auto Score Submission:** Checks qualification for top 25 after game completion
61
  - **Storage:** Folder-based discovery at `games/leaderboards/{daily|weekly}/{period}/{file_id}/settings.json`
62
  - **File ID Format:** `{wordlist_source}-{game_mode}-{sequence}` (e.g., `classic-classic-0`)
63
  - **Leaderboard Page:** Four tabs (Today, Daily, Weekly, History) accessible via `?page=today|daily|weekly|history`
 
203
 
204
  # OAuth Admin Access (NEW)
205
  ADMIN_USERS=username1,username2 # Comma-separated list of admin usernames
206
+ MAX_DISPLAY_ENTRIES=25 # Max leaderboard entries to display (default: 25)
207
  ```
208
 
209
  ### HF_REPO_ID Structure
README.md CHANGED
@@ -84,13 +84,13 @@ Wrdler is a vocabulary learning game with a simplified grid and strategic letter
84
 
85
  ### πŸ† Daily & Weekly Leaderboards (v0.2.1) βœ…
86
  **Comprehensive Leaderboard System:**
87
- - **Daily Leaderboards:** Top 20 scores for each day (resets UTC midnight)
88
- - **Weekly Leaderboards:** Top 20 scores for each ISO week (resets Monday UTC 00:00)
89
  - **Settings-Based Separation:** Each unique combination of game settings creates a separate leaderboard
90
  - Settings include: game_mode, wordlist_source, show_incorrect_guesses, enable_free_letters, puzzle_options
91
  - You compete only with players using identical settings
92
  - **Sorting:** Scores sorted by: score (desc) β†’ time (asc) β†’ difficulty (desc)
93
- - **Qualification:** Only top 20 scores displayed per leaderboard (more can be stored)
94
  - **Storage:** Folder-based discovery in HuggingFace repo (no index.json)
95
  - Path: `games/leaderboards/{daily|weekly}/{period}/{file_id}/settings.json`
96
  - File ID format: `{wordlist_source}-{game_mode}-{sequence}` (e.g., `classic-classic-0`)
@@ -195,6 +195,7 @@ SPACE_NAME=YourUsername/Wrdler # Your HF Space name
195
 
196
  # Optional
197
  CRYPTO_PK= # Reserved for future signing
 
198
  ```
199
 
200
  **How to get your HF_API_TOKEN:**
@@ -251,7 +252,7 @@ All test files must be placed in the `/tests` folder. This ensures a clean proje
251
  **Daily and Weekly Leaderboards Improved**
252
  - βœ… Settings-based leaderboard separation (unique leaderboards per settings combo)
253
  - βœ… Folder-based discovery system (no index.json)
254
- - βœ… Top 20 displayed entries per leaderboard
255
  - βœ… Four-tab leaderboard page (Today, Daily, Weekly, History)
256
  - βœ… Automatic score qualification and submission
257
  - βœ… Query parameter filtering for direct links (`?gidd=`, `?gidw=`)
 
84
 
85
  ### πŸ† Daily & Weekly Leaderboards (v0.2.1) βœ…
86
  **Comprehensive Leaderboard System:**
87
+ - **Daily Leaderboards:** Top 25 scores for each day (resets UTC midnight)
88
+ - **Weekly Leaderboards:** Top 25 scores for each ISO week (resets Monday UTC 00:00)
89
  - **Settings-Based Separation:** Each unique combination of game settings creates a separate leaderboard
90
  - Settings include: game_mode, wordlist_source, show_incorrect_guesses, enable_free_letters, puzzle_options
91
  - You compete only with players using identical settings
92
  - **Sorting:** Scores sorted by: score (desc) β†’ time (asc) β†’ difficulty (desc)
93
+ - **Qualification:** Only top 25 scores displayed per leaderboard (more can be stored)
94
  - **Storage:** Folder-based discovery in HuggingFace repo (no index.json)
95
  - Path: `games/leaderboards/{daily|weekly}/{period}/{file_id}/settings.json`
96
  - File ID format: `{wordlist_source}-{game_mode}-{sequence}` (e.g., `classic-classic-0`)
 
195
 
196
  # Optional
197
  CRYPTO_PK= # Reserved for future signing
198
+ MAX_DISPLAY_ENTRIES=25 # Max leaderboard entries to display (default: 25)
199
  ```
200
 
201
  **How to get your HF_API_TOKEN:**
 
252
  **Daily and Weekly Leaderboards Improved**
253
  - βœ… Settings-based leaderboard separation (unique leaderboards per settings combo)
254
  - βœ… Folder-based discovery system (no index.json)
255
+ - βœ… Top 25 displayed entries per leaderboard
256
  - βœ… Four-tab leaderboard page (Today, Daily, Weekly, History)
257
  - βœ… Automatic score qualification and submission
258
  - βœ… Query parameter filtering for direct links (`?gidd=`, `?gidw=`)
env.template CHANGED
@@ -21,3 +21,4 @@ CRYPTO_PK=btc_public_key_here
21
  IS_LOCAL=true
22
  USE_HF_WORDS=false
23
  HF_WORD_LIST_REPO_ID=hf_username/word-lists
 
 
21
  IS_LOCAL=true
22
  USE_HF_WORDS=false
23
  HF_WORD_LIST_REPO_ID=hf_username/word-lists
24
+ MAX_DISPLAY_ENTRIES=25
specs/leaderboard_spec.md CHANGED
@@ -30,8 +30,8 @@
30
 
31
  This specification documents the implemented **Daily and Weekly Leaderboard System** for Wrdler. The system:
32
 
33
- - βœ… Tracks top 20 scores for daily leaderboards (resets at UTC midnight)
34
- - βœ… Tracks top 20 scores for weekly leaderboards (resets at UTC Monday 00:00)
35
  - βœ… Creates separate leaderboards for each unique combination of game-affecting settings
36
  - βœ… Automatically adds qualifying scores from any game completion (including challenge mode)
37
  - βœ… Provides a dedicated leaderboard page with historical lookup capabilities
@@ -49,9 +49,9 @@ This specification documents the implemented **Daily and Weekly Leaderboard Syst
49
 
50
  1. **Settings-Based Leaderboards**: Each unique combination of game-affecting settings creates a separate leaderboard. Players using different settings compete on different leaderboards.
51
 
52
- 2. **Daily Leaderboards**: Create and maintain daily leaderboards with top 20 entries displayed (can store more), organized by date folders (e.g., `games/leaderboards/daily/2025-01-27/`)
53
 
54
- 3. **Weekly Leaderboards**: Create and maintain weekly leaderboards with top 20 entries displayed (can store more), organized by ISO week folders (e.g., `games/leaderboards/weekly/2025-W04/`)
55
 
56
  4. **Automatic Qualification**: Every game completion (challenge or solo) checks if score qualifies for the matching daily/weekly leaderboard based on current settings
57
 
@@ -194,7 +194,7 @@ Instead of maintaining an `index.json` file, leaderboards are discovered by:
194
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
195
  β”‚ Check if score β”‚
196
  β”‚ qualifies (top β”‚
197
- β”‚ 20 displayed) β”‚
198
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
199
  β”‚
200
  β–Ό
@@ -250,7 +250,7 @@ Both leaderboard files and challenge files use the **same base structure**. The
250
  "enable_free_letters": true,
251
  "wordlist_source": "classic.txt",
252
  "game_title": "Wrdler Gradio AI",
253
- "max_display_entries": 20
254
  }
255
  ```
256
 
@@ -270,7 +270,7 @@ Both leaderboard files and challenge files use the **same base structure**. The
270
  | `enable_free_letters` | bool | Free letters feature toggle (defines leaderboard uniqueness) |
271
  | `wordlist_source` | string | Source wordlist file (defines leaderboard uniqueness) |
272
  | `game_title` | string | Game title for display |
273
- | `max_display_entries` | int | Maximum entries to display (default 20, can store more) |
274
 
275
  ### 4.4 User Entry Schema
276
 
@@ -667,7 +667,7 @@ The following represents the actual implementation as of v0.2.0:
667
  - βœ… `find_matching_leaderboard()` with prefix filtering and full verification
668
  - βœ… `create_or_get_leaderboard()` with automatic sequence management
669
  - βœ… `submit_score_to_all_leaderboards()` as main entry point
670
- - βœ… `check_qualification()` for top 20 filtering
671
  - βœ… Period ID generators: `get_current_daily_id()`, `get_current_weekly_id()`
672
  - βœ… Historical lookup functions: `list_available_periods()`, `list_settings_for_period()`
673
  - βœ… URL generation with `get_leaderboard_url()`
@@ -753,7 +753,7 @@ HF_REPO_ID/games/
753
  #### Known Limitations (as of v0.2.0)
754
 
755
  1. **No caching:** Each page load fetches from HF repository (can be slow)
756
- 2. **No pagination:** Displays top 20 only (additional entries stored but not shown)
757
  3. **Limited error handling:** Basic logging, could benefit from retry logic
758
  4. **No rate limiting:** Submission frequency not constrained
759
  5. **No archival:** Old leaderboards remain indefinitely (no cleanup script)
@@ -761,7 +761,7 @@ HF_REPO_ID/games/
761
  #### Future Enhancements (Planned for v0.3.0+)
762
 
763
  - ⏳ In-memory caching with TTL (60s for periods, 15s for leaderboards)
764
- - ⏳ Pagination for >20 entries
765
  - ⏳ Retry logic with exponential backoff
766
  - ⏳ Rate limiting per IP/session
767
  - ⏳ Archival script for old periods (>365 days daily, >156 weeks weekly)
@@ -833,7 +833,7 @@ HF_REPO_ID/games/
833
  - Provide a maintenance script to prune old periods and reindex cache.
834
  - Privacy:
835
  - Store only display names and gameplay metrics; avoid PII.
836
- - Users may choose β€œAnonymous”; do not display IP or identifiers publicly.
837
 
838
  ### 14.6 Time and Period Boundaries
839
 
 
30
 
31
  This specification documents the implemented **Daily and Weekly Leaderboard System** for Wrdler. The system:
32
 
33
+ - βœ… Tracks top 25 scores for daily leaderboards (resets at UTC midnight)
34
+ - βœ… Tracks top 25 scores for weekly leaderboards (resets at UTC Monday 00:00)
35
  - βœ… Creates separate leaderboards for each unique combination of game-affecting settings
36
  - βœ… Automatically adds qualifying scores from any game completion (including challenge mode)
37
  - βœ… Provides a dedicated leaderboard page with historical lookup capabilities
 
49
 
50
  1. **Settings-Based Leaderboards**: Each unique combination of game-affecting settings creates a separate leaderboard. Players using different settings compete on different leaderboards.
51
 
52
+ 2. **Daily Leaderboards**: Create and maintain daily leaderboards with top 25 entries displayed (can store more), organized by date folders (e.g., `games/leaderboards/daily/2025-01-27/`)
53
 
54
+ 3. **Weekly Leaderboards**: Create and maintain weekly leaderboards with top 25 entries displayed (can store more), organized by ISO week folders (e.g., `games/leaderboards/weekly/2025-W04/`)
55
 
56
  4. **Automatic Qualification**: Every game completion (challenge or solo) checks if score qualifies for the matching daily/weekly leaderboard based on current settings
57
 
 
194
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
195
  β”‚ Check if score β”‚
196
  β”‚ qualifies (top β”‚
197
+ β”‚ 25 displayed) β”‚
198
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
199
  β”‚
200
  β–Ό
 
250
  "enable_free_letters": true,
251
  "wordlist_source": "classic.txt",
252
  "game_title": "Wrdler Gradio AI",
253
+ "max_display_entries": 25
254
  }
255
  ```
256
 
 
270
  | `enable_free_letters` | bool | Free letters feature toggle (defines leaderboard uniqueness) |
271
  | `wordlist_source` | string | Source wordlist file (defines leaderboard uniqueness) |
272
  | `game_title` | string | Game title for display |
273
+ | `max_display_entries` | int | Maximum entries to display (default 25, configurable via MAX_DISPLAY_ENTRIES env var) |
274
 
275
  ### 4.4 User Entry Schema
276
 
 
667
  - βœ… `find_matching_leaderboard()` with prefix filtering and full verification
668
  - βœ… `create_or_get_leaderboard()` with automatic sequence management
669
  - βœ… `submit_score_to_all_leaderboards()` as main entry point
670
+ - βœ… `check_qualification()` for top 25 filtering
671
  - βœ… Period ID generators: `get_current_daily_id()`, `get_current_weekly_id()`
672
  - βœ… Historical lookup functions: `list_available_periods()`, `list_settings_for_period()`
673
  - βœ… URL generation with `get_leaderboard_url()`
 
753
  #### Known Limitations (as of v0.2.0)
754
 
755
  1. **No caching:** Each page load fetches from HF repository (can be slow)
756
+ 2. **No pagination:** Displays top 25 only (additional entries stored but not shown)
757
  3. **Limited error handling:** Basic logging, could benefit from retry logic
758
  4. **No rate limiting:** Submission frequency not constrained
759
  5. **No archival:** Old leaderboards remain indefinitely (no cleanup script)
 
761
  #### Future Enhancements (Planned for v0.3.0+)
762
 
763
  - ⏳ In-memory caching with TTL (60s for periods, 15s for leaderboards)
764
+ - ⏳ Pagination for >25 entries
765
  - ⏳ Retry logic with exponential backoff
766
  - ⏳ Rate limiting per IP/session
767
  - ⏳ Archival script for old periods (>365 days daily, >156 weeks weekly)
 
833
  - Provide a maintenance script to prune old periods and reindex cache.
834
  - Privacy:
835
  - Store only display names and gameplay metrics; avoid PII.
836
+ - Users must enter a name (Anonymous not allowed); do not display IP or identifiers publicly.
837
 
838
  ### 14.6 Time and Period Boundaries
839
 
specs/requirements.md CHANGED
@@ -232,13 +232,15 @@ This document breaks down the implementation tasks for Wrdler using the game rul
232
  - Scans folders for period IDs
233
  - Filters by file_id prefix
234
  - Loads and verifies full settings match
235
- - βœ… **Top 20 Display:** Sorted by score β†’ time β†’ difficulty
 
 
236
  - βœ… **Automatic Submission:** From game over popup
237
  - βœ… **Challenge Integration:** Tracks source_challenge_id
238
 
239
  **Storage Structure:**
240
  ```
241
- games/
242
  β”œβ”€β”€ leaderboards/
243
  β”‚ β”œβ”€β”€ daily/{YYYY-MM-DD}/{file_id}/settings.json
244
  β”‚ └── weekly/{YYYY-Www}/{file_id}/settings.json
@@ -278,13 +280,13 @@ games/
278
  - `get_current_daily_id()` / `get_current_weekly_id()` - Period ID generators
279
 
280
  **Privacy:**
281
- - Only stores: username (optional), score, time, word_list, difficulty
282
- - No PII beyond optional player name
283
  - All data in HuggingFace repository
284
 
285
  **Known Limitations (v0.2.1):**
286
  - No caching (future)
287
- - No pagination beyond top 20
288
  - Basic error handling
289
  - No rate limiting
290
  - No archival script
 
232
  - Scans folders for period IDs
233
  - Filters by file_id prefix
234
  - Loads and verifies full settings match
235
+ - βœ… **Top 25 Display:** Sorted by score β†’ time β†’ difficulty
236
+ - **Sorting:** Scores sorted by: score (desc) β†’ time (asc) β†’ difficulty (desc)
237
+ - **Qualification:** Only top 25 (configurable) scores displayed per leaderboard (more can be stored)
238
  - βœ… **Automatic Submission:** From game over popup
239
  - βœ… **Challenge Integration:** Tracks source_challenge_id
240
 
241
  **Storage Structure:**
242
  ```
243
+ HF_REPO_ID/games/
244
  β”œβ”€β”€ leaderboards/
245
  β”‚ β”œβ”€β”€ daily/{YYYY-MM-DD}/{file_id}/settings.json
246
  β”‚ └── weekly/{YYYY-Www}/{file_id}/settings.json
 
280
  - `get_current_daily_id()` / `get_current_weekly_id()` - Period ID generators
281
 
282
  **Privacy:**
283
+ - Only stores: username (required), score, time, word_list, difficulty
284
+ - No PII beyond required player name
285
  - All data in HuggingFace repository
286
 
287
  **Known Limitations (v0.2.1):**
288
  - No caching (future)
289
+ - No pagination beyond top 25
290
  - Basic error handling
291
  - No rate limiting
292
  - No archival script
specs/specs.md CHANGED
@@ -104,7 +104,7 @@ Wrdler features a comprehensive daily and weekly leaderboard system:
104
  - `enable_free_letters` (boolean)
105
  - `puzzle_options` (spacer, may_overlap)
106
  - **Sorting:** Scores sorted by: score (desc) β†’ time (asc) β†’ difficulty (desc)
107
- - **Qualification:** Only top 20 scores displayed per leaderboard (more can be stored)
108
 
109
  **Storage Structure:**
110
  ```
 
104
  - `enable_free_letters` (boolean)
105
  - `puzzle_options` (spacer, may_overlap)
106
  - **Sorting:** Scores sorted by: score (desc) β†’ time (asc) β†’ difficulty (desc)
107
+ - **Qualification:** Only top 25 (configurable) scores displayed per leaderboard (more can be stored)
108
 
109
  **Storage Structure:**
110
  ```
wrdler/__init__.py CHANGED
@@ -9,5 +9,5 @@ Key differences from BattleWords:
9
  - Daily and weekly leaderboards
10
  """
11
 
12
- __version__ = "0.2.4"
13
  __all__ = ["models", "generator", "logic", "ui", "word_loader", "leaderboard", "leaderboard_page"]
 
9
  - Daily and weekly leaderboards
10
  """
11
 
12
+ __version__ = "0.2.5"
13
  __all__ = ["models", "generator", "logic", "ui", "word_loader", "leaderboard", "leaderboard_page"]
wrdler/leaderboard.py CHANGED
@@ -6,7 +6,7 @@ Manages daily and weekly leaderboards with automatic score submission,
6
  qualification checking, and historical lookup.
7
 
8
  Leaderboard Configuration:
9
- - Max display entries: 20 per leaderboard (can store more)
10
  - Daily reset: UTC midnight
11
  - Weekly reset: Monday UTC 00:00 (ISO week)
12
  - Sorting: score (desc), time (asc), difficulty (desc)
@@ -23,7 +23,7 @@ File ID Format:
23
  {wordlist_source}-{game_mode}-{sequence}
24
  Example: classic-classic-0, easy-easy-1
25
  """
26
- __version__ = "0.2.0"
27
 
28
  from dataclasses import dataclass, field
29
  from datetime import datetime, timezone, timedelta
@@ -36,13 +36,13 @@ from wrdler.modules.storage import (
36
  _upload_json_to_repo,
37
  _list_repo_folders
38
  )
39
- from wrdler.modules.constants import HF_REPO_ID, APP_SETTINGS
40
  from wrdler.game_storage import generate_uid
41
 
42
  logger = logging.getLogger(__name__)
43
 
44
  # Configuration
45
- MAX_DISPLAY_ENTRIES = 20
46
  LEADERBOARD_BASE_PATH = "games/leaderboards"
47
  DAILY_LEADERBOARD_PATH = f"{LEADERBOARD_BASE_PATH}/daily"
48
  WEEKLY_LEADERBOARD_PATH = f"{LEADERBOARD_BASE_PATH}/weekly"
@@ -636,10 +636,10 @@ def check_qualification(
636
  word_list_difficulty: Optional[float] = None
637
  ) -> bool:
638
  """
639
- Check if a score qualifies for the leaderboard display (top 20).
640
 
641
- Note: The leaderboard can store more than 20 entries, but only top 20 are displayed.
642
- This function checks if the score would be in the top 20.
643
 
644
  Args:
645
  leaderboard: Existing leaderboard (or None if new)
@@ -653,7 +653,7 @@ def check_qualification(
653
  if leaderboard is None or len(leaderboard.users) < MAX_DISPLAY_ENTRIES:
654
  return True
655
 
656
- # Get the 20th entry (last displayed)
657
  display_users = leaderboard.get_display_users()
658
  if len(display_users) < MAX_DISPLAY_ENTRIES:
659
  return True
@@ -695,7 +695,7 @@ def get_leaderboard_url(entry_type: EntryType, period_id: str, file_id: str, bas
695
 
696
  # Determine base URL (use current host or default to production)
697
  if base_url is None:
698
- # Try to get from environment or default to production space
699
  import os
700
  from wrdler.modules.constants import SPACE_NAME
701
 
@@ -775,7 +775,7 @@ def submit_to_leaderboard(
775
  break
776
 
777
  if rank is None:
778
- # Entry was sorted out of top 20
779
  logger.info(f"? Score {user_entry.score} was sorted out of top {MAX_DISPLAY_ENTRIES}")
780
  # Still save the entry (stored but not displayed)
781
  save_leaderboard(leaderboard, file_id, repo_id)
 
6
  qualification checking, and historical lookup.
7
 
8
  Leaderboard Configuration:
9
+ - Max display entries: 25 per leaderboard (can store more)
10
  - Daily reset: UTC midnight
11
  - Weekly reset: Monday UTC 00:00 (ISO week)
12
  - Sorting: score (desc), time (asc), difficulty (desc)
 
23
  {wordlist_source}-{game_mode}-{sequence}
24
  Example: classic-classic-0, easy-easy-1
25
  """
26
+ __version__ = "0.2.1"
27
 
28
  from dataclasses import dataclass, field
29
  from datetime import datetime, timezone, timedelta
 
36
  _upload_json_to_repo,
37
  _list_repo_folders
38
  )
39
+ from wrdler.modules.constants import HF_REPO_ID, APP_SETTINGS, MAX_DISPLAY_ENTRIES
40
  from wrdler.game_storage import generate_uid
41
 
42
  logger = logging.getLogger(__name__)
43
 
44
  # Configuration
45
+ # MAX_DISPLAY_ENTRIES = 30 from environmental variables in constants.py
46
  LEADERBOARD_BASE_PATH = "games/leaderboards"
47
  DAILY_LEADERBOARD_PATH = f"{LEADERBOARD_BASE_PATH}/daily"
48
  WEEKLY_LEADERBOARD_PATH = f"{LEADERBOARD_BASE_PATH}/weekly"
 
636
  word_list_difficulty: Optional[float] = None
637
  ) -> bool:
638
  """
639
+ Check if a score qualifies for the leaderboard display (top 30).
640
 
641
+ Note: The leaderboard can store more than 30 entries, but only top 30 are displayed.
642
+ This function checks if the score would be in the top 30.
643
 
644
  Args:
645
  leaderboard: Existing leaderboard (or None if new)
 
653
  if leaderboard is None or len(leaderboard.users) < MAX_DISPLAY_ENTRIES:
654
  return True
655
 
656
+ # Get the 30th entry (last displayed)
657
  display_users = leaderboard.get_display_users()
658
  if len(display_users) < MAX_DISPLAY_ENTRIES:
659
  return True
 
695
 
696
  # Determine base URL (use current host or default to production)
697
  if base_url is None:
698
+ # Try to get from environment or default to production
699
  import os
700
  from wrdler.modules.constants import SPACE_NAME
701
 
 
775
  break
776
 
777
  if rank is None:
778
+ # Entry was sorted out of top 30
779
  logger.info(f"? Score {user_entry.score} was sorted out of top {MAX_DISPLAY_ENTRIES}")
780
  # Still save the entry (stored but not displayed)
781
  save_leaderboard(leaderboard, file_id, repo_id)
wrdler/leaderboard_page.py CHANGED
@@ -134,7 +134,9 @@ def _render_leaderboard_table(leaderboard: Optional[LeaderboardSettings], title:
134
  'text-align: center; color: #20d46c; font-weight: bold;' if col == 'Score' else
135
  'text-align: center;' for col in df.columns], axis=1)
136
 
137
- st.dataframe(styled_df, use_container_width=True, hide_index=True)
 
 
138
 
139
  # Show entry count and last updated
140
  total_entries = len(leaderboard.users)
@@ -358,7 +360,7 @@ def _render_history_tab():
358
 
359
  with col2:
360
  st.subheader("Weekly History")
361
- weekly_periods = list_available_periods("weekly", limit=20)
362
  if weekly_periods:
363
  selected_weekly = st.selectbox(
364
  "Select a week",
 
134
  'text-align: center; color: #20d46c; font-weight: bold;' if col == 'Score' else
135
  'text-align: center;' for col in df.columns], axis=1)
136
 
137
+ # Calculate height to show all rows (approx 35px per row + 38px header)
138
+ height = (len(df) + 1) * 35 + 3
139
+ st.dataframe(styled_df, use_container_width=True, hide_index=True, height=height)
140
 
141
  # Show entry count and last updated
142
  total_entries = len(leaderboard.users)
 
360
 
361
  with col2:
362
  st.subheader("Weekly History")
363
+ weekly_periods = list_available_periods("weekly", limit=6)
364
  if weekly_periods:
365
  selected_weekly = st.selectbox(
366
  "Select a week",
wrdler/modules/constants.py CHANGED
@@ -25,6 +25,7 @@ SPACE_NAME = os.getenv('SPACE_NAME', 'Surn/Wrdler')
25
  SHORTENER_JSON_FILE = "shortener.json"
26
  USE_HF_WORDS = os.getenv("USE_HF_WORDS", "false").lower() == "true"
27
  HF_WORD_LIST_REPO_ID = os.getenv("HF_WORD_LIST_REPO_ID", "ysharma/Chat_with_Meta_llama3_1_8b")
 
28
 
29
  # List of smaller, faster fallback models if the primary one fails
30
  AI_MODELS = [
@@ -80,6 +81,7 @@ def load_settings() -> Dict[str, Any]:
80
  "words_per_puzzle": 6,
81
  "free_letters_count": 2,
82
  "max_incorrect_guesses": 10,
 
83
  }
84
 
85
  try:
 
25
  SHORTENER_JSON_FILE = "shortener.json"
26
  USE_HF_WORDS = os.getenv("USE_HF_WORDS", "false").lower() == "true"
27
  HF_WORD_LIST_REPO_ID = os.getenv("HF_WORD_LIST_REPO_ID", "ysharma/Chat_with_Meta_llama3_1_8b")
28
+ MAX_DISPLAY_ENTRIES = int(os.getenv("MAX_DISPLAY_ENTRIES", 25))
29
 
30
  # List of smaller, faster fallback models if the primary one fails
31
  AI_MODELS = [
 
81
  "words_per_puzzle": 6,
82
  "free_letters_count": 2,
83
  "max_incorrect_guesses": 10,
84
+ "max_display_entries": MAX_DISPLAY_ENTRIES,
85
  }
86
 
87
  try:
wrdler/ui.py CHANGED
@@ -545,7 +545,7 @@ border-radius: 50% !important;
545
  position:relative;
546
  z-index: 1200;
547
  }
548
- .username_input [id^="text_input"], .st-key-username_input [id^="text_input"] { color: #fff;}
549
  .st-emotion-cache-18kf3ut, .stColumn.st-emotion-cache-116javk {padding-bottom:3px;}
550
 
551
  /* grid adjustments */
@@ -2038,15 +2038,15 @@ def _game_over_content(state: GameState) -> None:
2038
  st.session_state["player_username"] = ""
2039
 
2040
  username = st.text_input(
2041
- "Enter your name (optional)",
2042
  value=st.session_state.get("player_username", ""),
2043
  key="username_input",
2044
- placeholder="Anonymous"
2045
  )
2046
  if username:
2047
  st.session_state["player_username"] = username
2048
- else:
2049
- username = "Anonymous"
2050
 
2051
  # Helper function to submit to leaderboards
2052
  def _submit_to_leaderboards(username: str, score: int, time_secs: int, word_list: list, challenge_id: str = None):
@@ -2105,7 +2105,10 @@ def _game_over_content(state: GameState) -> None:
2105
  if "share_url" not in st.session_state or st.session_state.get("share_url") is None:
2106
  button_text = "πŸ“Š Submit Your Result" if is_shared_game else "πŸ”— Generate Share Link"
2107
 
2108
- if st.button(button_text, key="generate_share_link", use_container_width=True):
 
 
 
2109
  try:
2110
  # Extract game data
2111
  word_list = [w.text for w in state.puzzle.words]
@@ -2186,7 +2189,7 @@ def _game_over_content(state: GameState) -> None:
2186
  if not is_shared_game and not st.session_state.get("leaderboard_submitted", False):
2187
  st.markdown("---")
2188
  st.markdown("##### πŸ† Or just submit to leaderboards")
2189
- if st.button("πŸ† Submit to Leaderboards", key="submit_leaderboard_only", use_container_width=True):
2190
  try:
2191
  word_list = [w.text for w in state.puzzle.words]
2192
  lb_results = _submit_to_leaderboards(
 
545
  position:relative;
546
  z-index: 1200;
547
  }
548
+ .username_input [id^="text_input"], .st-key-username_input [id^="text_input"] { color: #666;}
549
  .st-emotion-cache-18kf3ut, .stColumn.st-emotion-cache-116javk {padding-bottom:3px;}
550
 
551
  /* grid adjustments */
 
2038
  st.session_state["player_username"] = ""
2039
 
2040
  username = st.text_input(
2041
+ "Enter your name (required)",
2042
  value=st.session_state.get("player_username", ""),
2043
  key="username_input",
2044
+ placeholder="Your Name"
2045
  )
2046
  if username:
2047
  st.session_state["player_username"] = username
2048
+
2049
+ can_submit = bool(username and username.strip())
2050
 
2051
  # Helper function to submit to leaderboards
2052
  def _submit_to_leaderboards(username: str, score: int, time_secs: int, word_list: list, challenge_id: str = None):
 
2105
  if "share_url" not in st.session_state or st.session_state.get("share_url") is None:
2106
  button_text = "πŸ“Š Submit Your Result" if is_shared_game else "πŸ”— Generate Share Link"
2107
 
2108
+ if not can_submit:
2109
+ st.warning("⚠️ Please enter your name to submit.")
2110
+
2111
+ if st.button(button_text, key="generate_share_link", use_container_width=True, disabled=not can_submit):
2112
  try:
2113
  # Extract game data
2114
  word_list = [w.text for w in state.puzzle.words]
 
2189
  if not is_shared_game and not st.session_state.get("leaderboard_submitted", False):
2190
  st.markdown("---")
2191
  st.markdown("##### πŸ† Or just submit to leaderboards")
2192
+ if st.button("πŸ† Submit to Leaderboards", key="submit_leaderboard_only", use_container_width=True, disabled=not can_submit):
2193
  try:
2194
  word_list = [w.text for w in state.puzzle.words]
2195
  lb_results = _submit_to_leaderboards(