Spaces:
Running
Running
0.2.17 (claude code)
Browse files- documentation updates and corrections
- updated CLAUDE.md with accurate feature status and project structure
- clarified v0.3.0 planned features vs current implementation
- README.md +53 -0
- battlewords/__init__.py +1 -1
- battlewords/storage.py +182 -0
- claude.md +146 -30
- specs/requirements.md +45 -84
- specs/specs.md +13 -0
README.md
CHANGED
|
@@ -93,6 +93,7 @@ docker run -p 8501:8501 battlewords
|
|
| 93 |
- `generator.py` β word placement logic
|
| 94 |
- `logic.py` β game mechanics (reveal, guess, scoring)
|
| 95 |
- `ui.py` β Streamlit UI composition
|
|
|
|
| 96 |
- `words/wordlist.txt` β candidate words
|
| 97 |
- `specs/` β documentation (`specs.md`, `requirements.md`)
|
| 98 |
- `tests/` β unit tests
|
|
@@ -106,6 +107,11 @@ docker run -p 8501:8501 battlewords
|
|
| 106 |
|
| 107 |
## Changelog
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
-0.2.16
|
| 110 |
- replace question marks in score panel with underscores
|
| 111 |
- add option to toggle incorrect guess history display in settings (enabled by default)
|
|
@@ -358,3 +364,50 @@ For any issues or enhancements, please refer to the project documentation or con
|
|
| 358 |
|
| 359 |
Happy gaming and sound designing!
|
| 360 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
- `generator.py` β word placement logic
|
| 94 |
- `logic.py` β game mechanics (reveal, guess, scoring)
|
| 95 |
- `ui.py` β Streamlit UI composition
|
| 96 |
+
- `storage.py` β **NEW**: persistent storage and high scores
|
| 97 |
- `words/wordlist.txt` β candidate words
|
| 98 |
- `specs/` β documentation (`specs.md`, `requirements.md`)
|
| 99 |
- `tests/` β unit tests
|
|
|
|
| 107 |
|
| 108 |
## Changelog
|
| 109 |
|
| 110 |
+
-0.2.17
|
| 111 |
+
- documentation updates and corrections
|
| 112 |
+
- updated CLAUDE.md with accurate feature status and project structure
|
| 113 |
+
- clarified v0.3.0 planned features vs current implementation
|
| 114 |
+
|
| 115 |
-0.2.16
|
| 116 |
- replace question marks in score panel with underscores
|
| 117 |
- add option to toggle incorrect guess history display in settings (enabled by default)
|
|
|
|
| 364 |
|
| 365 |
Happy gaming and sound designing!
|
| 366 |
|
| 367 |
+
## What's New in v0.3.0
|
| 368 |
+
|
| 369 |
+
### Game Sharing π
|
| 370 |
+
- Each game gets a unique ID based on the word combination
|
| 371 |
+
- Share challenges with friends via URL (`?game_id=ABC123`)
|
| 372 |
+
- Friends get the same words but different positions
|
| 373 |
+
- Compare scores on the same puzzle!
|
| 374 |
+
|
| 375 |
+
### High Scores π
|
| 376 |
+
- Local high score tracking (stored in `~/.battlewords/data/`)
|
| 377 |
+
- Filter high scores by wordlist and game mode
|
| 378 |
+
- Top 10 leaderboard display
|
| 379 |
+
- Player names supported (optional)
|
| 380 |
+
|
| 381 |
+
### Persistent Storage πΎ
|
| 382 |
+
- All game results automatically saved locally
|
| 383 |
+
- View your personal statistics
|
| 384 |
+
- No account needed - everything stored on your device
|
| 385 |
+
|
| 386 |
+
## Folder Structure
|
| 387 |
+
|
| 388 |
+
- `app.py` β Streamlit entry point
|
| 389 |
+
- `battlewords/` β Python package
|
| 390 |
+
- `models.py` β data models and types
|
| 391 |
+
- `word_loader.py` β word list loading and validation
|
| 392 |
+
- `generator.py` β word placement logic
|
| 393 |
+
- `logic.py` β game mechanics (reveal, guess, scoring)
|
| 394 |
+
- `ui.py` β Streamlit UI composition
|
| 395 |
+
- `storage.py` β **NEW**: persistent storage and high scores
|
| 396 |
+
- `words/wordlist.txt` β candidate words
|
| 397 |
+
- `specs/` β documentation (`specs.md`, `requirements.md`)
|
| 398 |
+
- `tests/` β unit tests
|
| 399 |
+
|
| 400 |
+
## Data Storage
|
| 401 |
+
|
| 402 |
+
**Privacy First**: All game data is stored locally on your device in `~/.battlewords/data/`. No data is sent to external servers. Player names are optional and default to "Anonymous".
|
| 403 |
+
|
| 404 |
+
**Storage Location**:
|
| 405 |
+
- Windows: `C:\Users\<username>\.battlewords\data\`
|
| 406 |
+
- Linux/Mac: `~/.battlewords/data/`
|
| 407 |
+
|
| 408 |
+
**Files**:
|
| 409 |
+
- `game_results.json` - All completed games
|
| 410 |
+
- `highscores.json` - Top 100 scores
|
| 411 |
+
|
| 412 |
+
You can delete these files at any time to reset your data.
|
| 413 |
+
|
battlewords/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
-
__version__ = "0.2.
|
| 2 |
__all__ = ["models", "generator", "logic", "ui"]
|
|
|
|
| 1 |
+
__version__ = "0.2.17"
|
| 2 |
__all__ = ["models", "generator", "logic", "ui"]
|
battlewords/storage.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// file: battlewords/storage.py
|
| 2 |
+
"""
|
| 3 |
+
Storage module for BattleWords game.
|
| 4 |
+
|
| 5 |
+
Provides functionality for:
|
| 6 |
+
1. Saving/loading game results to local JSON files
|
| 7 |
+
2. Managing high scores and leaderboards
|
| 8 |
+
3. Sharing game IDs via query strings
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
from dataclasses import dataclass, field, asdict
|
| 13 |
+
from typing import List, Dict, Optional, Any
|
| 14 |
+
from datetime import datetime
|
| 15 |
+
import json
|
| 16 |
+
import os
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
@dataclass
|
| 20 |
+
class GameResult:
|
| 21 |
+
game_id: str
|
| 22 |
+
wordlist: str
|
| 23 |
+
game_mode: str
|
| 24 |
+
score: int
|
| 25 |
+
tier: str
|
| 26 |
+
elapsed_seconds: int
|
| 27 |
+
words_found: List[str]
|
| 28 |
+
completed_at: str
|
| 29 |
+
player_name: Optional[str] = None
|
| 30 |
+
|
| 31 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 32 |
+
return asdict(self)
|
| 33 |
+
|
| 34 |
+
@classmethod
|
| 35 |
+
def from_dict(cls, data: Dict[str, Any]) -> "GameResult":
|
| 36 |
+
return cls(**data)
|
| 37 |
+
|
| 38 |
+
@dataclass
|
| 39 |
+
class HighScoreEntry:
|
| 40 |
+
player_name: str
|
| 41 |
+
score: int
|
| 42 |
+
tier: str
|
| 43 |
+
wordlist: str
|
| 44 |
+
game_mode: str
|
| 45 |
+
elapsed_seconds: int
|
| 46 |
+
completed_at: str
|
| 47 |
+
game_id: str
|
| 48 |
+
|
| 49 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 50 |
+
return asdict(self)
|
| 51 |
+
|
| 52 |
+
@classmethod
|
| 53 |
+
def from_dict(cls, data: Dict[str, Any]) -> "HighScoreEntry":
|
| 54 |
+
return cls(**data)
|
| 55 |
+
|
| 56 |
+
class GameStorage:
|
| 57 |
+
def __init__(self, storage_dir: Optional[str] = None):
|
| 58 |
+
if storage_dir is None:
|
| 59 |
+
storage_dir = os.path.join(
|
| 60 |
+
os.path.expanduser("~"),
|
| 61 |
+
".battlewords",
|
| 62 |
+
"data"
|
| 63 |
+
)
|
| 64 |
+
self.storage_dir = Path(storage_dir)
|
| 65 |
+
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
| 66 |
+
self.results_file = self.storage_dir / "game_results.json"
|
| 67 |
+
self.highscores_file = self.storage_dir / "highscores.json"
|
| 68 |
+
|
| 69 |
+
def save_result(self, result: GameResult) -> bool:
|
| 70 |
+
try:
|
| 71 |
+
results = self.load_all_results()
|
| 72 |
+
results.append(result.to_dict())
|
| 73 |
+
with open(self.results_file, 'w', encoding='utf-8') as f:
|
| 74 |
+
json.dump(results, f, indent=2, ensure_ascii=False)
|
| 75 |
+
self._update_highscores(result)
|
| 76 |
+
return True
|
| 77 |
+
except Exception as e:
|
| 78 |
+
print(f"Error saving result: {e}")
|
| 79 |
+
return False
|
| 80 |
+
|
| 81 |
+
def load_all_results(self) -> List[Dict[str, Any]]:
|
| 82 |
+
if not self.results_file.exists():
|
| 83 |
+
return []
|
| 84 |
+
try:
|
| 85 |
+
with open(self.results_file, 'r', encoding='utf-8') as f:
|
| 86 |
+
return json.load(f)
|
| 87 |
+
except Exception as e:
|
| 88 |
+
print(f"Error loading results: {e}")
|
| 89 |
+
return []
|
| 90 |
+
|
| 91 |
+
def get_results_by_game_id(self, game_id: str) -> List[GameResult]:
|
| 92 |
+
all_results = self.load_all_results()
|
| 93 |
+
matching = [
|
| 94 |
+
GameResult.from_dict(r)
|
| 95 |
+
for r in all_results
|
| 96 |
+
if r.get("game_id") == game_id
|
| 97 |
+
]
|
| 98 |
+
return sorted(matching, key=lambda x: x.score, reverse=True)
|
| 99 |
+
|
| 100 |
+
def _update_highscores(self, result: GameResult) -> None:
|
| 101 |
+
highscores = self.load_highscores()
|
| 102 |
+
entry = HighScoreEntry(
|
| 103 |
+
player_name=result.player_name or "Anonymous",
|
| 104 |
+
score=result.score,
|
| 105 |
+
tier=result.tier,
|
| 106 |
+
wordlist=result.wordlist,
|
| 107 |
+
game_mode=result.game_mode,
|
| 108 |
+
elapsed_seconds=result.elapsed_seconds,
|
| 109 |
+
completed_at=result.completed_at,
|
| 110 |
+
game_id=result.game_id
|
| 111 |
+
)
|
| 112 |
+
highscores.append(entry.to_dict())
|
| 113 |
+
highscores.sort(key=lambda x: x["score"], reverse=True)
|
| 114 |
+
highscores = highscores[:100]
|
| 115 |
+
with open(self.highscores_file, 'w', encoding='utf-8') as f:
|
| 116 |
+
json.dump(highscores, f, indent=2, ensure_ascii=False)
|
| 117 |
+
|
| 118 |
+
def load_highscores(
|
| 119 |
+
self,
|
| 120 |
+
wordlist: Optional[str] = None,
|
| 121 |
+
game_mode: Optional[str] = None,
|
| 122 |
+
limit: int = 10
|
| 123 |
+
) -> List[HighScoreEntry]:
|
| 124 |
+
if not self.highscores_file.exists():
|
| 125 |
+
return []
|
| 126 |
+
try:
|
| 127 |
+
with open(self.highscores_file, 'r', encoding='utf-8') as f:
|
| 128 |
+
scores = json.load(f)
|
| 129 |
+
if wordlist:
|
| 130 |
+
scores = [s for s in scores if s.get("wordlist") == wordlist]
|
| 131 |
+
if game_mode:
|
| 132 |
+
scores = [s for s in scores if s.get("game_mode") == game_mode]
|
| 133 |
+
scores.sort(key=lambda x: x["score"], reverse=True)
|
| 134 |
+
return [HighScoreEntry.from_dict(s) for s in scores[:limit]]
|
| 135 |
+
except Exception as e:
|
| 136 |
+
print(f"Error loading highscores: {e}")
|
| 137 |
+
return []
|
| 138 |
+
|
| 139 |
+
def get_player_stats(self, player_name: str) -> Dict[str, Any]:
|
| 140 |
+
all_results = self.load_all_results()
|
| 141 |
+
player_results = [
|
| 142 |
+
GameResult.from_dict(r)
|
| 143 |
+
for r in all_results
|
| 144 |
+
if r.get("player_name") == player_name
|
| 145 |
+
]
|
| 146 |
+
if not player_results:
|
| 147 |
+
return {
|
| 148 |
+
"games_played": 0,
|
| 149 |
+
"total_score": 0,
|
| 150 |
+
"average_score": 0,
|
| 151 |
+
"best_score": 0,
|
| 152 |
+
"best_tier": None
|
| 153 |
+
}
|
| 154 |
+
scores = [r.score for r in player_results]
|
| 155 |
+
return {
|
| 156 |
+
"games_played": len(player_results),
|
| 157 |
+
"total_score": sum(scores),
|
| 158 |
+
"average_score": sum(scores) / len(scores),
|
| 159 |
+
"best_score": max(scores),
|
| 160 |
+
"best_tier": max(player_results, key=lambda x: x.score).tier,
|
| 161 |
+
"fastest_time": min(r.elapsed_seconds for r in player_results)
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
def generate_game_id_from_words(words: List[str]) -> str:
|
| 165 |
+
import hashlib
|
| 166 |
+
sorted_words = sorted([w.upper() for w in words])
|
| 167 |
+
word_string = "".join(sorted_words)
|
| 168 |
+
hash_obj = hashlib.sha256(word_string.encode('utf-8'))
|
| 169 |
+
return hash_obj.hexdigest()[:8].upper()
|
| 170 |
+
|
| 171 |
+
def parse_game_id_from_url() -> Optional[str]:
|
| 172 |
+
try:
|
| 173 |
+
import streamlit as st
|
| 174 |
+
params = st.query_params
|
| 175 |
+
return params.get("game_id")
|
| 176 |
+
except Exception:
|
| 177 |
+
return None
|
| 178 |
+
|
| 179 |
+
def create_shareable_url(game_id: str, base_url: Optional[str] = None) -> str:
|
| 180 |
+
if base_url is None:
|
| 181 |
+
base_url = "https://huggingface.co/spaces/Surn/BattleWords"
|
| 182 |
+
return f"{base_url}?game_id={game_id}"
|
claude.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
| 3 |
## Project Overview
|
| 4 |
BattleWords is a vocabulary learning game inspired by Battleship mechanics, built with Streamlit and Python 3.12. Players reveal cells on a 12x12 grid to discover hidden words and earn points for strategic guessing.
|
| 5 |
|
| 6 |
-
**Current Version:** 0.2.
|
|
|
|
| 7 |
**Repository:** https://github.com/Oncorporation/BattleWords.git
|
| 8 |
**Live Demo:** https://huggingface.co/spaces/Surn/BattleWords
|
| 9 |
|
|
@@ -14,6 +15,10 @@ BattleWords is a vocabulary learning game inspired by Battleship mechanics, buil
|
|
| 14 |
- After revealing a letter, players can guess words
|
| 15 |
- Scoring: word length + bonus for unrevealed letters
|
| 16 |
- Game ends when all words are guessed or all word letters revealed
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
### Scoring Tiers
|
| 19 |
- **Fantastic:** 42+ points
|
|
@@ -28,6 +33,7 @@ BattleWords is a vocabulary learning game inspired by Battleship mechanics, buil
|
|
| 28 |
- **Language:** Python 3.12.8
|
| 29 |
- **Visualization:** Matplotlib, NumPy
|
| 30 |
- **Data Processing:** Pandas, Altair
|
|
|
|
| 31 |
- **Testing:** Pytest
|
| 32 |
- **Code Quality:** Flake8, MyPy
|
| 33 |
- **Package Manager:** UV (modern Python package manager)
|
|
@@ -37,24 +43,32 @@ BattleWords is a vocabulary learning game inspired by Battleship mechanics, buil
|
|
| 37 |
battlewords/
|
| 38 |
βββ app.py # Streamlit entry point
|
| 39 |
βββ battlewords/ # Main package
|
| 40 |
-
β βββ __init__.py # Version: 0.2.
|
| 41 |
β βββ models.py # Data models (Coord, Word, Puzzle, GameState)
|
| 42 |
β βββ generator.py # Puzzle generation with deterministic seeding
|
| 43 |
β βββ logic.py # Game mechanics (reveal, guess, scoring)
|
| 44 |
-
β βββ ui.py # Streamlit UI (~
|
| 45 |
β βββ word_loader.py # Word list management
|
| 46 |
β βββ audio.py # Background music system
|
|
|
|
|
|
|
|
|
|
| 47 |
β βββ version_info.py # Version display
|
| 48 |
β βββ words/ # Word list files
|
| 49 |
β βββ classic.txt # Default word list
|
| 50 |
-
β
|
|
|
|
| 51 |
βββ tests/ # Unit tests
|
| 52 |
βββ specs/ # Documentation
|
|
|
|
|
|
|
|
|
|
| 53 |
βββ .env # Environment variables
|
| 54 |
βββ pyproject.toml # Project metadata
|
| 55 |
βββ requirements.txt # Dependencies
|
| 56 |
βββ uv.lock # UV lock file
|
| 57 |
-
|
|
|
|
| 58 |
```
|
| 59 |
|
| 60 |
## Key Features
|
|
@@ -63,6 +77,31 @@ battlewords/
|
|
| 63 |
1. **Classic Mode:** Allows consecutive guessing after correct answers
|
| 64 |
2. **Too Easy Mode:** Single guess per reveal
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
### Puzzle Generation
|
| 67 |
- Deterministic seeding support for reproducible puzzles
|
| 68 |
- Configurable word spacing (spacer: 0-2)
|
|
@@ -70,25 +109,55 @@ battlewords/
|
|
| 70 |
- 1: At least 1 blank cell between words (default)
|
| 71 |
- 2: At least 2 blank cells between words
|
| 72 |
- Validation ensures no overlaps, proper bounds, correct word distribution
|
|
|
|
| 73 |
|
| 74 |
-
### UI Components
|
| 75 |
- **Radar Visualization:** Animated matplotlib GIF showing word boundaries
|
| 76 |
- Displays pulsing rings at last letter of each word
|
| 77 |
- Hides rings for guessed words
|
| 78 |
- Three-layer composition: gradient background, scope image, animated rings
|
| 79 |
- Cached per-puzzle with signature matching
|
| 80 |
-
- **Game Grid:** Interactive 12x12 button grid
|
| 81 |
-
- **Score Panel:** Real-time scoring with client-side timer
|
| 82 |
-
- **Settings Sidebar:**
|
| 83 |
-
-
|
| 84 |
-
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
- Added `fig.subplots_adjust(left=0, right=0.9, top=0.9, bottom=0)`
|
| 89 |
- Set `fig.patch.set_alpha(0.0)` for transparent background
|
| 90 |
- Maintains 2% margin for tick visibility while ensuring consistent layer alignment
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
## Data Models
|
| 93 |
|
| 94 |
### Core Classes
|
|
@@ -152,39 +221,83 @@ docker run -p 8501:8501 battlewords
|
|
| 152 |
pytest tests/
|
| 153 |
```
|
| 154 |
|
| 155 |
-
##
|
| 156 |
-
**Branch:** cc-01
|
| 157 |
-
**Purpose:**
|
|
|
|
| 158 |
|
| 159 |
-
###
|
| 160 |
-
- **
|
| 161 |
-
- **
|
| 162 |
|
| 163 |
## Known Issues
|
| 164 |
- Word list loading bug: App may not select proper word lists in some environments
|
| 165 |
- Investigation needed in `word_loader.get_wordlist_files()` and `load_word_list()`
|
| 166 |
- Sidebar selection persistence needs verification
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
## Future Roadmap
|
| 169 |
|
| 170 |
-
###
|
| 171 |
-
-
|
| 172 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
- Keyboard navigation and guessing
|
| 174 |
-
- Deterministic seed UI
|
|
|
|
| 175 |
|
| 176 |
-
### Full (
|
| 177 |
-
-
|
| 178 |
-
-
|
| 179 |
-
-
|
| 180 |
-
-
|
| 181 |
-
-
|
|
|
|
| 182 |
|
| 183 |
## Deployment Targets
|
| 184 |
- **Hugging Face Spaces:** Primary deployment platform
|
| 185 |
- **Docker:** Containerized deployment for any platform
|
| 186 |
- **Local:** Development and testing
|
| 187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
## Notes for Claude
|
| 189 |
- Project uses modern Python features (3.12+)
|
| 190 |
- Heavy use of Streamlit session state for game state management
|
|
@@ -193,3 +306,6 @@ pytest tests/
|
|
| 193 |
- CSS heavily customized for game aesthetics
|
| 194 |
- All file paths should be absolute when working in WSL environment
|
| 195 |
- Current working directory: `/mnt/d/Projects/Battlewords`
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
## Project Overview
|
| 4 |
BattleWords is a vocabulary learning game inspired by Battleship mechanics, built with Streamlit and Python 3.12. Players reveal cells on a 12x12 grid to discover hidden words and earn points for strategic guessing.
|
| 5 |
|
| 6 |
+
**Current Version:** 0.2.17 (Stable)
|
| 7 |
+
**Next Version:** 0.3.0 (In Development - Storage & Sharing Features)
|
| 8 |
**Repository:** https://github.com/Oncorporation/BattleWords.git
|
| 9 |
**Live Demo:** https://huggingface.co/spaces/Surn/BattleWords
|
| 10 |
|
|
|
|
| 15 |
- After revealing a letter, players can guess words
|
| 16 |
- Scoring: word length + bonus for unrevealed letters
|
| 17 |
- Game ends when all words are guessed or all word letters revealed
|
| 18 |
+
- Incorrect guess history with optional display (enabled by default)
|
| 19 |
+
- 10 incorrect guess limit per game
|
| 20 |
+
- **PLANNED (v0.3.0):** Game sharing via unique game IDs
|
| 21 |
+
- **PLANNED (v0.3.0):** Local persistent storage for results and high scores
|
| 22 |
|
| 23 |
### Scoring Tiers
|
| 24 |
- **Fantastic:** 42+ points
|
|
|
|
| 33 |
- **Language:** Python 3.12.8
|
| 34 |
- **Visualization:** Matplotlib, NumPy
|
| 35 |
- **Data Processing:** Pandas, Altair
|
| 36 |
+
- **Storage:** JSON-based local persistence
|
| 37 |
- **Testing:** Pytest
|
| 38 |
- **Code Quality:** Flake8, MyPy
|
| 39 |
- **Package Manager:** UV (modern Python package manager)
|
|
|
|
| 43 |
battlewords/
|
| 44 |
βββ app.py # Streamlit entry point
|
| 45 |
βββ battlewords/ # Main package
|
| 46 |
+
β βββ __init__.py # Version: 0.2.17
|
| 47 |
β βββ models.py # Data models (Coord, Word, Puzzle, GameState)
|
| 48 |
β βββ generator.py # Puzzle generation with deterministic seeding
|
| 49 |
β βββ logic.py # Game mechanics (reveal, guess, scoring)
|
| 50 |
+
β βββ ui.py # Streamlit UI (~1800 lines)
|
| 51 |
β βββ word_loader.py # Word list management
|
| 52 |
β βββ audio.py # Background music system
|
| 53 |
+
β βββ sounds.py # Sound effects management
|
| 54 |
+
β βββ generate_sounds.py # Sound generation utilities
|
| 55 |
+
β βββ storage.py # Storage module (v0.3.0 - IN DEVELOPMENT)
|
| 56 |
β βββ version_info.py # Version display
|
| 57 |
β βββ words/ # Word list files
|
| 58 |
β βββ classic.txt # Default word list
|
| 59 |
+
β βββ fourth_grade.txt # Elementary word list
|
| 60 |
+
β βββ wordlist.txt # Full word list
|
| 61 |
βββ tests/ # Unit tests
|
| 62 |
βββ specs/ # Documentation
|
| 63 |
+
β βββ specs.md # Game specifications
|
| 64 |
+
β βββ requirements.md # Implementation requirements
|
| 65 |
+
β βββ history.md # Game history
|
| 66 |
βββ .env # Environment variables
|
| 67 |
βββ pyproject.toml # Project metadata
|
| 68 |
βββ requirements.txt # Dependencies
|
| 69 |
βββ uv.lock # UV lock file
|
| 70 |
+
βββ Dockerfile # Container deployment
|
| 71 |
+
βββ CLAUDE.md # This file - project context for Claude
|
| 72 |
```
|
| 73 |
|
| 74 |
## Key Features
|
|
|
|
| 77 |
1. **Classic Mode:** Allows consecutive guessing after correct answers
|
| 78 |
2. **Too Easy Mode:** Single guess per reveal
|
| 79 |
|
| 80 |
+
### Audio & Visual Effects
|
| 81 |
+
- **Background Music:** Toggleable ocean-themed background music with volume control
|
| 82 |
+
- **Sound Effects:** Hit/miss/correct/incorrect guess sounds with volume control
|
| 83 |
+
- **Animated Radar:** Pulsing rings showing word boundaries (last letter locations)
|
| 84 |
+
- **Ocean Theme:** Gradient animated background with wave effects
|
| 85 |
+
- **Incorrect Guess History:** Visual display of wrong guesses (toggleable in settings)
|
| 86 |
+
|
| 87 |
+
### PLANNED: Storage & Sharing (v0.3.0)
|
| 88 |
+
- **Game ID System:** 8-character deterministic IDs based on word combinations
|
| 89 |
+
- Format: `?game_id=ABC123` in URL
|
| 90 |
+
- Same words, different positions for each replay
|
| 91 |
+
- Enables fair challenges between players
|
| 92 |
+
- **Local Storage:**
|
| 93 |
+
- Location: `~/.battlewords/data/`
|
| 94 |
+
- Files: `game_results.json`, `highscores.json`
|
| 95 |
+
- Privacy-first: no cloud dependency
|
| 96 |
+
- **High Scores:**
|
| 97 |
+
- Top 100 scores tracked automatically
|
| 98 |
+
- Filterable by wordlist and game mode
|
| 99 |
+
- Optional player names (defaults to "Anonymous")
|
| 100 |
+
- **Player Statistics:**
|
| 101 |
+
- Games played, average score, best score
|
| 102 |
+
- Fastest completion time
|
| 103 |
+
- Per-player history
|
| 104 |
+
|
| 105 |
### Puzzle Generation
|
| 106 |
- Deterministic seeding support for reproducible puzzles
|
| 107 |
- Configurable word spacing (spacer: 0-2)
|
|
|
|
| 109 |
- 1: At least 1 blank cell between words (default)
|
| 110 |
- 2: At least 2 blank cells between words
|
| 111 |
- Validation ensures no overlaps, proper bounds, correct word distribution
|
| 112 |
+
- **v0.3.0:** Game ID system planned (not yet integrated into Puzzle model)
|
| 113 |
|
| 114 |
+
### UI Components (Current)
|
| 115 |
- **Radar Visualization:** Animated matplotlib GIF showing word boundaries
|
| 116 |
- Displays pulsing rings at last letter of each word
|
| 117 |
- Hides rings for guessed words
|
| 118 |
- Three-layer composition: gradient background, scope image, animated rings
|
| 119 |
- Cached per-puzzle with signature matching
|
| 120 |
+
- **Game Grid:** Interactive 12x12 button grid with responsive layout
|
| 121 |
+
- **Score Panel:** Real-time scoring with client-side JavaScript timer
|
| 122 |
+
- **Settings Sidebar:**
|
| 123 |
+
- Word list picker (classic, fourth_grade, wordlist)
|
| 124 |
+
- Game mode selector
|
| 125 |
+
- Word spacing configuration (0-2)
|
| 126 |
+
- Audio volume controls (music and effects separate)
|
| 127 |
+
- Toggle for incorrect guess history display
|
| 128 |
+
- **Theme System:** Ocean gradient background with CSS animations
|
| 129 |
+
- **Game Over Dialog:** Final score display with tier ranking
|
| 130 |
+
- **Incorrect Guess Display:** Shows history of wrong guesses with count
|
| 131 |
+
- **PLANNED (v0.3.0):** High scores expander in sidebar
|
| 132 |
+
- **PLANNED (v0.3.0):** Share challenge button in game over dialog
|
| 133 |
+
- **PLANNED (v0.3.0):** Game ID display during gameplay
|
| 134 |
+
|
| 135 |
+
### Recent Changes & Branch Status
|
| 136 |
+
**Branch:** cc-01 (Storage and sharing features - v0.3.0 development)
|
| 137 |
+
|
| 138 |
+
**Latest (v0.2.17):**
|
| 139 |
+
- Documentation updates and corrections
|
| 140 |
+
- Updated CLAUDE.md with accurate feature status
|
| 141 |
+
- Clarified v0.3.0 planned features vs current implementation
|
| 142 |
+
- Added comprehensive project structure details
|
| 143 |
+
- Improved version tracking and roadmap clarity
|
| 144 |
+
|
| 145 |
+
**Previously Fixed (v0.2.16):**
|
| 146 |
+
- Replace question marks with underscores in score panel
|
| 147 |
+
- Add toggle for incorrect guess history display (enabled by default)
|
| 148 |
+
- Game over popup positioning improvements
|
| 149 |
+
- Music playback after game end
|
| 150 |
+
- Sound effect and music volume issues
|
| 151 |
+
- Radar alignment inconsistencies
|
| 152 |
- Added `fig.subplots_adjust(left=0, right=0.9, top=0.9, bottom=0)`
|
| 153 |
- Set `fig.patch.set_alpha(0.0)` for transparent background
|
| 154 |
- Maintains 2% margin for tick visibility while ensuring consistent layer alignment
|
| 155 |
|
| 156 |
+
**In Progress (v0.3.0 on cc-01):**
|
| 157 |
+
- Storage module implementation (storage.py created)
|
| 158 |
+
- Game ID system for sharing
|
| 159 |
+
- High score tracking infrastructure
|
| 160 |
+
|
| 161 |
## Data Models
|
| 162 |
|
| 163 |
### Core Classes
|
|
|
|
| 221 |
pytest tests/
|
| 222 |
```
|
| 223 |
|
| 224 |
+
## Git Configuration & Deployment
|
| 225 |
+
**Current Branch:** cc-01
|
| 226 |
+
**Purpose:** Storage and sharing features (v0.3.0 development)
|
| 227 |
+
**Main Branch:** main (not specified in git config, but typical convention)
|
| 228 |
|
| 229 |
+
### Remotes
|
| 230 |
+
- **ONCORP (origin):** https://github.com/Oncorporation/BattleWords.git (main repository)
|
| 231 |
+
- **Hugging:** https://huggingface.co/spaces/Surn/BattleWords (live deployment)
|
| 232 |
|
| 233 |
## Known Issues
|
| 234 |
- Word list loading bug: App may not select proper word lists in some environments
|
| 235 |
- Investigation needed in `word_loader.get_wordlist_files()` and `load_word_list()`
|
| 236 |
- Sidebar selection persistence needs verification
|
| 237 |
|
| 238 |
+
## v0.3.0 Development Status (cc-01 branch)
|
| 239 |
+
|
| 240 |
+
### Completed β
|
| 241 |
+
- `battlewords/storage.py` module created with:
|
| 242 |
+
- `GameStorage` class for JSON-based local storage
|
| 243 |
+
- `GameResult` and `HighScoreEntry` dataclasses
|
| 244 |
+
- Functions: `generate_game_id_from_words()`, `parse_game_id_from_url()`, `create_shareable_url()`
|
| 245 |
+
- Storage location: `~/.battlewords/data/` (game_results.json, highscores.json)
|
| 246 |
+
- Documentation updated in specs/ folder
|
| 247 |
+
|
| 248 |
+
### In Progress β³
|
| 249 |
+
- Puzzle model integration for game_id field
|
| 250 |
+
- Generator updates for `target_words` parameter (replay from game_id)
|
| 251 |
+
- UI integration:
|
| 252 |
+
- Storage calls on game completion
|
| 253 |
+
- High score display in sidebar
|
| 254 |
+
- Share button in game over dialog
|
| 255 |
+
- Query parameter parsing for game_id
|
| 256 |
+
- Player name input in sidebar
|
| 257 |
+
|
| 258 |
+
### Planned π
|
| 259 |
+
- Unit tests for storage module
|
| 260 |
+
- Integration tests for complete storage flow
|
| 261 |
+
- Game replay from shared ID functionality
|
| 262 |
+
- Player statistics display
|
| 263 |
+
- Share results text generation
|
| 264 |
+
|
| 265 |
## Future Roadmap
|
| 266 |
|
| 267 |
+
### Phase 1.5 (v0.3.0) - Current Focus β³
|
| 268 |
+
- β
Storage module with local JSON persistence (backend complete)
|
| 269 |
+
- β
Game ID generation system (backend complete)
|
| 270 |
+
- β³ High score tracking and display (backend complete, UI pending)
|
| 271 |
+
- β³ Share challenge functionality (UI integration pending)
|
| 272 |
+
- β³ Game replay from shared IDs (generator updates needed)
|
| 273 |
+
- β³ Player name input and statistics
|
| 274 |
+
|
| 275 |
+
### Beta (v0.5.0)
|
| 276 |
+
- Word overlaps on shared letters (crossword-style gameplay)
|
| 277 |
+
- Enhanced responsive layout for mobile/tablet
|
| 278 |
- Keyboard navigation and guessing
|
| 279 |
+
- Deterministic seed UI for custom puzzles
|
| 280 |
+
- Improved accessibility features
|
| 281 |
|
| 282 |
+
### Full (v1.0.0)
|
| 283 |
+
- Optional cloud storage backend (FastAPI)
|
| 284 |
+
- Daily puzzle mode with global leaderboards
|
| 285 |
+
- Practice mode with hints
|
| 286 |
+
- Enhanced UX features (animations, themes)
|
| 287 |
+
- Multiple difficulty levels
|
| 288 |
+
- Internationalization (i18n) support
|
| 289 |
|
| 290 |
## Deployment Targets
|
| 291 |
- **Hugging Face Spaces:** Primary deployment platform
|
| 292 |
- **Docker:** Containerized deployment for any platform
|
| 293 |
- **Local:** Development and testing
|
| 294 |
|
| 295 |
+
### Privacy & Data
|
| 296 |
+
- All storage is local (no telemetry)
|
| 297 |
+
- Player names optional
|
| 298 |
+
- No data leaves user's machine
|
| 299 |
+
- Easy to delete: just remove `~/.battlewords/data/`
|
| 300 |
+
|
| 301 |
## Notes for Claude
|
| 302 |
- Project uses modern Python features (3.12+)
|
| 303 |
- Heavy use of Streamlit session state for game state management
|
|
|
|
| 306 |
- CSS heavily customized for game aesthetics
|
| 307 |
- All file paths should be absolute when working in WSL environment
|
| 308 |
- Current working directory: `/mnt/d/Projects/Battlewords`
|
| 309 |
+
- Storage features are backward-compatible (game works without storage)
|
| 310 |
+
- Game IDs are deterministic for consistent sharing
|
| 311 |
+
- JSON storage chosen for simplicity and privacy
|
specs/requirements.md
CHANGED
|
@@ -136,94 +136,55 @@ Known Issues / TODO
|
|
| 136 |
- Ensure `load_word_list(selected_wordlist)` loads the chosen file and matches `generate_puzzle(words_by_len=...)` expected shape.
|
| 137 |
- Add tests for selection, sorting, and fallback behavior.
|
| 138 |
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
-
|
| 144 |
-
-
|
| 145 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
Acceptance:
|
| 147 |
-
-
|
| 148 |
-
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
-
|
| 153 |
-
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
-
|
| 163 |
-
-
|
| 164 |
-
-
|
| 165 |
-
|
| 166 |
-
B) Game Content and Generation
|
| 167 |
-
- Curated word lists by difficulty; exclude obscure/abusive words.
|
| 168 |
-
- Deterministic seed support to reproduce puzzles (e.g., daily seed based on date).
|
| 169 |
-
- Validation pass to ensure no unintended partial words formed adjacently (content curation rule, optional).
|
| 170 |
-
- Optional generator diagnostics panel for QA using `st.expander` and `st.dataframe`.
|
| 171 |
-
- Streamlit: `st.cache_data` for word lists; `st.cache_resource` if needed for heavier resources.
|
| 172 |
-
|
| 173 |
-
C) Game Modes and Settings
|
| 174 |
-
- Daily Puzzle mode (same seed for all players per day).
|
| 175 |
-
- Practice mode (random puzzles).
|
| 176 |
-
- Difficulty presets that tweak word selection (common vs. rare) but still keep 2Γ4, 2Γ5, 2Γ6.
|
| 177 |
-
- Optional hint system with limited uses (e.g., reveal a random letter in an unguessed word) with score penalty.
|
| 178 |
-
- Streamlit: mode selection via `st.radio`/`st.segmented_control`; settings via `st.sidebar` with `st.toggle`/`st.slider`.
|
| 179 |
-
|
| 180 |
-
D) Persistence and Profiles
|
| 181 |
-
- Save/Load local game state (browser cookie or Streamlit session + query params).
|
| 182 |
-
- Cloud persistence via lightweight backend API (FastAPI) or Streamlit secrets + hosted storage for:
|
| 183 |
-
- User profiles (username only), completed puzzles, scores.
|
| 184 |
-
- Leaderboards for Daily mode.
|
| 185 |
-
- Privacy notice and opt-in for storing data.
|
| 186 |
-
- Streamlit: `st.text_input` for username; `st.button` to save; call backend via `requests`.
|
| 187 |
-
|
| 188 |
-
E) Leaderboards and Sharing
|
| 189 |
-
- Global and friends leaderboards (score and completion time if captured; note: game is strategy-first, time is optional).
|
| 190 |
-
- Share result string with spoiler-free grid (similar to popular word games).
|
| 191 |
-
- Streamlit: `st.table`/`st.dataframe` for leaderboards; `st.download_button` or copy-to-clipboard via `st.text_area` + `st.button`.
|
| 192 |
-
|
| 193 |
-
F) Observability and Quality
|
| 194 |
-
- Logging for generator failures and gameplay events (anonymized).
|
| 195 |
-
- Error boundary UI with recover/retry.
|
| 196 |
-
- Test suite:
|
| 197 |
-
- Unit: generator, logic, scoring, gating, radar.
|
| 198 |
-
- Property-based tests for generator (e.g., Hypothesis) to stress placement constraints.
|
| 199 |
-
- Integration tests that simulate a full game and validate scoring.
|
| 200 |
-
- Visual regression snapshots of grid/radar (optional).
|
| 201 |
-
- CI/CD with linting (flake8/ruff), type checks (mypy/pyright), tests, and build.
|
| 202 |
-
- Streamlit: developer toggles in an `st.expander` to simulate states; optional `st.fragment` to limit rerenders in hotspots.
|
| 203 |
-
|
| 204 |
-
G) Performance
|
| 205 |
-
- Optimize generator to avoid excessive retries (precompute candidate slots, shuffle deterministically).
|
| 206 |
-
- Memoize derived UI state.
|
| 207 |
-
- Efficient grid rendering (batch updates or delta rendering where possible in Streamlit).
|
| 208 |
-
- Streamlit: consider `st.fragment` for the grid/radar to avoid full-page rerenders.
|
| 209 |
-
|
| 210 |
-
H) Internationalization (Optional)
|
| 211 |
-
- i18n-ready strings; language toggle.
|
| 212 |
-
- Locale-specific word lists.
|
| 213 |
-
- Streamlit: language toggle via `st.selectbox`.
|
| 214 |
-
|
| 215 |
-
I) Security and Abuse Prevention
|
| 216 |
-
- Validate all inputs (guess strings AβZ only).
|
| 217 |
-
- Rate-limit backend endpoints (if any) and sanitize stored data.
|
| 218 |
-
- Streamlit: enforce validation in the submit handler and sanitize displayed content with strict formatting.
|
| 219 |
-
|
| 220 |
-
J) Deployment
|
| 221 |
-
- Streamlit Community Cloud or containerized deployment (Dockerfile) to any cloud.
|
| 222 |
-
- Environment configuration via `.env` or Streamlit secrets.
|
| 223 |
-
- Streamlit: use `st.secrets` for API keys.
|
| 224 |
|
| 225 |
Milestones and Estimates (High-level)
|
| 226 |
- Phase 1 (POC): 2β4 days
|
|
|
|
| 227 |
- Beta (0.5.0): 3β5 days (overlaps, responsive UI, keyboard, deterministic seed)
|
| 228 |
- Phase 2 (Full): 1β2 weeks depending on features selected
|
| 229 |
|
|
|
|
| 136 |
- Ensure `load_word_list(selected_wordlist)` loads the chosen file and matches `generate_puzzle(words_by_len=...)` expected shape.
|
| 137 |
- Add tests for selection, sorting, and fallback behavior.
|
| 138 |
|
| 139 |
+
Phase 1.5: Storage and Sharing (0.3.0) - NEW
|
| 140 |
+
Goal: Add persistent storage, high scores, and game sharing capabilities.
|
| 141 |
+
|
| 142 |
+
A) Storage Module
|
| 143 |
+
- Create `battlewords/storage.py` with:
|
| 144 |
+
- `GameStorage` class for saving/loading game results and high scores
|
| 145 |
+
- `GameResult` and `HighScoreEntry` dataclasses
|
| 146 |
+
- JSON-based local storage in `~/.battlewords/data/`
|
| 147 |
+
- Update `models.py` to include `game_id` in Puzzle (based on word list)
|
| 148 |
+
- Update `generator.py` to:
|
| 149 |
+
- Generate game_id from sorted word list
|
| 150 |
+
- Accept optional `target_words` parameter for replay
|
| 151 |
+
- Integrate storage into game flow:
|
| 152 |
+
- Save result on game completion
|
| 153 |
+
- Display high scores in sidebar
|
| 154 |
+
Acceptance:
|
| 155 |
+
- Game results saved to local JSON files
|
| 156 |
+
- High scores correctly filtered and sorted
|
| 157 |
+
- Game IDs generated deterministically
|
| 158 |
+
|
| 159 |
+
B) Game Sharing
|
| 160 |
+
- Parse `game_id` from query params (`?game_id=ABC123`)
|
| 161 |
+
- Generate puzzle from game_id (same words, different positions)
|
| 162 |
+
- "Share Challenge" button creates shareable URL
|
| 163 |
+
- Display game_id in UI to show shared challenges
|
| 164 |
Acceptance:
|
| 165 |
+
- URL with game_id loads same word set
|
| 166 |
+
- Share button generates correct URL
|
| 167 |
+
- Game ID visible to players
|
| 168 |
+
|
| 169 |
+
C) High Score Display
|
| 170 |
+
- Sidebar expander for high scores
|
| 171 |
+
- Filter by: All-time, Current Wordlist, Current Mode
|
| 172 |
+
- Top 10 entries with: Rank, Player, Score, Tier, Time
|
| 173 |
+
- Player name input (optional, defaults to "Anonymous")
|
| 174 |
+
Acceptance:
|
| 175 |
+
- High scores display correctly
|
| 176 |
+
- Filters work as expected
|
| 177 |
+
- Player names saved with results
|
| 178 |
+
|
| 179 |
+
D) Tests
|
| 180 |
+
- Unit tests for storage operations
|
| 181 |
+
- Test game ID generation consistency
|
| 182 |
+
- Test save/load result flow
|
| 183 |
+
- Test high score filtering and ranking
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
Milestones and Estimates (High-level)
|
| 186 |
- Phase 1 (POC): 2β4 days
|
| 187 |
+
- Phase 1.5 (Storage & Sharing): 2β3 days β³ IN PROGRESS
|
| 188 |
- Beta (0.5.0): 3β5 days (overlaps, responsive UI, keyboard, deterministic seed)
|
| 189 |
- Phase 2 (Full): 1β2 weeks depending on features selected
|
| 190 |
|
specs/specs.md
CHANGED
|
@@ -53,12 +53,25 @@ Battlewords is inspired by the classic Battleship game, but uses words instead o
|
|
| 53 |
- Persistence, leaderboards, and additional modes as specified in requirements.
|
| 54 |
- Deterministic daily mode and practice mode supported.
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
## UI Elements
|
| 57 |
- 12x12 grid
|
| 58 |
- Radar screen (shows last letter locations); y-axis inverted so (0,0) is top-left
|
| 59 |
- Text box for word guesses
|
| 60 |
- Score display (shows word, base points, bonus points, total score)
|
| 61 |
- Guess status indicator (Correct/Try Again)
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
## Word List
|
| 64 |
- External list at `battlewords/words/wordlist.txt`.
|
|
|
|
| 53 |
- Persistence, leaderboards, and additional modes as specified in requirements.
|
| 54 |
- Deterministic daily mode and practice mode supported.
|
| 55 |
|
| 56 |
+
## New Features (v0.3.0)
|
| 57 |
+
- **Game ID Sharing:** Each puzzle generates a deterministic game ID based on the word list. Players can share URLs with `?game_id=ABC123` to challenge others with the same words.
|
| 58 |
+
- **Persistent Storage:** Game results and high scores are saved locally in `~/.battlewords/data/`.
|
| 59 |
+
- **High Scores:** Top scores are tracked and displayed in the sidebar, filterable by wordlist and game mode.
|
| 60 |
+
- **Player Name:** Optional player name is saved with results.
|
| 61 |
+
|
| 62 |
+
## Storage
|
| 63 |
+
- Game results and high scores are stored in JSON files for privacy and offline access.
|
| 64 |
+
- Game ID is generated from the sorted word list for replay/sharing.
|
| 65 |
+
|
| 66 |
## UI Elements
|
| 67 |
- 12x12 grid
|
| 68 |
- Radar screen (shows last letter locations); y-axis inverted so (0,0) is top-left
|
| 69 |
- Text box for word guesses
|
| 70 |
- Score display (shows word, base points, bonus points, total score)
|
| 71 |
- Guess status indicator (Correct/Try Again)
|
| 72 |
+
- Game ID display and share button in game over dialog.
|
| 73 |
+
- High score expander in sidebar.
|
| 74 |
+
- Player name input in sidebar.
|
| 75 |
|
| 76 |
## Word List
|
| 77 |
- External list at `battlewords/words/wordlist.txt`.
|