v0.2.5
Browse filesIncrease 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 +1 -0
- CLAUDE.md +2 -1
- README.md +5 -4
- env.template +1 -0
- specs/leaderboard_spec.md +11 -11
- specs/requirements.md +7 -5
- specs/specs.md +1 -1
- wrdler/__init__.py +1 -1
- wrdler/leaderboard.py +10 -10
- wrdler/leaderboard_page.py +4 -2
- wrdler/modules/constants.py +2 -0
- wrdler/ui.py +10 -7
|
@@ -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
|
|
@@ -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
|
| 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
|
|
@@ -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
|
| 88 |
-
- **Weekly Leaderboards:** Top
|
| 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
|
| 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
|
| 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=`)
|
|
@@ -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
|
|
@@ -30,8 +30,8 @@
|
|
| 30 |
|
| 31 |
This specification documents the implemented **Daily and Weekly Leaderboard System** for Wrdler. The system:
|
| 32 |
|
| 33 |
-
- β
Tracks top
|
| 34 |
-
- β
Tracks top
|
| 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
|
| 53 |
|
| 54 |
-
3. **Weekly Leaderboards**: Create and maintain weekly leaderboards with top
|
| 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 |
-
β
|
| 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":
|
| 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
|
| 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
|
| 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
|
| 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 >
|
| 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
|
| 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 |
|
|
@@ -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
|
|
|
|
|
|
|
| 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 (
|
| 282 |
-
- No PII beyond
|
| 283 |
- All data in HuggingFace repository
|
| 284 |
|
| 285 |
**Known Limitations (v0.2.1):**
|
| 286 |
- No caching (future)
|
| 287 |
-
- No pagination beyond top
|
| 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
|
|
@@ -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
|
| 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 |
```
|
|
@@ -9,5 +9,5 @@ Key differences from BattleWords:
|
|
| 9 |
- Daily and weekly leaderboards
|
| 10 |
"""
|
| 11 |
|
| 12 |
-
__version__ = "0.2.
|
| 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"]
|
|
@@ -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:
|
| 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.
|
| 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 =
|
| 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
|
| 640 |
|
| 641 |
-
Note: The leaderboard can store more than
|
| 642 |
-
This function checks if the score would be in the top
|
| 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
|
| 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
|
| 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
|
| 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)
|
|
@@ -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 |
-
|
|
|
|
|
|
|
| 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=
|
| 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",
|
|
@@ -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:
|
|
@@ -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: #
|
| 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 (
|
| 2042 |
value=st.session_state.get("player_username", ""),
|
| 2043 |
key="username_input",
|
| 2044 |
-
placeholder="
|
| 2045 |
)
|
| 2046 |
if username:
|
| 2047 |
st.session_state["player_username"] = username
|
| 2048 |
-
|
| 2049 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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(
|