wrdler / CLAUDE.md
Surn's picture
v0.2.5
082223e

A newer version of the Streamlit SDK is available: 1.52.1

Upgrade

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

@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

  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)

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

  1. HuggingFace Spaces (Primary) - Dockerfile deployment with OAuth support
  2. Local Development - Streamlit run
  3. Docker - Containerized deployment

Git Configuration