A newer version of the Streamlit SDK is available:
1.52.1
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
- Classic Mode: Allows consecutive guessing after correct answers
- 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.txtblocklist 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_idfield - Unified JSON format with
entry_typefield (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)
- Settings:
- 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
@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:
# 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
# Install dependencies
uv pip install -r requirements.txt --link-mode=copy
# Run app
streamlit run app.py
Testing
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
- Use existing query parameter routing (like leaderboard pages)
- HuggingFace OAuth integration:
- Add
hf_oauth: trueto README.md YAML header β DONE - Create
wrdler/oauth.pywith utility functions β DONE - OAuth user info available at
st.session_state["oauth_user"] - Check admin access via
ADMIN_USERSenvironment variable
- Add
- 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
- Check authentication with
- 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
- Keep sidebar minimal:
- Version info
- User info (if logged in)
- Link to settings page
HuggingFace OAuth Flow
- User clicks login button (HF Spaces provides this automatically)
- User authorizes with HF account
- HF redirects back with OAuth token
- User info stored in
st.session_state["oauth_user"] - Check
usernameagainstADMIN_USERSenv var - Grant/deny access to settings page
Files to Modify
wrdler/ui.py- Add settings page routing, remove sidebar settingswrdler/settings_page.py- NEW FILE - Settings UI with OAuth protectionwrdler/oauth.py- Already created β
Key OAuth Functions (wrdler/oauth.py)
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)
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
- HuggingFace Spaces (Primary) - Dockerfile deployment with OAuth support
- Local Development - Streamlit run
- Docker - Containerized deployment
Git Configuration
- Current Branch: AI (working branch)
- Main Branch: main
- Remotes: