# Battlewords: Implementation Requirements This document breaks down the tasks to build Battlewords using the game rules described in `specs.md`. It is organized in phases: a minimal Proof of Concept (POC), a Beta Version (0.5.0), and a Full Version (1.0.0). Assumptions - Tech stack: Python 3.10+, Streamlit for UI, matplotlib for radar, numpy for tick helpers, Pillow for animated GIFs. - Single-player, local state stored in Streamlit session state for POC. - Grid is always 12x12 with exactly six words: two 4-letter, two 5-letter, two 6-letter words; horizontal/vertical only; no shared letters or overlaps in POC; shared-letter overlaps allowed in Beta; no overlaps in Full. - Entry point is `app.py`. Streamlit key components (API usage plan) - State & caching - `st.session_state` for `puzzle`, `grid_size`, `revealed`, `guessed`, `score`, `last_action`, `can_guess`. - `st.session_state.points_by_word` for per-word score breakdown. - `st.session_state.letter_map` derived from puzzle. - `st.session_state.selected_wordlist` for sidebar picker. - `st.session_state.radar_gif_path` for session-persistent radar animation. - `st.session_state.show_incorrect_guesses` toggle. - `st.session_state.show_challenge_share_links` toggle (v0.2.27, default OFF) to control visibility of challenge share links in the header and Game Over dialog. - Layout & structure - `st.title`, `st.subheader`, `st.markdown` for headers/instructions. - `st.columns(12)` to render the 12×12 grid; `st.container` for grouping; `st.sidebar` for secondary controls/help. - `st.expander` for inline help/intel tips. - Widgets (interaction) - `st.button` for each grid cell (144 total) with unique `key` to handle reveals. - `st.form` + `st.text_input` + `st.form_submit_button("OK")` for controlled word guessing (disabled until a reveal). - `st.button("New Game")` to reset state; sidebar `selectbox` for wordlist selection and `Sort Wordlist` button (length+alpha). - `st.metric` to show score. - Visualization - Animated radar using matplotlib `FuncAnimation` + `PillowWriter` saved to GIF. - Scope overlay image generated once and reused; metallic gradient background. - Radar plot uses inverted Y so (0,0) is top-left. - Control flow - App reruns on interaction; uses `st.rerun()` after state changes (reveal, guess); `st.stop()` after game over summary to freeze UI. Folder Structure - `app.py` – Streamlit entry point - `battlewords/` – Python package - `__init__.py` - `models.py` – data models and types - `word_loader.py` – load/validate/cached word lists (uses `battlewords/words/wordlist.txt` with fallback) - `generator.py` – word placement; imports from `word_loader`; avoids duplicate words - `logic.py` – game mechanics (reveal, guess, scoring, tiers) - `ui.py` – Streamlit UI composition; animated radar; immediate rerender on reveal/guess via `st.rerun()`; inverted radar Y - `words/wordlist.txt` – candidate words - `specs/` – documentation (this file and `specs.md`) - `tests/` – unit tests Phase 1: Proof of Concept (0.1.0) Goal: A playable, single-session game demonstrating core rules, scoring, and radar without persistence or advanced UX. 1) Data Models - Define `Coord(x:int, y:int)`. - Define `Word(text:str, start:Coord, direction:str{"H","V"}, cells:list[Coord])`. - Define `Puzzle(words:list[Word], radar:list[Coord])` – radar holds last-letter coordinates. - Define `GameState(grid_size:int=12, puzzle:Puzzle, revealed:set[Coord], guessed:set[str], score:int, last_action:str, can_guess:bool)`. Acceptance: Types exist and are consumed by generator/logic; simple constructors and validators. 2) Word List - Add an English word list filtered to alphabetic uppercase, lengths in {4,5,6}. - Ensure words contain no special characters; maintain reasonable difficulty. - Streamlit: `st.cache_data` to memoize loading/filtering. - Loader is centralized in `word_loader.py` and used by generator and UI. Acceptance: Loading function returns lists by length with >= 25 words per length or fallback minimal lists. 3) Puzzle Generation (Placement) - Randomly place 2×4, 2×5, 2×6 letter words on a 12×12 grid. - Constraints (POC): - Horizontal (left→right) or Vertical (top→down) only. - No overlapping letters between different words (cells must be unique). - Compute radar pulses as the last cell of each word. - Retry strategy with max attempts; raise a controlled error if generation fails. Acceptance: Generator returns a valid `Puzzle` passing validation checks (no collisions, in-bounds, correct counts, no duplicates). 4) Game Mechanics - Reveal: - Click a covered cell to reveal; if the cell is part of a word, show the letter; else mark empty (CSS class `empty`). - After a reveal action, set `can_guess=True`. - Guess: - Accept a guess only if `can_guess` is True and input length ∈ {4,5,6}. - Match guess case-insensitively against unguessed words in puzzle. - If correct: add base points = word length; bonus points = count of unrevealed cells in that word at guess time; mark all cells of the word as revealed; add to `guessed`. - If incorrect: no points awarded. - After any guess, set `can_guess=False` and require another reveal before next guess. - Exception: in default mode a correct guess allows chaining (`can_guess=True`); other modes may disable chaining. - Streamlit: `with st.form("guess"):` + `st.text_input("Your guess")` + `st.form_submit_button("OK", disabled=not can_guess)`; after guess, call `st.rerun()`. - End of game when all 6 words are guessed or all word letters are revealed; display summary and tier, then `st.stop()`. Acceptance: Unit tests cover scoring, guess gating, and reveal behavior. 5) UI (Streamlit) - Layout: - Title and brief instructions via `st.title`, `st.subheader`, `st.markdown`. - Left: 12×12 grid using `st.columns(12)`. - Right: Animated radar, Correct/Try Again indicator, guess form, and score panel. - Sidebar: New Game, wordlist selectbox, Sort Wordlist action, Game Mode selector. - Visuals: - Covered vs revealed styles; revealed empty cells use CSS class `empty`. - Completed word cells styled with `bw-cell-complete`; cell tooltips show coordinates. Acceptance: Users can play end-to-end; radar shows exactly 6 pulses; reveal and guess update via rerun; completed words are visually distinct. 6) Scoring Tiers - After game ends, compute tier: - Good: 34–37 - Great: 38–41 - Fantastic: 42+ 7) Basic Tests - Placement validity (bounds, overlap, counts, no duplicate words). - Scoring logic and bonus calculation. - Guess gating (reveal required except chaining after correct guess when enabled). Current Deltas (0.1.3 → 0.1.10) - 0.1.3 - Sidebar wordlist select; sorting persists length-then-alpha ordering; auto new-game after 5s notice. - Score panel improvements; per-word points; final score styling. - 0.1.4 - Animated radar GIF with metallic gradient and scope overlay; session reuse via `radar_gif_path`. - Mobile layout improvements; tighter grid spacing and horizontal scroll per row. - 0.1.5 - Hit/Miss indicator derived from `last_action`. - Completed word cells render as non-buttons with tooltips. - Helper functions for scope image and stable letter map rebuild. - 0.1.10 - Game Mode selector (`standard`, `too easy`); chaining allowed only in `standard`. - Guess feedback indicator switched to Correct/Try Again. - Version footer shows commit/Python/Streamlit; ocean background effect. - Word list default/persistence fixes and sort action persists after delay. - 0.2.24 - compress height - change incorrect guess tooltip location - update final screen layout - add word difficulty formula - update documentation - 0.2.28 - Add Progressive Web App (PWA) support with `service worker` and `manifest.json` - Add INSTALL_GUIDE.md for PWA install instructions - No gameplay logic changes Known Issues / TODO - Word list selection bug: improper list fetched/propagated in some runs. - Verify `get_wordlist_files()` returns correct filenames and `selected_wordlist` persists across `_new_game()`. - Ensure `load_word_list(selected_wordlist)` loads the chosen file and matches `generate_puzzle(words_by_len=...)` expected shape. - Add tests for selection, sorting, and fallback behavior. Phase 1.5: Storage and Sharing (0.3.0) - NEW Goal: Add persistent storage, high scores, and game sharing capabilities. A) Storage Module - Create `battlewords/storage.py` with: - `GameStorage` class for saving/loading game results and high scores - `GameResult` and `HighScoreEntry` dataclasses - JSON-based local storage in `~/.battlewords/data/` - Update `models.py` to include `game_id` in Puzzle (based on word list) - Update `generator.py` to: - Generate game_id from sorted word list - Accept optional `target_words` parameter for replay - Integrate storage into game flow: - Save result on game completion - Display high scores in sidebar Acceptance: - Game results saved to local JSON files - High scores correctly filtered and sorted - Game IDs generated deterministically B) Game Sharing - Parse `game_id` from query params (`?game_id=ABC123`) - Generate puzzle from game_id (same words, different positions) - "Share Challenge" button creates shareable URL - Display game_id in UI to show shared challenges Acceptance: - URL with game_id loads same word set - Share button generates correct URL - Game ID visible to players C) High Score Display - Sidebar expander for high scores - Filter by: All-time, Current Wordlist, Current Mode - Top 10 entries with: Rank, Player, Score, Tier, Time - Player name input (optional, defaults to "Anonymous") Acceptance: - High scores display correctly - Filters work as expected - Player names saved with results D) Tests - Unit tests for storage operations - Test game ID generation consistency - Test save/load result flow - Test high score filtering and ranking Milestones and Estimates (High-level) - Phase 1 (POC): 2–4 days ✅ COMPLETE - Phase 1.5 (Local Storage & Sharing - planned): 2–3 days ⏳ PENDING (deferred to v0.3.0) - Phase 1.6 (Remote Storage & Challenge Mode): 3–4 days ✅ COMPLETE (v0.2.20-0.2.27) - Phase 1.7 (Local Player History): 2–3 days ⏳ IN PROGRESS (v0.3.0) - Beta (0.5.0): 3–5 days (overlaps, responsive UI, keyboard, deterministic seed) - Phase 2 (Full): 1–2 weeks depending on features selected Definitions of Done (per task) - Code merged with tests and docs updated. - No regressions in existing tests; coverage maintained or improved for core logic. - Manual playthrough validates rules: reveal/guess gating, scoring, radar pulses, end state and tiers. ## v0.2.20 Update: Game Sharing with Shortened game_id URL ### Game Sharing Feature - On game completion, save a JSON file to the storage server named by a unique `uid`. - The JSON file contains: word_list, score, time, game_mode, grid_size, and puzzle options. - Generate a shortened URL for this file; use it as the `game_id` in the shareable link. - When a user loads a game with a `game_id` query string, fetch the JSON file and apply all settings for the session. ### Implementation Notes - The game_id is a shortened URL referencing the JSON file. - The app applies all settings from the file for a true replay. - No direct encoding of game data in the query string; only the reference is shared. ## Phase 1.6: Remote Storage & Challenge Mode (v0.2.20-0.2.27) ✅ COMPLETE ### Goal Persist per-game settings and leaderboards on a storage server (Hugging Face Hub repo) with shortened URLs for challenge sharing. ### A) Storage Server Integration ✅ - Imported modules from OpenBadge `modules/storage.py`: - `upload_files_to_repo(...)` to write JSON to `HF_REPO_ID` - `gen_full_url(...)` for shortener lookups/creation backed by `shortener.json` - Created `battlewords/game_storage.py` wrapper with functions: - `save_game_to_hf()` - Save challenge and get short URL - `load_game_from_sid()` - Load challenge from short ID - `add_user_result_to_game()` - Append user result to existing challenge - `get_shareable_url()` - Generate shareable URLs - Repository structure in HF dataset: - `shortener.json` - Short URL mappings - `games/{uid}/settings.json` - Per-game challenge data with users array - Required env vars (.env): `HF_API_TOKEN` (or `HF_TOKEN`), `HF_REPO_ID`, `SPACE_NAME` ### B) Sharing Link (game_id) ✅ - Shortened URL flow: `gen_full_url(full_url=...)` returns short id (sid) - Shareable link format: `https:///?game_id=` - On app load with `game_id`: fetch JSON, apply settings, show Challenge Mode banner ### C) Challenge Mode Features ✅ - Multi-user leaderboards with score, time, and difficulty tracking - Results sorted by: highest score → fastest time → highest difficulty - Challenge Mode UI banner showing top 5 players - Submit result to existing challenge or create new challenge - Word list difficulty calculation and display - "Show Challenge Share Links" toggle (default OFF) for URL visibility control - Each player gets different random words from the same wordlist source ### D) Dependencies ✅ - Added `huggingface_hub` and `python-dotenv` to requirements - Module imports in `ui.py:30` ### E) Acceptance Criteria ✅ - ✅ Completed game produces working share link with `game_id` sid - ✅ Visiting link reconstructs challenge with leaderboard - ✅ Multiple users can submit results to same challenge - ✅ Leaderboard displays and sorts correctly - ✅ Documentation updated with env vars and flows - ✅ App works without HF credentials (Challenge Mode features disabled gracefully) ### F) Implementation Files - `battlewords/game_storage.py` - HF storage wrapper (v0.1.0) - `battlewords/modules/storage.py` - Generic HF storage (v0.1.5) - `battlewords/ui.py` - Challenge Mode UI integration (lines 508-601, 1588-1701) - `battlewords/generator.py` - Support for target_words parameter ## Phase 1.7: Local Player History (v0.3.0) ⏳ IN PROGRESS ### Goal Add local persistent storage for individual player game results and personal high scores (offline-capable, privacy-first). ### A) Local Storage Module - Create `battlewords/local_storage.py` with: - `GameResult` and `HighScoreEntry` dataclasses - JSON-based storage in `~/.battlewords/data/` - Functions: `save_game_result()`, `load_high_scores()`, `get_player_stats()` - Storage location: `~/.battlewords/data/` (game_results.json, highscores.json) ### B) High Score Display - Sidebar expander for personal high scores - Filter by: All-time, Current Wordlist, Current Mode - Top 10 entries with: Rank, Player, Score, Tier, Time - Player name input (optional, defaults to "Anonymous") ### C) Player Statistics - Games played, average score, best score - Fastest completion time - Per-player history tracking ### D) Acceptance Criteria - Local JSON files created and updated on game completion - High scores display correctly in sidebar - Filters work as expected - Player names saved with results - No cloud dependency required - Easy data deletion (remove ~/.battlewords/data/)