BattleWords / specs /requirements.md
Surn's picture
0.2.28
2de3d96

A newer version of the Streamlit SDK is available: 1.52.2

Upgrade

ο»Ώ# 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.

  1. 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.

  1. 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).

  1. 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.

  1. 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.

  1. Scoring Tiers
  • After game ends, compute tier:
    • Good: 34–37
    • Great: 38–41
    • Fantastic: 42+
  1. 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://<SPACE_NAME>/?game_id=<sid>
  • 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/)