v0.2.3 Update Leaderboard titles to daterange
Browse files- CLAUDE.md +8 -1
- README.md +3 -164
- specs/leaderboard_spec.md +3 -3
- specs/requirements.md +6 -1
- specs/specs.md +11 -0
- wrdler/__init__.py +1 -1
- wrdler/leaderboard_page.py +48 -22
- wrdler/ui.py +5 -7
CLAUDE.md
CHANGED
|
@@ -41,6 +41,13 @@ Wrdler is a simplified vocabulary puzzle game based on BattleWords:
|
|
| 41 |
- Remote storage via HuggingFace datasets
|
| 42 |
- Word list difficulty calculation
|
| 43 |
- "Show Challenge Share Links" toggle (default OFF)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
### Daily & Weekly Leaderboards
|
| 46 |
- **Settings-Based Separation:** Each unique settings combo creates separate leaderboard
|
|
@@ -55,7 +62,7 @@ Wrdler is a simplified vocabulary puzzle game based on BattleWords:
|
|
| 55 |
2025-12-08 00:00:00 UTC to 2025-12-08 23:59:59 UTC
|
| 56 |
and
|
| 57 |
2025-12-07 16:00:00 PST to 2025-12-08 15:59:59 PST
|
| 58 |
-
The leaderboard expander label should show: `
|
| 59 |
|
| 60 |
### AI Word Generation
|
| 61 |
- Topic-based word list generation via HuggingFace Spaces or local transformers
|
|
|
|
| 41 |
- Remote storage via HuggingFace datasets
|
| 42 |
- Word list difficulty calculation
|
| 43 |
- "Show Challenge Share Links" toggle (default OFF)
|
| 44 |
+
- **Integration:**
|
| 45 |
+
- Automatic submission after game completion (opt-in via game over popup)
|
| 46 |
+
- Challenge scores also contribute to daily/weekly leaderboards
|
| 47 |
+
- Source tracking via `source_challenge_id` field
|
| 48 |
+
- Unified JSON format with `entry_type` field (daily/weekly/challenge)
|
| 49 |
+
|
| 50 |
+
**Access:** 'Leaderboard' link in the footer navigation at the bottom of the page
|
| 51 |
|
| 52 |
### Daily & Weekly Leaderboards
|
| 53 |
- **Settings-Based Separation:** Each unique settings combo creates separate leaderboard
|
|
|
|
| 62 |
2025-12-08 00:00:00 UTC to 2025-12-08 23:59:59 UTC
|
| 63 |
and
|
| 64 |
2025-12-07 16:00:00 PST to 2025-12-08 15:59:59 PST
|
| 65 |
+
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]`
|
| 66 |
|
| 67 |
### AI Word Generation
|
| 68 |
- Topic-based word list generation via HuggingFace Spaces or local transformers
|
README.md
CHANGED
|
@@ -98,7 +98,7 @@ Wrdler is a vocabulary learning game with a simplified grid and strategic letter
|
|
| 98 |
- Leaderboard files store UTC dates/times for each period.
|
| 99 |
- When viewing daily leaderboards, the app displays the UTC period as a PST date range:
|
| 100 |
- 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`.
|
| 101 |
-
- The leaderboard expander label should show: `
|
| 102 |
- Daily leaderboards display the date range in PST for easier local time understanding.
|
| 103 |
- Weekly leaderboards use ISO week dates (e.g., `2025-W49`) and are available every Monday.
|
| 104 |
|
|
@@ -115,7 +115,7 @@ Wrdler is a vocabulary learning game with a simplified grid and strategic letter
|
|
| 115 |
- Source tracking via `source_challenge_id` field
|
| 116 |
- Unified JSON format with `entry_type` field (daily/weekly/challenge)
|
| 117 |
|
| 118 |
-
**Access:**
|
| 119 |
|
| 120 |
### Deployment & Technical
|
| 121 |
- **Dockerfile-based deployment** supported for Hugging Face Spaces and other container platforms
|
|
@@ -237,7 +237,7 @@ All test files must be placed in the `/tests` folder. This ensures a clean proje
|
|
| 237 |
4. Earn points for correct guesses and bonus points for unrevealed letters.
|
| 238 |
5. **The game ends when all six words are found or all word letters are revealed. Your score tier is displayed.**
|
| 239 |
6. **To play a shared challenge, use a link with `?game_id=<sid>`. Your result will be added to the challenge leaderboard.**
|
| 240 |
-
7. **Access leaderboards anytime using the
|
| 241 |
|
| 242 |
## Changelog
|
| 243 |
|
|
@@ -425,165 +425,4 @@ Ensure you have the necessary permissions and API access (if required) to use th
|
|
| 425 |
|
| 426 |
For any issues or enhancements, please refer to the project documentation or contact the project maintainer.
|
| 427 |
|
| 428 |
-
Happy gaming and sound designing!
|
| 429 |
-
|
| 430 |
-
## What's New in v0.2.1: Daily & Weekly Leaderboards ๐
|
| 431 |
-
|
| 432 |
-
### Comprehensive Leaderboard System
|
| 433 |
-
- **Settings-Based Competition:** Separate leaderboards for each unique settings combination
|
| 434 |
-
- Game mode (classic, easy, too easy)
|
| 435 |
-
- Wordlist source (classic.txt, fourth_grade.txt, etc.)
|
| 436 |
-
- Display options (show incorrect guesses, enable free letters)
|
| 437 |
-
- Puzzle options (spacer, may_overlap)
|
| 438 |
-
- **Daily & Weekly Periods:** Top 20 scores per day and per ISO week
|
| 439 |
-
- **Automatic Qualification:** Submit scores after any game completion
|
| 440 |
-
- **Settings page planned:** Move from sidebar to dedicated page, login required
|
| 441 |
-
|
| 442 |
-
### Leaderboard Page Navigation
|
| 443 |
-
- **Today Tab:** Current daily and weekly leaderboards with direct link filtering
|
| 444 |
-
- **Daily Tab:** Last 7 days of history with expandable date groups
|
| 445 |
-
- **Weekly Tab:** Current week with all settings combinations
|
| 446 |
-
- **History Tab:** Browse any past period with dropdown selectors
|
| 447 |
-
|
| 448 |
-
### Storage & Discovery
|
| 449 |
-
- **Folder-Based Architecture:** No index.json, scans period folders directly
|
| 450 |
-
- **File ID Format:** `{wordlist_source}-{game_mode}-{sequence}` for fast filtering
|
| 451 |
-
- **HuggingFace Storage:** `games/leaderboards/{daily|weekly}/{period}/{file_id}/settings.json`
|
| 452 |
-
- **Unified Format:** Single JSON structure with `entry_type` field (daily/weekly/challenge)
|
| 453 |
-
|
| 454 |
-
### Integration Features
|
| 455 |
-
- Challenge scores automatically contribute to daily/weekly leaderboards
|
| 456 |
-
- Source tracking via `source_challenge_id` field
|
| 457 |
-
- Query parameters for direct leaderboard links
|
| 458 |
-
- Settings badges display full game configuration
|
| 459 |
-
|
| 460 |
-
### Challenge Mode ๐ฏ (Existing Features)
|
| 461 |
-
- Share challenges with friends via short URLs (`?game_id=<sid>`)
|
| 462 |
-
- Multi-user challenge leaderboards sorted by score and time
|
| 463 |
-
- Top 5 players displayed in Challenge Mode banner
|
| 464 |
-
- "Show Challenge Share Links" toggle for privacy control (default OFF)
|
| 465 |
-
|
| 466 |
-
## Known Issues / TODO
|
| 467 |
-
|
| 468 |
-
- Word list loading bug: the app may not select the proper word lists in some environments. Investigate `word_loader.get_wordlist_files()` / `load_word_list()` and sidebar selection persistence to ensure the chosen file is correctly used by the generator.
|
| 469 |
-
|
| 470 |
-
## Development Phases
|
| 471 |
-
|
| 472 |
-
- **Proof of Concept (0.1.0):** No overlaps, basic UI, single session.
|
| 473 |
-
- **Beta (0.5.0):** Responsive layout, leaderboards, keyboard support, deterministic seed.
|
| 474 |
-
- **Full (1.0.0):** Enhanced UX, Overlaps allowed on shared letters, persistence, daily/practice modes, advanced features.
|
| 475 |
-
|
| 476 |
-
See `specs/requirements.md` and `specs/specs.md` for full details and roadmap.
|
| 477 |
-
|
| 478 |
-
## License
|
| 479 |
-
|
| 480 |
-
Wrdler is based on BattleWords. BattlewordsTM. All Rights Reserved. All content, trademarks and logos are copyrighted by the owner.
|
| 481 |
-
|
| 482 |
-
## Hugging Face Spaces Configuration
|
| 483 |
-
|
| 484 |
-
Wrdler is deployable as a Hugging Face Space. You can use either the YAML config block or a Dockerfile for advanced/custom deployments.
|
| 485 |
-
|
| 486 |
-
To configure your Space with the YAML block, add it at the top of your `README.md`:
|
| 487 |
-
|
| 488 |
-
```yaml
|
| 489 |
-
---
|
| 490 |
-
title: Wrdler
|
| 491 |
-
emoji: ๐ฒ
|
| 492 |
-
colorFrom: blue
|
| 493 |
-
colorTo: indigo
|
| 494 |
-
sdk: streamlit
|
| 495 |
-
sdk_version: 1.51.0
|
| 496 |
-
python_version: 3.12.8
|
| 497 |
-
app_file: app.py
|
| 498 |
-
tags:
|
| 499 |
-
- game
|
| 500 |
-
- vocabulary
|
| 501 |
-
- streamlit
|
| 502 |
-
- education
|
| 503 |
-
---
|
| 504 |
-
```
|
| 505 |
-
|
| 506 |
-
**Key parameters:**
|
| 507 |
-
- `title`, `emoji`, `colorFrom`, `colorTo`: Visuals for your Space.
|
| 508 |
-
- `sdk`: Use `streamlit` for Streamlit apps.
|
| 509 |
-
- `sdk_version`: Latest supported Streamlit version.
|
| 510 |
-
- `python_version`: Python version (default is3.10).
|
| 511 |
-
- `app_file`: Entry point for your app.
|
| 512 |
-
- `tags`: List of descriptive tags.
|
| 513 |
-
|
| 514 |
-
**Dependencies:**
|
| 515 |
-
Add a `requirements.txt` with your Python dependencies (e.g., `streamlit`, etc.).
|
| 516 |
-
|
| 517 |
-
**Port:**
|
| 518 |
-
Streamlit Spaces use port `8501` by default.
|
| 519 |
-
|
| 520 |
-
**Embedding:**
|
| 521 |
-
Spaces can be embedded in other sites using an `<iframe>`:
|
| 522 |
-
|
| 523 |
-
```html
|
| 524 |
-
<iframe src="https://[YourUsername]-Wrdler.hf.space?embed=true" title="Wrdler"></iframe>
|
| 525 |
-
```
|
| 526 |
-
|
| 527 |
-
For full configuration options, see [Spaces Config Reference](https://huggingface.co/docs/hub/spaces-config-reference) and [Streamlit SDK Guide](https://huggingface.co/docs/hub/spaces-sdks-streamlit).
|
| 528 |
-
|
| 529 |
-
# Assets Setup
|
| 530 |
-
|
| 531 |
-
To fully experience Wrdler, especially the audio elements, ensure you set up the following assets:
|
| 532 |
-
|
| 533 |
-
- Place your background music `.mp3` files in `wrdler/assets/audio/music/` to enable music.
|
| 534 |
-
- Place your sound effect files (`.mp3` or `.wav`) in `wrdler/assets/audio/effects/` for sound effects.
|
| 535 |
-
|
| 536 |
-
Refer to the documentation for guidance on compatible audio formats and common troubleshooting tips.
|
| 537 |
-
|
| 538 |
-
# Sound Asset Generation
|
| 539 |
-
|
| 540 |
-
To generate and save custom sound effects for Wrdler, you can use the `generate_sound_effect` function.
|
| 541 |
-
|
| 542 |
-
## Function: `generate_sound_effect`
|
| 543 |
-
|
| 544 |
-
```python
|
| 545 |
-
def generate_sound_effect(effect: str, save_to_assets: bool = False, use_api: str = "huggingface") -> str:
|
| 546 |
-
"""
|
| 547 |
-
Generate a sound effect and save it as a file.
|
| 548 |
-
|
| 549 |
-
Parameters:
|
| 550 |
-
- `effect`: Name of the effect to generate.
|
| 551 |
-
- `save_to_assets`: If `True`, saves the effect to the assets directory;
|
| 552 |
-
if `False`, saves to a temporary location. Default is `False`.
|
| 553 |
-
- `use_api`: API to use for generation. Options are "huggingface" or "replicate". Default is "huggingface".
|
| 554 |
-
|
| 555 |
-
Returns:
|
| 556 |
-
- File path to the saved sound effect.
|
| 557 |
-
```
|
| 558 |
-
|
| 559 |
-
## Parameters
|
| 560 |
-
|
| 561 |
-
- `effect`: The name of the sound effect you want to generate (e.g., "explosion", "powerup").
|
| 562 |
-
- `save_to_assets` (optional): Set to `True` to save the generated sound effect to the game's assets directory. If `False`, the effect is saved to a temporary location. Default is `False`.
|
| 563 |
-
- `use_api` (optional): The API to use for generating the sound. Options are `"huggingface"` or `"replicate"`. Default is `"huggingface"`.
|
| 564 |
-
|
| 565 |
-
## Returns
|
| 566 |
-
|
| 567 |
-
- The function returns the file path to the saved sound effect, whether it's in the assets directory or a temporary location.
|
| 568 |
-
|
| 569 |
-
## Usage Example
|
| 570 |
-
|
| 571 |
-
To generate a sound effect and save it to the assets directory:
|
| 572 |
-
|
| 573 |
-
```python
|
| 574 |
-
generate_sound_effect("your_effect_name", save_to_assets=True)
|
| 575 |
-
```
|
| 576 |
-
|
| 577 |
-
To generate a sound effect and keep it in a temporary location:
|
| 578 |
-
|
| 579 |
-
```python
|
| 580 |
-
temp_path = generate_sound_effect("your_effect_name", save_to_assets=False)
|
| 581 |
-
```
|
| 582 |
-
|
| 583 |
-
## Note
|
| 584 |
-
|
| 585 |
-
Ensure you have the necessary permissions and API access (if required) to use the sound generation service. Generated sounds are subject to the terms of use of the respective API.
|
| 586 |
-
|
| 587 |
-
For any issues or enhancements, please refer to the project documentation or contact the project maintainer.
|
| 588 |
-
|
| 589 |
Happy gaming and sound designing!
|
|
|
|
| 98 |
- Leaderboard files store UTC dates/times for each period.
|
| 99 |
- When viewing daily leaderboards, the app displays the UTC period as a PST date range:
|
| 100 |
- 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`.
|
| 101 |
+
- 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]`
|
| 102 |
- Daily leaderboards display the date range in PST for easier local time understanding.
|
| 103 |
- Weekly leaderboards use ISO week dates (e.g., `2025-W49`) and are available every Monday.
|
| 104 |
|
|
|
|
| 115 |
- Source tracking via `source_challenge_id` field
|
| 116 |
- Unified JSON format with `entry_type` field (daily/weekly/challenge)
|
| 117 |
|
| 118 |
+
**Access:** 'Leaderboard' link in the footer navigation at the bottom of the page
|
| 119 |
|
| 120 |
### Deployment & Technical
|
| 121 |
- **Dockerfile-based deployment** supported for Hugging Face Spaces and other container platforms
|
|
|
|
| 237 |
4. Earn points for correct guesses and bonus points for unrevealed letters.
|
| 238 |
5. **The game ends when all six words are found or all word letters are revealed. Your score tier is displayed.**
|
| 239 |
6. **To play a shared challenge, use a link with `?game_id=<sid>`. Your result will be added to the challenge leaderboard.**
|
| 240 |
+
7. **Access leaderboards anytime using the 'Leaderboard' link in the footer navigation.**
|
| 241 |
|
| 242 |
## Changelog
|
| 243 |
|
|
|
|
| 425 |
|
| 426 |
For any issues or enhancements, please refer to the project documentation or contact the project maintainer.
|
| 427 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 428 |
Happy gaming and sound designing!
|
specs/leaderboard_spec.md
CHANGED
|
@@ -185,7 +185,7 @@ Instead of maintaining an `index.json` file, leaderboards are discovered by:
|
|
| 185 |
โผ โผ
|
| 186 |
โโโโโโโโโ โโโโโโโโโโโโโ
|
| 187 |
โ Daily โ โ Weekly โ
|
| 188 |
-
โ LB โ โ LB
|
| 189 |
โโโโโโโโโ โโโโโโโโโโโโโ
|
| 190 |
โ โ
|
| 191 |
โโโโโโฌโโโโโโ
|
|
@@ -745,7 +745,7 @@ HF_REPO_ID/games/
|
|
| 745 |
|
| 746 |
#### Key Differences from Spec
|
| 747 |
|
| 748 |
-
1. **Navigation:** Implemented as footer menu
|
| 749 |
2. **Caching:** Not implemented in v0.2.0 (deferred to v0.3.0 for optimization)
|
| 750 |
3. **Tab Implementation:** Used query parameters with custom nav links instead of Streamlit native tabs for better URL support
|
| 751 |
4. **Table Rendering:** Used pandas DataFrames with styling instead of custom HTML tables
|
|
@@ -883,4 +883,4 @@ HF_REPO_ID/games/
|
|
| 883 |
"YYYY-MM-DD HH:MM:SS PST to YYYY-MM-DD HH:MM:SS PST"
|
| 884 |
(PST is UTC-8; adjust for daylight saving as needed)
|
| 885 |
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`.
|
| 886 |
-
The leaderboard expander label should show: `
|
|
|
|
| 185 |
โผ โผ
|
| 186 |
โโโโโโโโโ โโโโโโโโโโโโโ
|
| 187 |
โ Daily โ โ Weekly โ
|
| 188 |
+
โ LB โ โ LB |
|
| 189 |
โโโโโโโโโ โโโโโโโโโโโโโ
|
| 190 |
โ โ
|
| 191 |
โโโโโโฌโโโโโโ
|
|
|
|
| 745 |
|
| 746 |
#### Key Differences from Spec
|
| 747 |
|
| 748 |
+
1. **Navigation:** Implemented as 'Leaderboard' link in the footer menu instead of sidebar button
|
| 749 |
2. **Caching:** Not implemented in v0.2.0 (deferred to v0.3.0 for optimization)
|
| 750 |
3. **Tab Implementation:** Used query parameters with custom nav links instead of Streamlit native tabs for better URL support
|
| 751 |
4. **Table Rendering:** Used pandas DataFrames with styling instead of custom HTML tables
|
|
|
|
| 883 |
"YYYY-MM-DD HH:MM:SS PST to YYYY-MM-DD HH:MM:SS PST"
|
| 884 |
(PST is UTC-8; adjust for daylight saving as needed)
|
| 885 |
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`.
|
| 886 |
+
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
|
@@ -197,7 +197,9 @@ This document breaks down the implementation tasks for Wrdler using the game rul
|
|
| 197 |
2025-12-08 00:00:00 UTC to 2025-12-08 23:59:59 UTC
|
| 198 |
and
|
| 199 |
2025-12-07 16:00:00 PST to 2025-12-08 15:59:59 PST
|
| 200 |
-
The leaderboard expander label should show: `
|
|
|
|
|
|
|
| 201 |
|
| 202 |
### Core Implementation
|
| 203 |
|
|
@@ -289,3 +291,6 @@ games/
|
|
| 289 |
|
| 290 |
## Test File Location
|
| 291 |
All test files must be placed in the `/tests` folder. This ensures a clean project structure and makes it easy to discover and run all tests.
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
2025-12-08 00:00:00 UTC to 2025-12-08 23:59:59 UTC
|
| 198 |
and
|
| 199 |
2025-12-07 16:00:00 PST to 2025-12-08 15:59:59 PST
|
| 200 |
+
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]`
|
| 201 |
+
|
| 202 |
+
**Access:** 'Leaderboard' link in the footer navigation at the bottom of the page
|
| 203 |
|
| 204 |
### Core Implementation
|
| 205 |
|
|
|
|
| 291 |
|
| 292 |
## Test File Location
|
| 293 |
All test files must be placed in the `/tests` folder. This ensures a clean project structure and makes it easy to discover and run all tests.
|
| 294 |
+
|
| 295 |
+
### PWA Support
|
| 296 |
+
- โ
**PWA Installation:** App is installable as a Progressive Web App on desktop and mobile
|
specs/specs.md
CHANGED
|
@@ -138,6 +138,17 @@ HF_REPO_ID/games/
|
|
| 138 |
- Filters by file_id prefix for matching settings
|
| 139 |
- Loads and verifies full settings match
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
### PWA Support
|
| 142 |
- โ
**PWA Installation:** App is installable as a Progressive Web App on desktop and mobile
|
| 143 |
- Added `service worker` and `manifest.json`
|
|
|
|
| 138 |
- Filters by file_id prefix for matching settings
|
| 139 |
- Loads and verifies full settings match
|
| 140 |
|
| 141 |
+
**Date Display Updates:**
|
| 142 |
+
- All leaderboard files use UTC for period boundaries.
|
| 143 |
+
- When displaying daily leaderboards, show the UTC period as a PST date range.
|
| 144 |
+
- Example: For UTC file date 2025-12-08, display:
|
| 145 |
+
2025-12-08 00:00:00 UTC to 2025-12-08 23:59:59 UTC
|
| 146 |
+
and
|
| 147 |
+
2025-12-07 16:00:00 PST to 2025-12-08 15:59:59 PST
|
| 148 |
+
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]`
|
| 149 |
+
|
| 150 |
+
**Access:** 'Leaderboard' link in the footer navigation at the bottom of the page
|
| 151 |
+
|
| 152 |
### PWA Support
|
| 153 |
- โ
**PWA Installation:** App is installable as a Progressive Web App on desktop and mobile
|
| 154 |
- Added `service worker` and `manifest.json`
|
wrdler/__init__.py
CHANGED
|
@@ -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.3"
|
| 13 |
__all__ = ["models", "generator", "logic", "ui", "word_loader", "leaderboard", "leaderboard_page"]
|
wrdler/leaderboard_page.py
CHANGED
|
@@ -5,12 +5,16 @@ Wrdler Leaderboard Page
|
|
| 5 |
Streamlit page component for displaying daily and weekly leaderboards
|
| 6 |
with historical lookup capabilities.
|
| 7 |
"""
|
| 8 |
-
__version__ = "0.2.
|
| 9 |
|
| 10 |
import streamlit as st
|
| 11 |
-
from datetime import datetime
|
| 12 |
from typing import Optional, Tuple
|
| 13 |
import pandas as pd
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
from wrdler.leaderboard import (
|
| 16 |
load_leaderboard,
|
|
@@ -27,6 +31,40 @@ from wrdler.leaderboard import (
|
|
| 27 |
from wrdler.modules.constants import APP_SETTINGS
|
| 28 |
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
def _format_time(seconds: int) -> str:
|
| 31 |
"""Format seconds as MM:SS."""
|
| 32 |
mins, secs = divmod(seconds, 60)
|
|
@@ -50,9 +88,9 @@ def _settings_badge(lb: LeaderboardSettings) -> str:
|
|
| 50 |
source = lb.wordlist_source
|
| 51 |
incorrect = "errors:on" if lb.show_incorrect_guesses else "errors:off"
|
| 52 |
free = "free:on" if lb.enable_free_letters else "free:off"
|
| 53 |
-
spacer = lb.puzzle_options.get("spacer", 0)
|
| 54 |
-
overlap = lb.puzzle_options.get("may_overlap", False)
|
| 55 |
-
return f"[{mode} โข {source} โข {incorrect} โข {free}
|
| 56 |
|
| 57 |
|
| 58 |
def _render_leaderboard_table(leaderboard: Optional[LeaderboardSettings], title: str):
|
|
@@ -223,12 +261,8 @@ def _render_daily_tab():
|
|
| 223 |
|
| 224 |
for date_id in daily_periods:
|
| 225 |
# Human friendly date title
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
is_today = date_id == get_current_daily_id()
|
| 229 |
-
date_title = ("๐ Today " if is_today else "") + date_obj.strftime("%A, %B %d, %Y")
|
| 230 |
-
except ValueError:
|
| 231 |
-
date_title = date_id
|
| 232 |
|
| 233 |
# List all settings folders (file_id) for this period
|
| 234 |
settings_list = list_settings_for_period("daily", date_id)
|
|
@@ -313,7 +347,7 @@ def _render_history_tab():
|
|
| 313 |
|
| 314 |
if st.button("Load Daily", key="load_daily"):
|
| 315 |
leaderboard = load_leaderboard("daily", selected_daily, selected_file_id)
|
| 316 |
-
header = f"Daily: {selected_daily} "
|
| 317 |
if leaderboard is not None:
|
| 318 |
header += _settings_badge(leaderboard)
|
| 319 |
_render_leaderboard_table(leaderboard, header)
|
|
@@ -387,11 +421,7 @@ def _render_today_tab():
|
|
| 387 |
lb = load_leaderboard("daily", daily_id, gidd)
|
| 388 |
|
| 389 |
if lb:
|
| 390 |
-
|
| 391 |
-
date_obj = datetime.strptime(daily_id, "%Y-%m-%d")
|
| 392 |
-
date_title = "๐ Today " + date_obj.strftime("%A, %B %d, %Y")
|
| 393 |
-
except ValueError:
|
| 394 |
-
date_title = daily_id
|
| 395 |
|
| 396 |
header_suffix = _settings_badge(lb)
|
| 397 |
with st.expander(f"{date_title} {header_suffix}", expanded=True):
|
|
@@ -432,11 +462,7 @@ def _render_today_tab():
|
|
| 432 |
st.subheader("๐
Daily")
|
| 433 |
daily_id = get_current_daily_id()
|
| 434 |
|
| 435 |
-
|
| 436 |
-
date_obj = datetime.strptime(daily_id, "%Y-%m-%d")
|
| 437 |
-
date_title = "Today " + date_obj.strftime("%A, %B %d, %Y")
|
| 438 |
-
except ValueError:
|
| 439 |
-
date_title = daily_id
|
| 440 |
|
| 441 |
settings_list = list_settings_for_period("daily", daily_id)
|
| 442 |
if not settings_list:
|
|
|
|
| 5 |
Streamlit page component for displaying daily and weekly leaderboards
|
| 6 |
with historical lookup capabilities.
|
| 7 |
"""
|
| 8 |
+
__version__ = "0.2.2"
|
| 9 |
|
| 10 |
import streamlit as st
|
| 11 |
+
from datetime import datetime, timedelta, timezone
|
| 12 |
from typing import Optional, Tuple
|
| 13 |
import pandas as pd
|
| 14 |
+
try:
|
| 15 |
+
from zoneinfo import ZoneInfo
|
| 16 |
+
except ImportError:
|
| 17 |
+
ZoneInfo = None
|
| 18 |
|
| 19 |
from wrdler.leaderboard import (
|
| 20 |
load_leaderboard,
|
|
|
|
| 31 |
from wrdler.modules.constants import APP_SETTINGS
|
| 32 |
|
| 33 |
|
| 34 |
+
def _get_pst_range_str(date_id: str) -> str:
|
| 35 |
+
"""
|
| 36 |
+
Convert UTC date string (YYYY-MM-DD) to a formatted PST date range string.
|
| 37 |
+
Example: "Mon, Dec 08, 2025 4:00 PM PST โ Tue, Dec 09, 2025 3:59:59 PM PST"
|
| 38 |
+
"""
|
| 39 |
+
try:
|
| 40 |
+
# UTC start and end
|
| 41 |
+
utc_start = datetime.strptime(date_id, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
| 42 |
+
utc_end = utc_start + timedelta(days=1) - timedelta(seconds=1)
|
| 43 |
+
|
| 44 |
+
tz = None
|
| 45 |
+
if ZoneInfo:
|
| 46 |
+
try:
|
| 47 |
+
tz = ZoneInfo("America/Los_Angeles")
|
| 48 |
+
except Exception:
|
| 49 |
+
pass
|
| 50 |
+
|
| 51 |
+
if tz is None:
|
| 52 |
+
# Fallback to fixed offset UTC-8 (PST)
|
| 53 |
+
tz = timezone(timedelta(hours=-8), name="PST")
|
| 54 |
+
|
| 55 |
+
pst_start = utc_start.astimezone(tz)
|
| 56 |
+
pst_end = utc_end.astimezone(tz)
|
| 57 |
+
|
| 58 |
+
def fmt(dt, show_seconds=False):
|
| 59 |
+
# %I is 01-12. lstrip("0") removes leading zero.
|
| 60 |
+
time_str = dt.strftime("%I:%M" + (":%S" if show_seconds else "") + " %p").lstrip("0")
|
| 61 |
+
return f"{dt.strftime('%a, %b %d, %Y')} {time_str} {dt.strftime('%Z')}"
|
| 62 |
+
|
| 63 |
+
return f"{fmt(pst_start)} โ {fmt(pst_end, show_seconds=True)}"
|
| 64 |
+
except Exception:
|
| 65 |
+
return date_id
|
| 66 |
+
|
| 67 |
+
|
| 68 |
def _format_time(seconds: int) -> str:
|
| 69 |
"""Format seconds as MM:SS."""
|
| 70 |
mins, secs = divmod(seconds, 60)
|
|
|
|
| 88 |
source = lb.wordlist_source
|
| 89 |
incorrect = "errors:on" if lb.show_incorrect_guesses else "errors:off"
|
| 90 |
free = "free:on" if lb.enable_free_letters else "free:off"
|
| 91 |
+
#spacer = lb.puzzle_options.get("spacer", 0)
|
| 92 |
+
#overlap = lb.puzzle_options.get("may_overlap", False)
|
| 93 |
+
return f"\n\n[{mode} โข {source} โข {incorrect} โข {free}]"
|
| 94 |
|
| 95 |
|
| 96 |
def _render_leaderboard_table(leaderboard: Optional[LeaderboardSettings], title: str):
|
|
|
|
| 261 |
|
| 262 |
for date_id in daily_periods:
|
| 263 |
# Human friendly date title
|
| 264 |
+
is_today = date_id == get_current_daily_id()
|
| 265 |
+
date_title = ("๐ Today: " if is_today else "") + _get_pst_range_str(date_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
# List all settings folders (file_id) for this period
|
| 268 |
settings_list = list_settings_for_period("daily", date_id)
|
|
|
|
| 347 |
|
| 348 |
if st.button("Load Daily", key="load_daily"):
|
| 349 |
leaderboard = load_leaderboard("daily", selected_daily, selected_file_id)
|
| 350 |
+
header = f"Daily: {_get_pst_range_str(selected_daily)} "
|
| 351 |
if leaderboard is not None:
|
| 352 |
header += _settings_badge(leaderboard)
|
| 353 |
_render_leaderboard_table(leaderboard, header)
|
|
|
|
| 421 |
lb = load_leaderboard("daily", daily_id, gidd)
|
| 422 |
|
| 423 |
if lb:
|
| 424 |
+
date_title = "๐ Today: " + _get_pst_range_str(daily_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
header_suffix = _settings_badge(lb)
|
| 427 |
with st.expander(f"{date_title} {header_suffix}", expanded=True):
|
|
|
|
| 462 |
st.subheader("๐
Daily")
|
| 463 |
daily_id = get_current_daily_id()
|
| 464 |
|
| 465 |
+
date_title = "Today " + _get_pst_range_str(daily_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 466 |
|
| 467 |
settings_list = list_settings_for_period("daily", daily_id)
|
| 468 |
if not settings_list:
|
wrdler/ui.py
CHANGED
|
@@ -1618,7 +1618,7 @@ def _render_guess_form(state: GameState):
|
|
| 1618 |
display: inline-flex;
|
| 1619 |
align-items: center;
|
| 1620 |
width: auto;
|
| 1621 |
-
min-width:
|
| 1622 |
}
|
| 1623 |
.st-key-guess_input .stTooltipIcon .stTooltipHoverTarget::after {
|
| 1624 |
content: "incorrect guesses";
|
|
@@ -2408,9 +2408,7 @@ def _render_footer(current_page: str = "play"):
|
|
| 2408 |
"""
|
| 2409 |
# Determine which link should be highlighted as active
|
| 2410 |
play_active = "active" if current_page == "play" else ""
|
| 2411 |
-
|
| 2412 |
-
daily_active = "active" if current_page == "daily" else ""
|
| 2413 |
-
weekly_active = "active" if current_page == "weekly" else ""
|
| 2414 |
|
| 2415 |
# Check if we're in challenge mode and need to preserve game_id
|
| 2416 |
game_id = None
|
|
@@ -2430,11 +2428,13 @@ def _render_footer(current_page: str = "play"):
|
|
| 2430 |
today_url = f"?page=today&game_id={game_id}"
|
| 2431 |
daily_url = f"?page=daily&game_id={game_id}"
|
| 2432 |
weekly_url = f"?page=weekly&game_id={game_id}"
|
|
|
|
| 2433 |
play_url = f"?game_id={game_id}"
|
| 2434 |
else:
|
| 2435 |
today_url = "?page=today"
|
| 2436 |
daily_url = "?page=daily"
|
| 2437 |
weekly_url = "?page=weekly"
|
|
|
|
| 2438 |
play_url = "/"
|
| 2439 |
|
| 2440 |
st.markdown(
|
|
@@ -2494,9 +2494,7 @@ def _render_footer(current_page: str = "play"):
|
|
| 2494 |
</style>
|
| 2495 |
<div class="bw-footer">
|
| 2496 |
<nav class="bw-footer-nav">
|
| 2497 |
-
<a href="{
|
| 2498 |
-
<a href="{daily_url}" title="View Daily Leaderboards" target="_self" class="{daily_active}">๐
Daily</a>
|
| 2499 |
-
<a href="{weekly_url}" title="View Weekly Leaderboards" target="_self" class="{weekly_active}">๐ Weekly</a>
|
| 2500 |
<a href="{play_url}" title="Play Wrdler" target="_self" class="{play_active}">๐ฎ Play</a>
|
| 2501 |
</nav>
|
| 2502 |
</div>
|
|
|
|
| 1618 |
display: inline-flex;
|
| 1619 |
align-items: center;
|
| 1620 |
width: auto;
|
| 1621 |
+
min-width: 100px;
|
| 1622 |
}
|
| 1623 |
.st-key-guess_input .stTooltipIcon .stTooltipHoverTarget::after {
|
| 1624 |
content: "incorrect guesses";
|
|
|
|
| 2408 |
"""
|
| 2409 |
# Determine which link should be highlighted as active
|
| 2410 |
play_active = "active" if current_page == "play" else ""
|
| 2411 |
+
leaderboard_active = "active" if current_page in {"today", "daily", "weekly", "history"} else ""
|
|
|
|
|
|
|
| 2412 |
|
| 2413 |
# Check if we're in challenge mode and need to preserve game_id
|
| 2414 |
game_id = None
|
|
|
|
| 2428 |
today_url = f"?page=today&game_id={game_id}"
|
| 2429 |
daily_url = f"?page=daily&game_id={game_id}"
|
| 2430 |
weekly_url = f"?page=weekly&game_id={game_id}"
|
| 2431 |
+
leaderboard_url = f"?page=today&game_id={game_id}"
|
| 2432 |
play_url = f"?game_id={game_id}"
|
| 2433 |
else:
|
| 2434 |
today_url = "?page=today"
|
| 2435 |
daily_url = "?page=daily"
|
| 2436 |
weekly_url = "?page=weekly"
|
| 2437 |
+
leaderboard_url = "?page=today"
|
| 2438 |
play_url = "/"
|
| 2439 |
|
| 2440 |
st.markdown(
|
|
|
|
| 2494 |
</style>
|
| 2495 |
<div class="bw-footer">
|
| 2496 |
<nav class="bw-footer-nav">
|
| 2497 |
+
<a href="{leaderboard_url}" title="View Leaderboards" target="_self" class="{leaderboard_active}">๐ Leaderboard</a>
|
|
|
|
|
|
|
| 2498 |
<a href="{play_url}" title="Play Wrdler" target="_self" class="{play_active}">๐ฎ Play</a>
|
| 2499 |
</nav>
|
| 2500 |
</div>
|