v0.2.14: Leaderboard and UI Enhancements
Browse filesUpdated leaderboard navigation to footer, improved time precision with millisecond support, and refined daily/weekly leaderboard structure. Simplified "Today" tab to focus on daily leaderboards. Enhanced game-over dialog with integrated leaderboard submission and updated timer display.
Updated `UserEntry` and related functions for `float` time handling, ensuring backward compatibility. Improved UI clarity, fixed typos, and adjusted leaderboard rendering logic. Updated documentation to reflect new features and API changes. Performed bug fixes and code cleanup for consistency.
- CLAUDE.md +5 -5
- GAMEPLAY_GUIDE.md +3 -3
- README.md +5 -5
- specs/leaderboard_spec.md +19 -36
- specs/requirements.md +3 -3
- specs/specs.md +4 -4
- wrdler/__init__.py +2 -2
- wrdler/game_storage.py +8 -8
- wrdler/leaderboard.py +5 -5
- wrdler/leaderboard_page.py +60 -114
- wrdler/ui.py +8 -8
CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# CLAUDE
|
| 2 |
|
| 3 |
-
Wrdler v0.2.
|
| 4 |
|
| 5 |
# Wrdler - Project Context
|
| 6 |
|
|
@@ -12,18 +12,18 @@ Wrdler is a simplified vocabulary puzzle game based on BattleWords:
|
|
| 12 |
- **2 free letter guesses at game start** (all instances revealed)
|
| 13 |
- **Word composition:** 2 four-letter, 2 five-letter, 2 six-letter words per puzzle
|
| 14 |
|
| 15 |
-
**Current Version:** 0.2.
|
| 16 |
-
**Last Updated:**
|
| 17 |
**Repository:** https://github.com/Oncorporation/Wrdler.git
|
| 18 |
**Branch:** main
|
| 19 |
|
| 20 |
-
## Recent Changes (v0.2.
|
| 21 |
- Leaderboard navigation moved to footer menu (not sidebar)
|
| 22 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
| 23 |
- Leaderboard page routing uses query parameters and custom navigation links
|
| 24 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 25 |
|
| 26 |
-
## Current Features (v0.2.
|
| 27 |
|
| 28 |
### Core Gameplay
|
| 29 |
- 8x6 grid with 6 hidden words (one per row, horizontal only)
|
|
|
|
| 1 |
# CLAUDE
|
| 2 |
|
| 3 |
+
Wrdler v0.2.14
|
| 4 |
|
| 5 |
# Wrdler - Project Context
|
| 6 |
|
|
|
|
| 12 |
- **2 free letter guesses at game start** (all instances revealed)
|
| 13 |
- **Word composition:** 2 four-letter, 2 five-letter, 2 six-letter words per puzzle
|
| 14 |
|
| 15 |
+
**Current Version:** 0.2.14
|
| 16 |
+
**Last Updated:** 2026-01-05
|
| 17 |
**Repository:** https://github.com/Oncorporation/Wrdler.git
|
| 18 |
**Branch:** main
|
| 19 |
|
| 20 |
+
## Recent Changes (v0.2.14)
|
| 21 |
- Leaderboard navigation moved to footer menu (not sidebar)
|
| 22 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
| 23 |
- Leaderboard page routing uses query parameters and custom navigation links
|
| 24 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 25 |
|
| 26 |
+
## Current Features (v0.2.14)
|
| 27 |
|
| 28 |
### Core Gameplay
|
| 29 |
- 8x6 grid with 6 hidden words (one per row, horizontal only)
|
GAMEPLAY_GUIDE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
# Wrdler Gameplay Guide
|
| 2 |
|
| 3 |
-
Version 0.2.
|
| 4 |
|
| 5 |
-
**Last Updated:**
|
| 6 |
|
| 7 |
## Welcome to Wrdler!
|
| 8 |
|
|
@@ -10,7 +10,7 @@ Wrdler is a simplified vocabulary puzzle game where you discover 6 hidden words
|
|
| 10 |
|
| 11 |
---
|
| 12 |
|
| 13 |
-
## Recent Changes (v0.2.
|
| 14 |
- Leaderboard navigation moved to footer menu (not the sidebar)
|
| 15 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 16 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
|
|
|
| 1 |
# Wrdler Gameplay Guide
|
| 2 |
|
| 3 |
+
Version 0.2.14
|
| 4 |
|
| 5 |
+
**Last Updated:** 2026-01-05
|
| 6 |
|
| 7 |
## Welcome to Wrdler!
|
| 8 |
|
|
|
|
| 10 |
|
| 11 |
---
|
| 12 |
|
| 13 |
+
## Recent Changes (v0.2.14)
|
| 14 |
- Leaderboard navigation moved to footer menu (not the sidebar)
|
| 15 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 16 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
README.md
CHANGED
|
@@ -21,7 +21,7 @@ thumbnail: >-
|
|
| 21 |
|
| 22 |
# Wrdler
|
| 23 |
|
| 24 |
-
Version 0.2.
|
| 25 |
|
| 26 |
Wrdler is a vocabulary puzzle game with daily and weekly leaderboards, 8x6 grid, and two free letter guesses at the start. See CHANGELOG or specs for details on the latest updates.
|
| 27 |
|
|
@@ -29,12 +29,12 @@ Wrdler is a vocabulary puzzle game with daily and weekly leaderboards, 8x6 grid,
|
|
| 29 |
|
| 30 |
Wrdler is a vocabulary learning game with a simplified grid and strategic letter guessing. The objective is to discover hidden words on a grid by making smart guesses before all letters are revealed.
|
| 31 |
|
| 32 |
-
**Current Version:** v0.2.
|
| 33 |
-
**Last Updated:**
|
| 34 |
|
| 35 |
-
## Recent Changes (v0.2.
|
|
|
|
| 36 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 37 |
-
- Leaderboard navigation moved to footer menu (not sidebar)
|
| 38 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
| 39 |
- Leaderboard page routing uses query parameters and custom navigation links
|
| 40 |
|
|
|
|
| 21 |
|
| 22 |
# Wrdler
|
| 23 |
|
| 24 |
+
Version 0.2.14
|
| 25 |
|
| 26 |
Wrdler is a vocabulary puzzle game with daily and weekly leaderboards, 8x6 grid, and two free letter guesses at the start. See CHANGELOG or specs for details on the latest updates.
|
| 27 |
|
|
|
|
| 29 |
|
| 30 |
Wrdler is a vocabulary learning game with a simplified grid and strategic letter guessing. The objective is to discover hidden words on a grid by making smart guesses before all letters are revealed.
|
| 31 |
|
| 32 |
+
**Current Version:** v0.2.14
|
| 33 |
+
**Last Updated:** 2026-01-05
|
| 34 |
|
| 35 |
+
## Recent Changes (v0.2.14)
|
| 36 |
+
- Leaderboard navigation moved to footer menu (not the sidebar)
|
| 37 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
|
|
|
| 38 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
| 39 |
- Leaderboard page routing uses query parameters and custom navigation links
|
| 40 |
|
specs/leaderboard_spec.md
CHANGED
|
@@ -1,29 +1,16 @@
|
|
| 1 |
ο»Ώ# Wrdler Leaderboard System Specification
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
-## Recent Changes (v0.2.12)
|
| 15 |
-
-- Layout changes for improved usability
|
| 16 |
-
-- Fixed static spinner graphic and favicon
|
| 17 |
-
-- Background enable/disable toggles improved
|
| 18 |
-
-- Sidebar disabled for streamlined UI
|
| 19 |
-
+## Recent Changes (v0.2.13)
|
| 20 |
-
+- Leaderboard navigation moved to footer menu (not the sidebar)
|
| 21 |
-
+- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 22 |
-
+- Game over dialog integrates leaderboard submission and shows qualification results
|
| 23 |
-
+- Leaderboard page routing uses query parameters and custom navigation links
|
| 24 |
-
+
|
| 25 |
-
+## Planned (v0.2.14)
|
| 26 |
-
+- Documented API for collecting submitted words and estimating per-word difficulty from leaderboard `UserEntry` data (time/6 and score/6)
|
| 27 |
|
| 28 |
---
|
| 29 |
|
|
@@ -303,10 +290,10 @@ Each user entry in the `users` array:
|
|
| 303 |
"uid": "20251130T190249Z-0XLG5O",
|
| 304 |
"username": "Charles",
|
| 305 |
"word_list": ["CHEER", "PLENTY", "REAL", "ARRIVE", "DEAF", "DAILY"],
|
| 306 |
-
"word_list_difficulty": 117.48,
|
| 307 |
"score": 39,
|
| 308 |
"time": 132,
|
| 309 |
"timestamp": "2025-11-30T19:02:49.544933+00:00",
|
|
|
|
| 310 |
"source_challenge_id": null
|
| 311 |
}
|
| 312 |
```
|
|
@@ -316,11 +303,13 @@ Each user entry in the `users` array:
|
|
| 316 |
| `uid` | string | Unique user entry ID |
|
| 317 |
| `username` | string | Player display name |
|
| 318 |
| `word_list` | array | 6 words played |
|
| 319 |
-
| `word_list_difficulty` | float | Calculated difficulty score |
|
| 320 |
| `score` | int | Final score |
|
| 321 |
-
| `time` |
|
| 322 |
| `timestamp` | string | ISO 8601 when entry was recorded |
|
| 323 |
-
| `
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
### 4.5 Settings Matching
|
| 326 |
|
|
@@ -486,7 +475,7 @@ __version__ = "0.1.6" # Updated to add folder listing functions
|
|
| 486 |
def submit_score_to_all_leaderboards(
|
| 487 |
username: str,
|
| 488 |
score: int,
|
| 489 |
-
time_seconds:
|
| 490 |
word_list: List[str],
|
| 491 |
settings: GameSettings,
|
| 492 |
word_list_difficulty: Optional[float] = None,
|
|
@@ -933,10 +922,4 @@ HF_REPO_ID/games/
|
|
| 933 |
"YYYY-MM-DD HH:MM:SS PST to YYYY-MM-DD HH:MM:SS PST"
|
| 934 |
(PST is UTC-8; adjust for daylight saving as needed)
|
| 935 |
For example, a UTC file date of `2025-12-08` covers `2025-12-08 00:00:00 UTC` to `2025-12-08 23:59:59 UTC`, which is displayed as `2025-12-07 16:00:00 PST` to `2025-12-08 15:59:59 PST`.
|
| 936 |
-
The leaderboard expander label should show: `Mon, Dec 08, 2025 4:00 PM PST β Tue, Dec 09, 2025 3:59:59 PM PST [settings badge]`
|
| 937 |
-
|
| 938 |
-
**Leaderboard Page UI:**
|
| 939 |
-
- **Today Tab:** Current daily and weekly leaderboards
|
| 940 |
-
- **Daily Tab:** Last 7 days of daily leaderboards
|
| 941 |
-
- **Weekly Tab:** Last 5 weeks displayed as individual expanders (current week or `week=YYYY-Www` query opens by default)
|
| 942 |
-
- **History Tab:** Historical leaderboard browser
|
|
|
|
| 1 |
ο»Ώ# Wrdler Leaderboard System Specification
|
| 2 |
|
| 3 |
+
**Document Version:** 1.4.6
|
| 4 |
+
**Project Version:** 0.2.14
|
| 5 |
+
**Author:** GitHub Copilot
|
| 6 |
+
**Last Updated:** 2026-01-05
|
| 7 |
+
**Status:** β
Implemented and Documented
|
| 8 |
+
|
| 9 |
+
## Recent Changes (v0.2.14)
|
| 10 |
+
- Leaderboard navigation moved to footer menu (not the sidebar)
|
| 11 |
+
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 12 |
+
- Game over dialog integrates leaderboard submission and shows qualification results
|
| 13 |
+
- Leaderboard page routing uses query parameters and custom navigation links
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
---
|
| 16 |
|
|
|
|
| 290 |
"uid": "20251130T190249Z-0XLG5O",
|
| 291 |
"username": "Charles",
|
| 292 |
"word_list": ["CHEER", "PLENTY", "REAL", "ARRIVE", "DEAF", "DAILY"],
|
|
|
|
| 293 |
"score": 39,
|
| 294 |
"time": 132,
|
| 295 |
"timestamp": "2025-11-30T19:02:49.544933+00:00",
|
| 296 |
+
"word_list_difficulty": 117.48,
|
| 297 |
"source_challenge_id": null
|
| 298 |
}
|
| 299 |
```
|
|
|
|
| 303 |
| `uid` | string | Unique user entry ID |
|
| 304 |
| `username` | string | Player display name |
|
| 305 |
| `word_list` | array | 6 words played |
|
|
|
|
| 306 |
| `score` | int | Final score |
|
| 307 |
+
| `time` | float | Time in seconds (stored as `time`; legacy reads may also accept `time_seconds`) |
|
| 308 |
| `timestamp` | string | ISO 8601 when entry was recorded |
|
| 309 |
+
| `word_list_difficulty` | float\|null | Calculated difficulty score (optional) |
|
| 310 |
+
| `source_challenge_id` | string\|null | If from a challenge, the original `challenge_id` (optional) |
|
| 311 |
+
|
| 312 |
+
**Implementation note:** `UserEntry.from_dict()` supports both `time` and legacy `time_seconds` during load; serialization always writes `time`.
|
| 313 |
|
| 314 |
### 4.5 Settings Matching
|
| 315 |
|
|
|
|
| 475 |
def submit_score_to_all_leaderboards(
|
| 476 |
username: str,
|
| 477 |
score: int,
|
| 478 |
+
time_seconds: float,
|
| 479 |
word_list: List[str],
|
| 480 |
settings: GameSettings,
|
| 481 |
word_list_difficulty: Optional[float] = None,
|
|
|
|
| 922 |
"YYYY-MM-DD HH:MM:SS PST to YYYY-MM-DD HH:MM:SS PST"
|
| 923 |
(PST is UTC-8; adjust for daylight saving as needed)
|
| 924 |
For example, a UTC file date of `2025-12-08` covers `2025-12-08 00:00:00 UTC` to `2025-12-08 23:59:59 UTC`, which is displayed as `2025-12-07 16:00:00 PST` to `2025-12-08 15:59:59 PST`.
|
| 925 |
+
The leaderboard expander label should show: `Mon, Dec 08, 2025 4:00 PM PST β Tue, Dec 09, 2025 3:59:59 PM PST [settings badge]`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
specs/requirements.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
ο»Ώ# Wrdler Requirements
|
| 2 |
|
| 3 |
-
**Version:** 0.2.
|
| 4 |
**Status:** Production Ready - Leaderboards Implemented
|
| 5 |
-
**Last Updated:**
|
| 6 |
|
| 7 |
-
## Recent Changes (v0.2.
|
| 8 |
- Leaderboard navigation moved to footer menu (not sidebar)
|
| 9 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 10 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
|
|
|
| 1 |
ο»Ώ# Wrdler Requirements
|
| 2 |
|
| 3 |
+
**Version:** 0.2.14
|
| 4 |
**Status:** Production Ready - Leaderboards Implemented
|
| 5 |
+
**Last Updated:** 2026-01-05
|
| 6 |
|
| 7 |
+
## Recent Changes (v0.2.14)
|
| 8 |
- Leaderboard navigation moved to footer menu (not sidebar)
|
| 9 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 10 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
specs/specs.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
# Wrdler Specifications
|
| 2 |
|
| 3 |
-
**Version:** 0.2.
|
| 4 |
-
**Last Updated:**
|
| 5 |
|
| 6 |
**Status:** Production Ready - Leaderboards & Enhanced Settings Page Implemented
|
| 7 |
|
| 8 |
-
## Recent Changes (v0.2.
|
| 9 |
- Leaderboard navigation moved to footer menu (not sidebar)
|
| 10 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 11 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
|
@@ -145,7 +145,7 @@ HF_REPO_ID/games/
|
|
| 145 |
- **Daily Tab:** Last 7 days of daily leaderboards with expandable date groups
|
| 146 |
- Expandable groups per date
|
| 147 |
- All settings combinations shown
|
| 148 |
-
- **Weekly Tab:** Last 5 weeks, each rendered as its own expander (current or `week=YYYY-
|
| 149 |
- All settings combinations displayed
|
| 150 |
- **History Tab:** Historical leaderboard browser with dropdown selectors
|
| 151 |
- Dropdown selectors for period and settings
|
|
|
|
| 1 |
# Wrdler Specifications
|
| 2 |
|
| 3 |
+
**Version:** 0.2.14
|
| 4 |
+
**Last Updated:** 2026-01-05
|
| 5 |
|
| 6 |
**Status:** Production Ready - Leaderboards & Enhanced Settings Page Implemented
|
| 7 |
|
| 8 |
+
## Recent Changes (v0.2.14)
|
| 9 |
- Leaderboard navigation moved to footer menu (not sidebar)
|
| 10 |
- Footer navigation links to Leaderboard, Play, and Settings pages
|
| 11 |
- Game over dialog integrates leaderboard submission and shows qualification results
|
|
|
|
| 145 |
- **Daily Tab:** Last 7 days of daily leaderboards with expandable date groups
|
| 146 |
- Expandable groups per date
|
| 147 |
- All settings combinations shown
|
| 148 |
+
- **Weekly Tab:** Last 5 weeks, each rendered as its own expander (current or `week=YYYY-Ww` query selection opens by default)
|
| 149 |
- All settings combinations displayed
|
| 150 |
- **History Tab:** Historical leaderboard browser with dropdown selectors
|
| 151 |
- Dropdown selectors for period and settings
|
wrdler/__init__.py
CHANGED
|
@@ -8,8 +8,8 @@ Key differences from BattleWords:
|
|
| 8 |
- 2 free letter guesses at game start
|
| 9 |
- Daily and weekly leaderboards
|
| 10 |
|
| 11 |
-
v0.2.
|
| 12 |
"""
|
| 13 |
|
| 14 |
-
__version__ = "0.2.
|
| 15 |
__all__ = ["models", "generator", "logic", "ui", "word_loader", "leaderboard", "leaderboard_page"]
|
|
|
|
| 8 |
- 2 free letter guesses at game start
|
| 9 |
- Daily and weekly leaderboards
|
| 10 |
|
| 11 |
+
v0.2.14: Leaderboard changes to reduce API calls, add millisecond precision to game time.
|
| 12 |
"""
|
| 13 |
|
| 14 |
+
__version__ = "0.2.14"
|
| 15 |
__all__ = ["models", "generator", "logic", "ui", "word_loader", "leaderboard", "leaderboard_page"]
|
wrdler/game_storage.py
CHANGED
|
@@ -12,7 +12,7 @@ Wrdler Specifications:
|
|
| 12 |
- No word overlaps
|
| 13 |
- 2 free letter guesses at game start
|
| 14 |
"""
|
| 15 |
-
__version__ = "0.2.
|
| 16 |
|
| 17 |
import json
|
| 18 |
import tempfile
|
|
@@ -60,7 +60,7 @@ def serialize_game_settings(
|
|
| 60 |
word_list: List[str],
|
| 61 |
username: str,
|
| 62 |
score: int,
|
| 63 |
-
time_seconds:
|
| 64 |
game_mode: str,
|
| 65 |
grid_size: int = 8,
|
| 66 |
spacer: int = 0,
|
|
@@ -160,7 +160,7 @@ def add_user_result_to_game(
|
|
| 160 |
username: str,
|
| 161 |
word_list: List[str],
|
| 162 |
score: int,
|
| 163 |
-
time_seconds:
|
| 164 |
repo_id: Optional[str] = None
|
| 165 |
) -> bool:
|
| 166 |
"""
|
|
@@ -215,7 +215,7 @@ def add_user_result_to_game(
|
|
| 215 |
if difficulty_value is not None:
|
| 216 |
user_result["word_list_difficulty"] = difficulty_value
|
| 217 |
user_result["score"] = score
|
| 218 |
-
user_result["time"] = time_seconds
|
| 219 |
user_result["timestamp"] = datetime.now(timezone.utc).isoformat()
|
| 220 |
|
| 221 |
# Add to users array
|
|
@@ -275,7 +275,7 @@ def save_game_to_hf(
|
|
| 275 |
word_list: List[str],
|
| 276 |
username: str,
|
| 277 |
score: int,
|
| 278 |
-
time_seconds:
|
| 279 |
game_mode: str,
|
| 280 |
grid_size: int = 8,
|
| 281 |
spacer: int = 0,
|
|
@@ -332,7 +332,7 @@ def save_game_to_hf(
|
|
| 332 |
... word_list=["WORD", "TEST", "GAME", "PLAY", "SCORE", "FINAL"],
|
| 333 |
... username="Alice",
|
| 334 |
... score=42,
|
| 335 |
-
... time_seconds=180,
|
| 336 |
... game_mode="classic",
|
| 337 |
... wordlist_source="classic.txt"
|
| 338 |
... )
|
|
@@ -349,7 +349,7 @@ def save_game_to_hf(
|
|
| 349 |
word_list=word_list,
|
| 350 |
username=username,
|
| 351 |
score=score,
|
| 352 |
-
time_seconds=time_seconds,
|
| 353 |
game_mode=game_mode,
|
| 354 |
grid_size=grid_size,
|
| 355 |
spacer=spacer,
|
|
@@ -579,7 +579,7 @@ if __name__ == "__main__":
|
|
| 579 |
word_list=["WORD", "TEST", "GAME", "PLAY", "SCORE", "FINAL"],
|
| 580 |
username="Alice",
|
| 581 |
score=42,
|
| 582 |
-
time_seconds=180,
|
| 583 |
game_mode="classic",
|
| 584 |
grid_size=8, # Wrdler default
|
| 585 |
wordlist_source="classic.txt"
|
|
|
|
| 12 |
- No word overlaps
|
| 13 |
- 2 free letter guesses at game start
|
| 14 |
"""
|
| 15 |
+
__version__ = "0.2.1"
|
| 16 |
|
| 17 |
import json
|
| 18 |
import tempfile
|
|
|
|
| 60 |
word_list: List[str],
|
| 61 |
username: str,
|
| 62 |
score: int,
|
| 63 |
+
time_seconds: float,
|
| 64 |
game_mode: str,
|
| 65 |
grid_size: int = 8,
|
| 66 |
spacer: int = 0,
|
|
|
|
| 160 |
username: str,
|
| 161 |
word_list: List[str],
|
| 162 |
score: int,
|
| 163 |
+
time_seconds: float,
|
| 164 |
repo_id: Optional[str] = None
|
| 165 |
) -> bool:
|
| 166 |
"""
|
|
|
|
| 215 |
if difficulty_value is not None:
|
| 216 |
user_result["word_list_difficulty"] = difficulty_value
|
| 217 |
user_result["score"] = score
|
| 218 |
+
user_result["time"] = float(time_seconds)
|
| 219 |
user_result["timestamp"] = datetime.now(timezone.utc).isoformat()
|
| 220 |
|
| 221 |
# Add to users array
|
|
|
|
| 275 |
word_list: List[str],
|
| 276 |
username: str,
|
| 277 |
score: int,
|
| 278 |
+
time_seconds: float,
|
| 279 |
game_mode: str,
|
| 280 |
grid_size: int = 8,
|
| 281 |
spacer: int = 0,
|
|
|
|
| 332 |
... word_list=["WORD", "TEST", "GAME", "PLAY", "SCORE", "FINAL"],
|
| 333 |
... username="Alice",
|
| 334 |
... score=42,
|
| 335 |
+
... time_seconds=180.1,
|
| 336 |
... game_mode="classic",
|
| 337 |
... wordlist_source="classic.txt"
|
| 338 |
... )
|
|
|
|
| 349 |
word_list=word_list,
|
| 350 |
username=username,
|
| 351 |
score=score,
|
| 352 |
+
time_seconds=float(time_seconds),
|
| 353 |
game_mode=game_mode,
|
| 354 |
grid_size=grid_size,
|
| 355 |
spacer=spacer,
|
|
|
|
| 579 |
word_list=["WORD", "TEST", "GAME", "PLAY", "SCORE", "FINAL"],
|
| 580 |
username="Alice",
|
| 581 |
score=42,
|
| 582 |
+
time_seconds=180.01,
|
| 583 |
game_mode="classic",
|
| 584 |
grid_size=8, # Wrdler default
|
| 585 |
wordlist_source="classic.txt"
|
wrdler/leaderboard.py
CHANGED
|
@@ -198,7 +198,7 @@ class UserEntry:
|
|
| 198 |
username: str
|
| 199 |
word_list: List[str]
|
| 200 |
score: int
|
| 201 |
-
time:
|
| 202 |
timestamp: str
|
| 203 |
word_list_difficulty: Optional[float] = None
|
| 204 |
source_challenge_id: Optional[str] = None # If entry came from a challenge
|
|
@@ -227,7 +227,7 @@ class UserEntry:
|
|
| 227 |
username=data["username"],
|
| 228 |
word_list=data["word_list"],
|
| 229 |
score=data["score"],
|
| 230 |
-
time=data.get("time", data.get("time_seconds", 0)), # Handle both field names
|
| 231 |
timestamp=data["timestamp"],
|
| 232 |
word_list_difficulty=data.get("word_list_difficulty"),
|
| 233 |
source_challenge_id=data.get("source_challenge_id"),
|
|
@@ -611,7 +611,7 @@ def save_leaderboard(
|
|
| 611 |
def create_user_entry(
|
| 612 |
username: str,
|
| 613 |
score: int,
|
| 614 |
-
time_seconds:
|
| 615 |
word_list: List[str],
|
| 616 |
word_list_difficulty: Optional[float] = None,
|
| 617 |
source_challenge_id: Optional[str] = None
|
|
@@ -632,7 +632,7 @@ def create_user_entry(
|
|
| 632 |
def check_qualification(
|
| 633 |
leaderboard: Optional[LeaderboardSettings],
|
| 634 |
score: int,
|
| 635 |
-
time_seconds:
|
| 636 |
word_list_difficulty: Optional[float] = None
|
| 637 |
) -> bool:
|
| 638 |
"""
|
|
@@ -793,7 +793,7 @@ def submit_to_leaderboard(
|
|
| 793 |
def submit_score_to_all_leaderboards(
|
| 794 |
username: str,
|
| 795 |
score: int,
|
| 796 |
-
time_seconds:
|
| 797 |
word_list: List[str],
|
| 798 |
settings: GameSettings,
|
| 799 |
word_list_difficulty: Optional[float] = None,
|
|
|
|
| 198 |
username: str
|
| 199 |
word_list: List[str]
|
| 200 |
score: int
|
| 201 |
+
time: float # seconds (matches existing 'time' field, not 'time_seconds')
|
| 202 |
timestamp: str
|
| 203 |
word_list_difficulty: Optional[float] = None
|
| 204 |
source_challenge_id: Optional[str] = None # If entry came from a challenge
|
|
|
|
| 227 |
username=data["username"],
|
| 228 |
word_list=data["word_list"],
|
| 229 |
score=data["score"],
|
| 230 |
+
time=data.get("time", data.get("time_seconds", 0.0)), # Handle both field names
|
| 231 |
timestamp=data["timestamp"],
|
| 232 |
word_list_difficulty=data.get("word_list_difficulty"),
|
| 233 |
source_challenge_id=data.get("source_challenge_id"),
|
|
|
|
| 611 |
def create_user_entry(
|
| 612 |
username: str,
|
| 613 |
score: int,
|
| 614 |
+
time_seconds: float,
|
| 615 |
word_list: List[str],
|
| 616 |
word_list_difficulty: Optional[float] = None,
|
| 617 |
source_challenge_id: Optional[str] = None
|
|
|
|
| 632 |
def check_qualification(
|
| 633 |
leaderboard: Optional[LeaderboardSettings],
|
| 634 |
score: int,
|
| 635 |
+
time_seconds: float,
|
| 636 |
word_list_difficulty: Optional[float] = None
|
| 637 |
) -> bool:
|
| 638 |
"""
|
|
|
|
| 793 |
def submit_score_to_all_leaderboards(
|
| 794 |
username: str,
|
| 795 |
score: int,
|
| 796 |
+
time_seconds: float,
|
| 797 |
word_list: List[str],
|
| 798 |
settings: GameSettings,
|
| 799 |
word_list_difficulty: Optional[float] = None,
|
wrdler/leaderboard_page.py
CHANGED
|
@@ -106,10 +106,10 @@ def _get_pst_range_str_from_utc_timestamp(utc_ts) -> str:
|
|
| 106 |
except Exception:
|
| 107 |
return str(utc_ts)
|
| 108 |
|
| 109 |
-
def _format_time(seconds:
|
| 110 |
"""Format seconds as MM:SS."""
|
| 111 |
mins, secs = divmod(seconds, 60)
|
| 112 |
-
return f"{mins:
|
| 113 |
|
| 114 |
|
| 115 |
def _get_rank_emoji(rank: int) -> str:
|
|
@@ -322,8 +322,8 @@ def render_leaderboard_page(default_tab: str = "daily"):
|
|
| 322 |
f"""
|
| 323 |
<div class="bw-tab-nav">
|
| 324 |
<a href="?page=today" target="_self" class="{'active' if active_tab == 'today' else ''}">π Today</a>
|
| 325 |
-
<a href="?page=daily" target="_self" class="{'active' if active_tab == 'daily' else ''}">π
Daily</a>
|
| 326 |
-
<a href="?page=weekly" target="_self" class="{'active' if active_tab == 'weekly' else ''}">π
|
| 327 |
<a href="?page=history" target="_self" class="{'active' if active_tab == 'history' else ''}">π History</a>
|
| 328 |
</div>
|
| 329 |
""",
|
|
@@ -352,7 +352,7 @@ def _render_daily_tab():
|
|
| 352 |
# Show last 7 days; for each day, show all leaderboards (settings combos)
|
| 353 |
daily_periods = list_available_periods("daily", limit=7)
|
| 354 |
if not daily_periods:
|
| 355 |
-
st.info("No daily leaderboards
|
| 356 |
return
|
| 357 |
|
| 358 |
for date_id in daily_periods:
|
|
@@ -384,7 +384,7 @@ def _render_daily_tab():
|
|
| 384 |
|
| 385 |
def _render_weekly_tab():
|
| 386 |
"""Render weekly leaderboard tab."""
|
| 387 |
-
st.header("π
|
| 388 |
st.write(f"Top {MAX_DISPLAY_ENTRIES} scores for each ISO week. Resets Monday at UTC midnight.")
|
| 389 |
|
| 390 |
try:
|
|
@@ -395,9 +395,9 @@ def _render_weekly_tab():
|
|
| 395 |
requested_week = params.get("week")
|
| 396 |
current_week = get_current_weekly_id()
|
| 397 |
|
| 398 |
-
weekly_periods = list_available_periods("weekly", limit=
|
| 399 |
if not weekly_periods:
|
| 400 |
-
st.info("No weekly leaderboards
|
| 401 |
return
|
| 402 |
|
| 403 |
if requested_week in weekly_periods:
|
|
@@ -418,7 +418,7 @@ def _render_weekly_tab():
|
|
| 418 |
expander_title = f"ποΈ {week_title}"
|
| 419 |
with st.expander(expander_title, expanded=(week_id == expanded_week)):
|
| 420 |
if not settings_list:
|
| 421 |
-
st.info("No leaderboards
|
| 422 |
continue
|
| 423 |
|
| 424 |
for idx, settings_info in enumerate(settings_list):
|
|
@@ -442,7 +442,7 @@ def _render_history_tab():
|
|
| 442 |
|
| 443 |
weekly_periods = list_available_periods("weekly", limit=12)
|
| 444 |
if not weekly_periods:
|
| 445 |
-
st.info("No weekly leaderboards
|
| 446 |
return
|
| 447 |
|
| 448 |
week_options = [(_format_week_title(week_id), week_id) for week_id in weekly_periods]
|
|
@@ -512,14 +512,14 @@ def _render_history_tab():
|
|
| 512 |
with col1:
|
| 513 |
st.subheader("Daily History")
|
| 514 |
if not week_daily_ids:
|
| 515 |
-
st.info("No daily leaderboards
|
| 516 |
else:
|
| 517 |
for date_id in week_daily_ids:
|
| 518 |
date_title = _get_pst_range_str(date_id)
|
| 519 |
settings_list = list_settings_for_period("daily", date_id)
|
| 520 |
if not settings_list:
|
| 521 |
with st.expander(date_title, expanded=False):
|
| 522 |
-
st.info("No leaderboards
|
| 523 |
continue
|
| 524 |
|
| 525 |
for settings_info in settings_list:
|
|
@@ -541,7 +541,7 @@ def _render_history_tab():
|
|
| 541 |
week_title = _format_week_title(selected_week)
|
| 542 |
settings_list = list_settings_for_period("weekly", selected_week)
|
| 543 |
if not settings_list:
|
| 544 |
-
st.info("No leaderboards
|
| 545 |
else:
|
| 546 |
for idx, settings_info in enumerate(settings_list):
|
| 547 |
file_id = settings_info["file_id"]
|
|
@@ -558,13 +558,12 @@ def _render_history_tab():
|
|
| 558 |
|
| 559 |
|
| 560 |
def _render_today_tab():
|
| 561 |
-
"""Render today's leaderboards tab - shows current daily
|
| 562 |
|
| 563 |
If query string parameters are present:
|
| 564 |
- gidd: Show only the specified daily leaderboard (file_id)
|
| 565 |
-
- gidw: Show only the specified weekly leaderboard (file_id)
|
| 566 |
|
| 567 |
-
Otherwise, show all current daily
|
| 568 |
"""
|
| 569 |
# Get query parameters
|
| 570 |
try:
|
|
@@ -573,105 +572,52 @@ def _render_today_tab():
|
|
| 573 |
params = {}
|
| 574 |
|
| 575 |
gidd = params.get("gidd", None)
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
# If both query params are present, show filtered view
|
| 579 |
-
if gidd or gidw:
|
| 580 |
st.header("π― Today's Leaderboards (Filtered)")
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
else:
|
| 600 |
-
st.warning(f"Daily leaderboard not found: {gidd}")
|
| 601 |
-
|
| 602 |
-
# Show weekly leaderboard if gidw is specified
|
| 603 |
-
with col2:
|
| 604 |
-
if gidw:
|
| 605 |
-
st.subheader("π Weekly")
|
| 606 |
-
weekly_id = get_current_weekly_id()
|
| 607 |
-
lb = load_leaderboard("weekly", weekly_id, gidw)
|
| 608 |
-
|
| 609 |
-
if lb:
|
| 610 |
-
try:
|
| 611 |
-
year, week = weekly_id.split("-W")
|
| 612 |
-
week_title = f"Week {int(week)}, {year}"
|
| 613 |
-
except ValueError:
|
| 614 |
-
week_title = weekly_id
|
| 615 |
-
|
| 616 |
-
header_suffix = _settings_badge(lb)
|
| 617 |
-
with st.expander(f"{week_title} {header_suffix}", expanded=True):
|
| 618 |
-
st.caption(f"Settings: mode={lb.game_mode}, source={lb.wordlist_source}, show_incorrect={lb.show_incorrect_guesses}, free_letters={lb.enable_free_letters}, spacer={lb.puzzle_options.get('spacer', 0)}, overlap={lb.puzzle_options.get('may_overlap', False)}")
|
| 619 |
-
_render_leaderboard_table(lb, "", grid_key=f"today-weekly-{weekly_id}-{gidw}")
|
| 620 |
-
else:
|
| 621 |
-
st.warning(f"Weekly leaderboard not found: {gidw}")
|
| 622 |
else:
|
| 623 |
-
# Show all current leaderboards (default view) in two columns
|
| 624 |
st.header("π Today's Leaderboards")
|
| 625 |
-
st.write("Current daily
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
if lb is not None:
|
| 645 |
-
header_suffix = _settings_badge(lb)
|
| 646 |
-
expander_title = f"{date_title} {header_suffix}" if header_suffix else date_title
|
| 647 |
-
with st.expander(expander_title, expanded=True):
|
| 648 |
-
if lb is not None:
|
| 649 |
-
st.caption(f"Settings: mode={lb.game_mode}, source={lb.wordlist_source}, show_incorrect={lb.show_incorrect_guesses}, free_letters={lb.enable_free_letters}, spacer={lb.puzzle_options.get('spacer', 0)}, overlap={lb.puzzle_options.get('may_overlap', False)}")
|
| 650 |
-
_render_leaderboard_table(lb, "", grid_key=f"today-daily-{daily_id}-{file_id}")
|
| 651 |
-
|
| 652 |
-
# Right column: Current Weekly Leaderboards
|
| 653 |
-
with col2:
|
| 654 |
-
st.subheader("π Weekly")
|
| 655 |
-
weekly_id = get_current_weekly_id()
|
| 656 |
-
|
| 657 |
-
try:
|
| 658 |
-
year, week = weekly_id.split("-W")
|
| 659 |
-
week_title = f"Week {int(week)}, {year}"
|
| 660 |
-
except ValueError:
|
| 661 |
-
week_title = weekly_id
|
| 662 |
-
|
| 663 |
-
settings_list = list_settings_for_period("weekly", weekly_id)
|
| 664 |
-
if not settings_list:
|
| 665 |
-
st.info("No weekly leaderboards found for this week.")
|
| 666 |
-
else:
|
| 667 |
-
for settings_info in settings_list:
|
| 668 |
-
file_id = settings_info["file_id"]
|
| 669 |
-
lb = load_leaderboard("weekly", weekly_id, file_id)
|
| 670 |
-
header_suffix = ""
|
| 671 |
if lb is not None:
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
|
|
|
| 106 |
except Exception:
|
| 107 |
return str(utc_ts)
|
| 108 |
|
| 109 |
+
def _format_time(seconds: float) -> str:
|
| 110 |
"""Format seconds as MM:SS."""
|
| 111 |
mins, secs = divmod(seconds, 60)
|
| 112 |
+
return f"{mins:02.0f}:{secs:06.3f}"
|
| 113 |
|
| 114 |
|
| 115 |
def _get_rank_emoji(rank: int) -> str:
|
|
|
|
| 322 |
f"""
|
| 323 |
<div class="bw-tab-nav">
|
| 324 |
<a href="?page=today" target="_self" class="{'active' if active_tab == 'today' else ''}">π Today</a>
|
| 325 |
+
<!-- <a href="?page=daily" target="_self" class="{'active' if active_tab == 'daily' else ''}">π
Daily</a> -->
|
| 326 |
+
<a href="?page=weekly" target="_self" class="{'active' if active_tab == 'weekly' else ''}">π This Week</a>
|
| 327 |
<a href="?page=history" target="_self" class="{'active' if active_tab == 'history' else ''}">π History</a>
|
| 328 |
</div>
|
| 329 |
""",
|
|
|
|
| 352 |
# Show last 7 days; for each day, show all leaderboards (settings combos)
|
| 353 |
daily_periods = list_available_periods("daily", limit=7)
|
| 354 |
if not daily_periods:
|
| 355 |
+
st.info("No daily leaderboards avaialble.")
|
| 356 |
return
|
| 357 |
|
| 358 |
for date_id in daily_periods:
|
|
|
|
| 384 |
|
| 385 |
def _render_weekly_tab():
|
| 386 |
"""Render weekly leaderboard tab."""
|
| 387 |
+
st.header("π This Week")
|
| 388 |
st.write(f"Top {MAX_DISPLAY_ENTRIES} scores for each ISO week. Resets Monday at UTC midnight.")
|
| 389 |
|
| 390 |
try:
|
|
|
|
| 395 |
requested_week = params.get("week")
|
| 396 |
current_week = get_current_weekly_id()
|
| 397 |
|
| 398 |
+
weekly_periods = list_available_periods("weekly", limit=1)
|
| 399 |
if not weekly_periods:
|
| 400 |
+
st.info("No weekly leaderboards available.")
|
| 401 |
return
|
| 402 |
|
| 403 |
if requested_week in weekly_periods:
|
|
|
|
| 418 |
expander_title = f"ποΈ {week_title}"
|
| 419 |
with st.expander(expander_title, expanded=(week_id == expanded_week)):
|
| 420 |
if not settings_list:
|
| 421 |
+
st.info("No leaderboards avaialble for this week.")
|
| 422 |
continue
|
| 423 |
|
| 424 |
for idx, settings_info in enumerate(settings_list):
|
|
|
|
| 442 |
|
| 443 |
weekly_periods = list_available_periods("weekly", limit=12)
|
| 444 |
if not weekly_periods:
|
| 445 |
+
st.info("No weekly leaderboards available.")
|
| 446 |
return
|
| 447 |
|
| 448 |
week_options = [(_format_week_title(week_id), week_id) for week_id in weekly_periods]
|
|
|
|
| 512 |
with col1:
|
| 513 |
st.subheader("Daily History")
|
| 514 |
if not week_daily_ids:
|
| 515 |
+
st.info("No daily leaderboards available for this week.")
|
| 516 |
else:
|
| 517 |
for date_id in week_daily_ids:
|
| 518 |
date_title = _get_pst_range_str(date_id)
|
| 519 |
settings_list = list_settings_for_period("daily", date_id)
|
| 520 |
if not settings_list:
|
| 521 |
with st.expander(date_title, expanded=False):
|
| 522 |
+
st.info("No leaderboards available for this date.")
|
| 523 |
continue
|
| 524 |
|
| 525 |
for settings_info in settings_list:
|
|
|
|
| 541 |
week_title = _format_week_title(selected_week)
|
| 542 |
settings_list = list_settings_for_period("weekly", selected_week)
|
| 543 |
if not settings_list:
|
| 544 |
+
st.info("No leaderboards available for this week.")
|
| 545 |
else:
|
| 546 |
for idx, settings_info in enumerate(settings_list):
|
| 547 |
file_id = settings_info["file_id"]
|
|
|
|
| 558 |
|
| 559 |
|
| 560 |
def _render_today_tab():
|
| 561 |
+
"""Render today's leaderboards tab - shows current daily leaderboard.
|
| 562 |
|
| 563 |
If query string parameters are present:
|
| 564 |
- gidd: Show only the specified daily leaderboard (file_id)
|
|
|
|
| 565 |
|
| 566 |
+
Otherwise, show all current daily leaderboards.
|
| 567 |
"""
|
| 568 |
# Get query parameters
|
| 569 |
try:
|
|
|
|
| 572 |
params = {}
|
| 573 |
|
| 574 |
gidd = params.get("gidd", None)
|
| 575 |
+
|
| 576 |
+
if gidd:
|
|
|
|
|
|
|
| 577 |
st.header("π― Today's Leaderboards (Filtered)")
|
| 578 |
+
|
| 579 |
+
st.subheader("π
Daily")
|
| 580 |
+
daily_id = get_current_daily_id()
|
| 581 |
+
lb = load_leaderboard("daily", daily_id, gidd)
|
| 582 |
+
|
| 583 |
+
if lb:
|
| 584 |
+
date_title = "π Today: " + _get_pst_range_str(daily_id)
|
| 585 |
+
|
| 586 |
+
header_suffix = _settings_badge(lb)
|
| 587 |
+
with st.expander(f"{date_title} {header_suffix}", expanded=True):
|
| 588 |
+
st.caption(
|
| 589 |
+
f"Settings: mode={lb.game_mode}, source={lb.wordlist_source}, "
|
| 590 |
+
f"show_incorrect={lb.show_incorrect_guesses}, free_letters={lb.enable_free_letters}, "
|
| 591 |
+
f"spacer={lb.puzzle_options.get('spacer', 0)}, overlap={lb.puzzle_options.get('may_overlap', False)}"
|
| 592 |
+
)
|
| 593 |
+
_render_leaderboard_table(lb, "", grid_key=f"today-daily-{daily_id}-{gidd}")
|
| 594 |
+
else:
|
| 595 |
+
st.warning(f"Daily leaderboard not available: {gidd}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 596 |
else:
|
|
|
|
| 597 |
st.header("π Today's Leaderboards")
|
| 598 |
+
st.write("Current daily leaderboards. Resets at UTC midnight.")
|
| 599 |
+
|
| 600 |
+
st.subheader("π
Daily")
|
| 601 |
+
daily_id = get_current_daily_id()
|
| 602 |
+
|
| 603 |
+
date_title = "Today " + _get_pst_range_str(daily_id)
|
| 604 |
+
|
| 605 |
+
settings_list = list_settings_for_period("daily", daily_id)
|
| 606 |
+
if not settings_list:
|
| 607 |
+
st.info("No daily leaderboards available for today.")
|
| 608 |
+
else:
|
| 609 |
+
for settings_info in settings_list:
|
| 610 |
+
file_id = settings_info["file_id"]
|
| 611 |
+
lb = load_leaderboard("daily", daily_id, file_id)
|
| 612 |
+
header_suffix = ""
|
| 613 |
+
if lb is not None:
|
| 614 |
+
header_suffix = _settings_badge(lb)
|
| 615 |
+
expander_title = f"{date_title} {header_suffix}" if header_suffix else date_title
|
| 616 |
+
with st.expander(expander_title, expanded=True):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
if lb is not None:
|
| 618 |
+
st.caption(
|
| 619 |
+
f"Settings: mode={lb.game_mode}, source={lb.wordlist_source}, "
|
| 620 |
+
f"show_incorrect={lb.show_incorrect_guesses}, free_letters={lb.enable_free_letters}, "
|
| 621 |
+
f"spacer={lb.puzzle_options.get('spacer', 0)}, overlap={lb.puzzle_options.get('may_overlap', False)}"
|
| 622 |
+
)
|
| 623 |
+
_render_leaderboard_table(lb, "", grid_key=f"today-daily-{daily_id}-{file_id}")
|
wrdler/ui.py
CHANGED
|
@@ -348,7 +348,7 @@ def _render_header():
|
|
| 348 |
if st.session_state.get("enable_free_letters", False):
|
| 349 |
st.subheader("Choose 2 free letters, then reveal cells and guess words on each line!")
|
| 350 |
else:
|
| 351 |
-
st.subheader("Reveal letters of words on the six lines. Guess the words
|
| 352 |
|
| 353 |
# Only show Challenge Mode expander if in challenge mode and game_id is present
|
| 354 |
params = st.query_params if hasattr(st, "query_params") else {}
|
|
@@ -403,13 +403,13 @@ def _render_header():
|
|
| 403 |
best_score = best_user["score"]
|
| 404 |
best_time = best_user["time"]
|
| 405 |
mins, secs = divmod(best_time, 60)
|
| 406 |
-
best_time_str = f"{mins:
|
| 407 |
|
| 408 |
# Build leaderboard HTML
|
| 409 |
leaderboard_rows = []
|
| 410 |
for i, user in enumerate(sorted_users[:5], 1): # Top 5
|
| 411 |
u_mins, uSecs = divmod(user["time"], 60)
|
| 412 |
-
u_time_str = f"{
|
| 413 |
medal = ["π₯", "π₯", "π₯"][i-1] if i <= 3 else f"{i}."
|
| 414 |
# show optional difficulty if present
|
| 415 |
diff_str = ""
|
|
@@ -986,7 +986,7 @@ def _render_score_panel(state: GameState):
|
|
| 986 |
end = state.end_time or (now if is_game_over(state) else None)
|
| 987 |
elapsed = (end or now) - start
|
| 988 |
mins, secs = divmod(int(elapsed.total_seconds()), 60)
|
| 989 |
-
timer_str = f"{mins:
|
| 990 |
start_ms = int(start.timestamp() * 1000)
|
| 991 |
end_ms = int(end.timestamp() * 1000) if end else "null"
|
| 992 |
|
|
@@ -1114,9 +1114,9 @@ def _game_over_content(state: GameState) -> None:
|
|
| 1114 |
start = state.start_time or state.end_time or datetime.now()
|
| 1115 |
end = state.end_time or datetime.now()
|
| 1116 |
elapsed = end - start
|
| 1117 |
-
elapsed_seconds =
|
| 1118 |
-
mins, secs = divmod(
|
| 1119 |
-
timer_str = f"{mins:
|
| 1120 |
|
| 1121 |
# Compute optional word list difficulty for current run
|
| 1122 |
difficulty_value = None
|
|
@@ -1306,7 +1306,7 @@ def _game_over_content(state: GameState) -> None:
|
|
| 1306 |
btn_flag = st.session_state.get("gameover_button_pressed", None)
|
| 1307 |
|
| 1308 |
# Helper function to submit to leaderboards
|
| 1309 |
-
def _submit_to_leaderboards(username: str, score: int, time_secs:
|
| 1310 |
"""Submit score to daily and weekly leaderboards (with fallback verification).
|
| 1311 |
Uses main `submit_score_to_all_leaderboards` and ensures weekly entry is attempted
|
| 1312 |
even if the main call fails to produce a weekly result.
|
|
|
|
| 348 |
if st.session_state.get("enable_free_letters", False):
|
| 349 |
st.subheader("Choose 2 free letters, then reveal cells and guess words on each line!")
|
| 350 |
else:
|
| 351 |
+
st.subheader("Reveal letters of words on the six lines. Guess the words for extra points and a better score!")
|
| 352 |
|
| 353 |
# Only show Challenge Mode expander if in challenge mode and game_id is present
|
| 354 |
params = st.query_params if hasattr(st, "query_params") else {}
|
|
|
|
| 403 |
best_score = best_user["score"]
|
| 404 |
best_time = best_user["time"]
|
| 405 |
mins, secs = divmod(best_time, 60)
|
| 406 |
+
best_time_str = f"{mins:02.0f}:{secs:06.3f}"
|
| 407 |
|
| 408 |
# Build leaderboard HTML
|
| 409 |
leaderboard_rows = []
|
| 410 |
for i, user in enumerate(sorted_users[:5], 1): # Top 5
|
| 411 |
u_mins, uSecs = divmod(user["time"], 60)
|
| 412 |
+
u_time_str = f"{mins:02.0f}:{secs:06.3f}"
|
| 413 |
medal = ["π₯", "π₯", "π₯"][i-1] if i <= 3 else f"{i}."
|
| 414 |
# show optional difficulty if present
|
| 415 |
diff_str = ""
|
|
|
|
| 986 |
end = state.end_time or (now if is_game_over(state) else None)
|
| 987 |
elapsed = (end or now) - start
|
| 988 |
mins, secs = divmod(int(elapsed.total_seconds()), 60)
|
| 989 |
+
timer_str = f"{mins:02.0f}:{secs:06.3f}"
|
| 990 |
start_ms = int(start.timestamp() * 1000)
|
| 991 |
end_ms = int(end.timestamp() * 1000) if end else "null"
|
| 992 |
|
|
|
|
| 1114 |
start = state.start_time or state.end_time or datetime.now()
|
| 1115 |
end = state.end_time or datetime.now()
|
| 1116 |
elapsed = end - start
|
| 1117 |
+
elapsed_seconds = float(elapsed.total_seconds())
|
| 1118 |
+
mins, secs = divmod(elapsed.total_seconds(), 60)
|
| 1119 |
+
timer_str = f"{mins:02.0f}:{secs:06.3f}"
|
| 1120 |
|
| 1121 |
# Compute optional word list difficulty for current run
|
| 1122 |
difficulty_value = None
|
|
|
|
| 1306 |
btn_flag = st.session_state.get("gameover_button_pressed", None)
|
| 1307 |
|
| 1308 |
# Helper function to submit to leaderboards
|
| 1309 |
+
def _submit_to_leaderboards(username: str, score: int, time_secs: float, word_list: list, challenge_id: str = None):
|
| 1310 |
"""Submit score to daily and weekly leaderboards (with fallback verification).
|
| 1311 |
Uses main `submit_score_to_all_leaderboards` and ensures weekly entry is attempted
|
| 1312 |
even if the main call fails to produce a weekly result.
|