| # Wrdler - Project Context | |
| ## Project Overview | |
| Wrdler is a simplified vocabulary puzzle game based on BattleWords: | |
| - **Python project** (Streamlit, Python 3.12.8) | |
| - **8x6 grid** (8 columns Γ 6 rows, one word per row, horizontal only) | |
| - **No scope/radar visualization** | |
| - **2 free letter guesses at game start** (all instances revealed) | |
| - **Word composition:** 2 four-letter, 2 five-letter, 2 six-letter words per puzzle | |
| **Current Version:** 0.2.4 | |
| **Repository:** https://github.com/Oncorporation/Wrdler.git | |
| **Branch:** AI (working branch) | |
| ## Current Features (v0.2.4) | |
| ### Core Gameplay | |
| - 8x6 grid with 6 hidden words (one per row, horizontal only) | |
| - Players choose 2 free letters at start; all instances are revealed | |
| - Click cells to reveal letters or empty spaces | |
| - Guess words for points (word length + bonus for unrevealed letters) | |
| - Game ends when all words guessed or all word letters are revealed | |
| - Incorrect guess history display (toggleable, default enabled) | |
| - 10 incorrect guess limit per game | |
| ### Game Modes | |
| 1. **Classic Mode:** Allows consecutive guessing after correct answers | |
| 2. **Too Easy Mode:** Single guess per reveal | |
| ### Scoring Tiers | |
| - **Legendary:** 45+ points | |
| - **Fantastic:** 42-44 points | |
| - **Great:** 39-41 points | |
| - **Good:** 35-38 points | |
| - **Keep practicing:** < 35 points | |
| ### Word List Management | |
| - Sidebar controls for sorting and filtering word lists | |
| - Filter capability using `assets/filter.txt` blocklist to remove unwanted words | |
| - Dialog display of removed words after filtering | |
| ### Challenge Mode & Remote Storage | |
| - Short URL-based challenge sharing via `?game_id=<sid>` | |
| - Each player gets different random words from same wordlist | |
| - Multi-user challenge leaderboards (top 5 display) | |
| - Remote storage via HuggingFace datasets | |
| - Word list difficulty calculation | |
| - "Show Challenge Share Links" toggle (default OFF) | |
| - **Integration:** | |
| - Automatic submission after game completion (opt-in via game over popup) | |
| - Challenge scores also contribute to daily/weekly leaderboards | |
| - Source tracking via `source_challenge_id` field | |
| - Unified JSON format with `entry_type` field (daily/weekly/challenge) | |
| **Access:** 'Leaderboard' link in the footer navigation at the bottom of the page | |
| ### Daily & Weekly Leaderboards | |
| - **Settings-Based Separation:** Each unique settings combo creates separate leaderboard | |
| - Settings: `game_mode`, `wordlist_source`, `show_incorrect_guesses`, `enable_free_letters`, `puzzle_options` (spacer, may_overlap) | |
| - **Auto Score Submission:** Checks qualification for top 25 after game completion | |
| - **Storage:** Folder-based discovery at `games/leaderboards/{daily|weekly}/{period}/{file_id}/settings.json` | |
| - **File ID Format:** `{wordlist_source}-{game_mode}-{sequence}` (e.g., `classic-classic-0`) | |
| - **Leaderboard Page:** Four tabs (Today, Daily, Weekly, History) accessible via `?page=today|daily|weekly|history` | |
| - Leaderboard files use UTC for all period boundaries. | |
| - When displaying daily leaderboards, show the UTC period as a PST date range. | |
| - Example: For UTC file date 2025-12-08, display: | |
| 2025-12-08 00:00:00 UTC to 2025-12-08 23:59:59 UTC | |
| and | |
| 2025-12-07 16:00:00 PST to 2025-12-08 15:59:59 PST | |
| 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]` | |
| ### AI Word Generation | |
| - Topic-based word list generation via HuggingFace Spaces or local transformers | |
| - Automatic word saving (max 1000 words per file) | |
| - Retry mechanism (up to 3 attempts) for insufficient word counts | |
| - Fallback to dictionary words if AI unavailable | |
| ### Audio & Visuals | |
| - Ocean-themed gradient background with wave animations | |
| - Toggleable background music with volume control | |
| - Sound effects (hit/miss/correct/incorrect) with volume control | |
| ### PWA Support | |
| - Installable as Progressive Web App on desktop and mobile | |
| - Service worker for offline caching of static assets | |
| - Works offline for basic functionality | |
| ## Technical Architecture | |
| ### Technology Stack | |
| - **Framework:** Streamlit 1.51.0 | |
| - **Language:** Python 3.12.8 (requires >=3.12, <3.13) | |
| - **Remote Storage:** huggingface_hub (>=0.20.0) | |
| - **AI Generation:** transformers, gradio_client | |
| - **Testing:** Pytest | |
| - **Package Manager:** UV or pip | |
| ### Project Structure | |
| ``` | |
| wrdler/ | |
| βββ app.py # Streamlit entry point | |
| βββ wrdler/ # Main package | |
| β βββ __init__.py # Version: 0.2.0 | |
| β βββ models.py # Data models (Coord, Word, Puzzle, GameState) | |
| β βββ generator.py # Puzzle generation with deterministic seeding | |
| β βββ logic.py # Game mechanics (reveal, guess, scoring) | |
| β βββ ui.py # Streamlit UI with query param routing | |
| β βββ oauth.py # HuggingFace OAuth utilities (NEW) | |
| β βββ settings_page.py # Settings page UI (IN PROGRESS) | |
| β βββ leaderboard.py # Leaderboard system (daily/weekly) | |
| β βββ leaderboard_page.py # Leaderboard UI page | |
| β βββ word_loader.py # Word list management | |
| β βββ word_loader_ai.py # AI word generation | |
| β βββ game_storage.py # HF game storage wrapper | |
| β βββ version_info.py # Version display | |
| β βββ modules/ # Shared utility modules (from OpenBadge) | |
| β β βββ __init__.py # Module exports | |
| β β βββ storage.py # HuggingFace storage & URL shortener (with folder listing) | |
| β β βββ storage.md # Storage module documentation | |
| β β βββ constants.py # Storage-related constants (trimmed) | |
| β β βββ file_utils.py # File utility functions | |
| β βββ words/ # Word list files | |
| β βββ classic.txt # Default word list | |
| β βββ fourth_grade.txt # Elementary word list | |
| βββ tests/ # Unit tests | |
| βββ specs/ # Documentation | |
| βββ static/ # PWA assets (manifest.json, service-worker.js) | |
| βββ .env # Environment variables (HF credentials) | |
| βββ pyproject.toml # Project metadata | |
| βββ requirements.txt # Dependencies | |
| βββ uv.lock # UV lock file | |
| βββ Dockerfile # Container deployment | |
| βββ README.md # User-facing documentation | |
| βββ CLAUDE.md # This file - project context for Claude | |
| βββ GAMEPLAY_GUIDE.md # User guide with tips and strategies | |
| βββ pyproject.toml # Project metadata | |
| βββ requirements.txt # Dependencies | |
| βββ uv.lock # UV lock file | |
| βββ Dockerfile # Container deployment | |
| βββ README.md # User-facing documentation | |
| βββ CLAUDE.md # This file - project context for Claude | |
| βββ GAMEPLAY_GUIDE.md # User guide with tips and strategies | |
| ``` | |
| ### Page Navigation System | |
| Uses **query parameter-based routing** (NOT Streamlit multi-page): | |
| - `?page=today|daily|weekly|history` β Leaderboard pages | |
| - `?page=settings` β Settings page (IN PROGRESS - OAuth protected) | |
| - `?game_id=<sid>` β Challenge mode | |
| - No query params β Main game page | |
| ## Data Models | |
| ### Core Classes | |
| ```python | |
| @dataclass | |
| class Coord: | |
| x: int # row, 0-based | |
| y: int # col, 0-based | |
| @dataclass | |
| class Word: | |
| text: str | |
| start: Coord | |
| direction: Direction # "H" or "V" | |
| cells: List[Coord] | |
| @dataclass | |
| class Puzzle: | |
| words: List[Word] | |
| may_overlap: bool | |
| spacer: int | |
| uid: str # Unique identifier | |
| @dataclass | |
| class GameState: | |
| grid_rows: int # 6 for Wrdler | |
| grid_cols: int # 8 for Wrdler | |
| puzzle: Puzzle | |
| revealed: Set[Coord] | |
| guessed: Set[str] | |
| score: int | |
| last_action: str | |
| can_guess: bool | |
| game_mode: str | |
| points_by_word: Dict[str, int] | |
| start_time: Optional[datetime] | |
| end_time: Optional[datetime] | |
| ``` | |
| ## Environment Variables | |
| Create a `.env` file in the project root: | |
| ```bash | |
| # Challenge Mode & Leaderboards (Remote Storage) | |
| HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx # HuggingFace API token with write access | |
| HF_REPO_ID=YourUsername/YourRepo # Dataset repo for challenge storage | |
| # AI Word Generation | |
| USE_HF_WORDS=false # Enable HF Space API for word generation | |
| HF_WORD_LIST_REPO_ID=YourUsername/WordRepo # Dataset repo for AI word lists | |
| # OAuth Admin Access (NEW) | |
| ADMIN_USERS=username1,username2 # Comma-separated list of admin usernames | |
| MAX_DISPLAY_ENTRIES=25 # Max leaderboard entries to display (default: 25) | |
| ``` | |
| ### HF_REPO_ID Structure | |
| ``` | |
| HF_REPO_ID/ | |
| βββ shortener.json # URL shortener mappings | |
| βββ games/{uid}/settings.json # Challenge data (entry_type: "challenge") | |
| βββ games/leaderboards/ | |
| βββ daily/{YYYY-MM-DD}/{file_id}/settings.json # Daily leaderboards | |
| βββ weekly/{YYYY-Www}/{file_id}/settings.json # Weekly leaderboards | |
| ``` | |
| ## Development Workflow | |
| ### Running Locally | |
| ```bash | |
| # Install dependencies | |
| uv pip install -r requirements.txt --link-mode=copy | |
| # Run app | |
| streamlit run app.py | |
| ``` | |
| ### Testing | |
| ```bash | |
| pytest tests/ | |
| ``` | |
| ## Next Step: OAuth-Protected Settings Page | |
| ### Goal | |
| Move all game settings from sidebar to a dedicated settings page at `?page=settings`, protected by HuggingFace OAuth (admin-only access). | |
| ### Implementation Approach | |
| 1. **Use existing query parameter routing** (like leaderboard pages) | |
| 2. **HuggingFace OAuth integration:** | |
| - Add `hf_oauth: true` to README.md YAML header β DONE | |
| - Create `wrdler/oauth.py` with utility functions β DONE | |
| - OAuth user info available at `st.session_state["oauth_user"]` | |
| - Check admin access via `ADMIN_USERS` environment variable | |
| 3. **Create `wrdler/settings_page.py`:** | |
| - Check authentication with `require_admin()` from oauth.py | |
| - Move settings UI from sidebar (word list, game mode, audio, etc.) | |
| - Persist settings to session state | |
| - Accessible via `?page=settings` | |
| 4. **Modify `wrdler/ui.py`:** | |
| - Add settings page route handler (similar to leaderboard routing) | |
| - Remove settings from sidebar (keep minimal controls only) | |
| - Add "βοΈ Settings" link in footer navigation | |
| 5. **Keep sidebar minimal:** | |
| - Version info | |
| - User info (if logged in) | |
| - Link to settings page | |
| ### HuggingFace OAuth Flow | |
| 1. User clicks login button (HF Spaces provides this automatically) | |
| 2. User authorizes with HF account | |
| 3. HF redirects back with OAuth token | |
| 4. User info stored in `st.session_state["oauth_user"]` | |
| 5. Check `username` against `ADMIN_USERS` env var | |
| 6. Grant/deny access to settings page | |
| ### Files to Modify | |
| - `wrdler/ui.py` - Add settings page routing, remove sidebar settings | |
| - `wrdler/settings_page.py` - NEW FILE - Settings UI with OAuth protection | |
| - `wrdler/oauth.py` - Already created β | |
| ### Key OAuth Functions (wrdler/oauth.py) | |
| ```python | |
| get_user_info() β Dict | None # Get authenticated user info | |
| get_username() β str | None # Get username (preferred_username) | |
| is_authenticated() β bool # Check if user is logged in | |
| is_admin(allowed_users) β bool # Check if user is admin | |
| require_admin(page_name) β bool # Validate admin access or show error | |
| ``` | |
| ## Technical Notes | |
| ### Important Implementation Details | |
| - **Python syntax only** - Use colons `:` not braces `{}` | |
| - **8Γ6 grid:** `grid_rows=6`, `grid_cols=8` | |
| - **Horizontal-only placement:** One word per row | |
| - **Query param routing:** All pages use `?page=<name>` system | |
| - **Session state management:** Heavy use of `st.session_state` | |
| - **OAuth detection:** Check `st.session_state.get("oauth_user")` | |
| ### Current Routing Pattern (ui.py) | |
| ```python | |
| params = st.query_params | |
| page = params.get("page", "") | |
| if page in {"today", "daily", "weekly", "history"}: | |
| render_leaderboard_page(default_tab=page) | |
| return | |
| if page == "settings": # TO BE IMPLEMENTED | |
| render_settings_page() | |
| return | |
| # Default: main game page | |
| run_app() | |
| ``` | |
| ## Deployment Platforms | |
| 1. **HuggingFace Spaces** (Primary) - Dockerfile deployment with OAuth support | |
| 2. **Local Development** - Streamlit run | |
| 3. **Docker** - Containerized deployment | |
| ## Git Configuration | |
| - **Current Branch:** AI (working branch) | |
| - **Main Branch:** main | |
| - **Remotes:** | |
| - origin: https://github.com/Oncorporation/Wrdler.git | |
| - Hugging: https://huggingface.co/spaces/Surn/Wrdler | |