Spaces:
Running
A newer version of the Streamlit SDK is available:
1.52.2
ο»Ώ# 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_stateforpuzzle,grid_size,revealed,guessed,score,last_action,can_guess.st.session_state.points_by_wordfor per-word score breakdown.st.session_state.letter_mapderived from puzzle.st.session_state.selected_wordlistfor sidebar picker.st.session_state.radar_gif_pathfor session-persistent radar animation.st.session_state.show_incorrect_guessestoggle.st.session_state.show_challenge_share_linkstoggle (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.markdownfor headers/instructions.st.columns(12)to render the 12Γ12 grid;st.containerfor grouping;st.sidebarfor secondary controls/help.st.expanderfor inline help/intel tips.
Widgets (interaction)
st.buttonfor each grid cell (144 total) with uniquekeyto 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; sidebarselectboxfor wordlist selection andSort Wordlistbutton (length+alpha).st.metricto show score.
Visualization
- Animated radar using matplotlib
FuncAnimation+PillowWritersaved to GIF. - Scope overlay image generated once and reused; metallic gradient background.
- Radar plot uses inverted Y so (0,0) is top-left.
- Animated radar using matplotlib
Control flow
- App reruns on interaction; uses
st.rerun()after state changes (reveal, guess);st.stop()after game over summary to freeze UI.
- App reruns on interaction; uses
Folder Structure
app.pyβ Streamlit entry pointbattlewords/β Python package__init__.pymodels.pyβ data models and typesword_loader.pyβ load/validate/cached word lists (usesbattlewords/words/wordlist.txtwith fallback)generator.pyβ word placement; imports fromword_loader; avoids duplicate wordslogic.pyβ game mechanics (reveal, guess, scoring, tiers)ui.pyβ Streamlit UI composition; animated radar; immediate rerender on reveal/guess viast.rerun(); inverted radar Ywords/wordlist.txtβ candidate words
specs/β documentation (this file andspecs.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.
- 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.
- 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_datato memoize loading/filtering. - Loader is centralized in
word_loader.pyand used by generator and UI.
Acceptance: Loading function returns lists by length with >= 25 words per length or fallback minimal lists.
- 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).
- 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.
- Click a covered cell to reveal; if the cell is part of a word, show the letter; else mark empty (CSS class
- Guess:
- Accept a guess only if
can_guessis 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=Falseand 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, callst.rerun().
- Accept a guess only if
- 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.
- 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.
- Title and brief instructions via
- Visuals:
- Covered vs revealed styles; revealed empty cells use CSS class
empty. - Completed word cells styled with
bw-cell-complete; cell tooltips show coordinates.
- Covered vs revealed styles; revealed empty cells use CSS class
Acceptance: Users can play end-to-end; radar shows exactly 6 pulses; reveal and guess update via rerun; completed words are visually distinct.
- Scoring Tiers
- After game ends, compute tier:
- Good: 34β37
- Great: 38β41
- Fantastic: 42+
- 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.
- Animated radar GIF with metallic gradient and scope overlay; session reuse via
- 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.
- Hit/Miss indicator derived from
- 0.1.10
- Game Mode selector (
standard,too easy); chaining allowed only instandard. - 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.
- Game Mode selector (
- 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 workerandmanifest.json - Add INSTALL_GUIDE.md for PWA install instructions
- No gameplay logic changes
- Add Progressive Web App (PWA) support with
Known Issues / TODO
- Word list selection bug: improper list fetched/propagated in some runs.
- Verify
get_wordlist_files()returns correct filenames andselected_wordlistpersists across_new_game(). - Ensure
load_word_list(selected_wordlist)loads the chosen file and matchesgenerate_puzzle(words_by_len=...)expected shape. - Add tests for selection, sorting, and fallback behavior.
- Verify
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.pywith:GameStorageclass for saving/loading game results and high scoresGameResultandHighScoreEntrydataclasses- JSON-based local storage in
~/.battlewords/data/
- Update
models.pyto includegame_idin Puzzle (based on word list) - Update
generator.pyto:- Generate game_id from sorted word list
- Accept optional
target_wordsparameter 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_idfrom 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_idin the shareable link. - When a user loads a game with a
game_idquery 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 toHF_REPO_IDgen_full_url(...)for shortener lookups/creation backed byshortener.json
- Created
battlewords/game_storage.pywrapper with functions:save_game_to_hf()- Save challenge and get short URLload_game_from_sid()- Load challenge from short IDadd_user_result_to_game()- Append user result to existing challengeget_shareable_url()- Generate shareable URLs
- Repository structure in HF dataset:
shortener.json- Short URL mappingsgames/{uid}/settings.json- Per-game challenge data with users array
- Required env vars (.env):
HF_API_TOKEN(orHF_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_hubandpython-dotenvto requirements - Module imports in
ui.py:30
E) Acceptance Criteria β
- β
Completed game produces working share link with
game_idsid - β 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.pywith:GameResultandHighScoreEntrydataclasses- 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/)