Spaces:
Running
Running
0.2.18
Browse files- Fix sound effect volume wiring and apply volume to all effects (hit/miss/correct/incorrect)
- Respect "Enable music" and "Volume" when playing congratulations music and when resuming background music (uses selected track)
- Add "Enable Sound Effects" checkbox (on by default) and honor it across the app
- Save generated effects to `assets/audio/effects/` so they are picked up by the app
- Add `requests` dependency for sound effect generation
- README.md +165 -153
- battlewords/__init__.py +1 -1
- battlewords/audio.py +7 -0
- battlewords/sounds.py +4 -4
- battlewords/ui.py +73 -74
- battlewords/words/__init__.py +0 -0
- pyproject.toml +2 -1
README.md
CHANGED
|
@@ -9,10 +9,10 @@ python_version: 3.12.8
|
|
| 9 |
app_port: 8501
|
| 10 |
app_file: app.py
|
| 11 |
tags:
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
---
|
| 17 |
|
| 18 |
# BattleWords
|
|
@@ -23,7 +23,7 @@ BattleWords is a vocabulary learning game inspired by classic Battleship mechani
|
|
| 23 |
|
| 24 |
## Features
|
| 25 |
|
| 26 |
-
-
|
| 27 |
- Words placed horizontally or vertically
|
| 28 |
- Radar visualization to help locate word boundaries
|
| 29 |
- Reveal grid cells and guess words for points
|
|
@@ -41,19 +41,19 @@ BattleWords is a vocabulary learning game inspired by classic Battleship mechani
|
|
| 41 |
|
| 42 |
## Installation
|
| 43 |
1. Clone the repository:
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
3. Install dependencies: ( add --system if not using a virutal environment)
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
|
| 58 |
|
| 59 |
## Running BattleWords
|
|
@@ -81,20 +81,20 @@ To deploy on Hugging Face Spaces:
|
|
| 81 |
For local Docker runs:
|
| 82 |
```sh
|
| 83 |
docker build -t battlewords .
|
| 84 |
-
docker run -
|
| 85 |
```
|
| 86 |
|
| 87 |
## Folder Structure
|
| 88 |
|
| 89 |
- `app.py` β Streamlit entry point
|
| 90 |
- `battlewords/` β Python package
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
- `specs/` β documentation (`specs.md`, `requirements.md`)
|
| 99 |
- `tests/` β unit tests
|
| 100 |
|
|
@@ -107,116 +107,128 @@ docker run -p 8501:8501 battlewords
|
|
| 107 |
|
| 108 |
## Changelog
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
-0.2.17
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
|
| 115 |
-0.2.16
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-0.2.15
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-0.2.14
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
|
| 130 |
-0.2.13
|
| 131 |
-
|
| 132 |
-
|
| 133 |
|
| 134 |
-0.2.12
|
| 135 |
-
|
| 136 |
|
| 137 |
-0.2.11
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
|
| 142 |
-0.2.10
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
|
| 150 |
-0.2.9
|
| 151 |
-
|
| 152 |
-
|
| 153 |
|
| 154 |
-0.2.8
|
| 155 |
-
|
| 156 |
|
| 157 |
-0.2.7
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-0.2.6
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
|
| 168 |
-
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
|
| 172 |
-
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
|
| 176 |
-
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
-
|
| 182 |
-
|
| 183 |
|
| 184 |
-
-
|
| 185 |
-
|
| 186 |
|
| 187 |
-
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
-
|
| 192 |
-
|
| 193 |
|
| 194 |
-
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
|
| 198 |
-
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
|
| 202 |
-
-
|
| 203 |
-
|
| 204 |
|
| 205 |
-
-
|
| 206 |
-
|
| 207 |
|
| 208 |
-
-
|
| 209 |
-
|
| 210 |
|
| 211 |
-
-
|
| 212 |
-
|
| 213 |
|
| 214 |
-
-
|
| 215 |
-
|
| 216 |
|
| 217 |
-
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
|
| 221 |
## Known Issues / TODO
|
| 222 |
|
|
@@ -247,14 +259,14 @@ emoji: π²
|
|
| 247 |
colorFrom: blue
|
| 248 |
colorTo: indigo
|
| 249 |
sdk: streamlit
|
| 250 |
-
sdk_version:
|
| 251 |
-
python_version:
|
| 252 |
app_file: app.py
|
| 253 |
tags:
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
---
|
| 259 |
```
|
| 260 |
|
|
@@ -262,17 +274,17 @@ tags:
|
|
| 262 |
- `title`, `emoji`, `colorFrom`, `colorTo`: Visuals for your Space.
|
| 263 |
- `sdk`: Use `streamlit` for Streamlit apps.
|
| 264 |
- `sdk_version`: Latest supported Streamlit version.
|
| 265 |
-
- `python_version`: Python version (default
|
| 266 |
- `app_file`: Entry point for your app.
|
| 267 |
- `tags`: List of descriptive tags.
|
| 268 |
|
| 269 |
-
**Dependencies:**
|
| 270 |
Add a `requirements.txt` with your Python dependencies (e.g., `streamlit`, etc.).
|
| 271 |
|
| 272 |
-
**Port:**
|
| 273 |
Streamlit Spaces use port `8501` by default.
|
| 274 |
|
| 275 |
-
**Embedding:**
|
| 276 |
Spaces can be embedded in other sites using an `<iframe>`:
|
| 277 |
|
| 278 |
```html
|
|
@@ -298,38 +310,38 @@ To generate and save custom sound effects for BattleWords, you can use the `gene
|
|
| 298 |
|
| 299 |
```python
|
| 300 |
def generate_sound_effect(effect: str, save_to_assets: bool = False, use_api: str = "huggingface") -> str:
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
```
|
| 334 |
|
| 335 |
## Parameters
|
|
@@ -375,7 +387,7 @@ Happy gaming and sound designing!
|
|
| 375 |
### High Scores π
|
| 376 |
- Local high score tracking (stored in `~/.battlewords/data/`)
|
| 377 |
- Filter high scores by wordlist and game mode
|
| 378 |
-
-
|
| 379 |
- Player names supported (optional)
|
| 380 |
|
| 381 |
### Persistent Storage πΎ
|
|
@@ -387,13 +399,13 @@ Happy gaming and sound designing!
|
|
| 387 |
|
| 388 |
- `app.py` β Streamlit entry point
|
| 389 |
- `battlewords/` β Python package
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
- `specs/` β documentation (`specs.md`, `requirements.md`)
|
| 398 |
- `tests/` β unit tests
|
| 399 |
|
|
@@ -407,7 +419,7 @@ Happy gaming and sound designing!
|
|
| 407 |
|
| 408 |
**Files**:
|
| 409 |
- `game_results.json` - All completed games
|
| 410 |
-
- `highscores.json` -
|
| 411 |
|
| 412 |
You can delete these files at any time to reset your data.
|
| 413 |
|
|
|
|
| 9 |
app_port: 8501
|
| 10 |
app_file: app.py
|
| 11 |
tags:
|
| 12 |
+
- game
|
| 13 |
+
- vocabulary
|
| 14 |
+
- streamlit
|
| 15 |
+
- education
|
| 16 |
---
|
| 17 |
|
| 18 |
# BattleWords
|
|
|
|
| 23 |
|
| 24 |
## Features
|
| 25 |
|
| 26 |
+
-12x12 grid with six hidden words (2x4-letter,2x5-letter,2x6-letter)
|
| 27 |
- Words placed horizontally or vertically
|
| 28 |
- Radar visualization to help locate word boundaries
|
| 29 |
- Reveal grid cells and guess words for points
|
|
|
|
| 41 |
|
| 42 |
## Installation
|
| 43 |
1. Clone the repository:
|
| 44 |
+
```
|
| 45 |
+
git clone https://github.com/Oncorporation/BattleWords.git
|
| 46 |
+
cd battlewords
|
| 47 |
+
```
|
| 48 |
+
2. (Optional) Create and activate a virtual environment:
|
| 49 |
+
```
|
| 50 |
+
python -m venv venv
|
| 51 |
+
source venv/bin/activate # On Windows use `venv\Scripts\activate`
|
| 52 |
+
```
|
| 53 |
3. Install dependencies: ( add --system if not using a virutal environment)
|
| 54 |
+
```
|
| 55 |
+
uv pip install -r requirements.txt --link-mode=copy
|
| 56 |
+
```
|
| 57 |
|
| 58 |
|
| 59 |
## Running BattleWords
|
|
|
|
| 81 |
For local Docker runs:
|
| 82 |
```sh
|
| 83 |
docker build -t battlewords .
|
| 84 |
+
docker run -p8501:8501 battlewords
|
| 85 |
```
|
| 86 |
|
| 87 |
## Folder Structure
|
| 88 |
|
| 89 |
- `app.py` β Streamlit entry point
|
| 90 |
- `battlewords/` β Python package
|
| 91 |
+
- `models.py` β data models and types
|
| 92 |
+
- `word_loader.py` β word list loading and validation
|
| 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
|
| 100 |
|
|
|
|
| 107 |
|
| 108 |
## Changelog
|
| 109 |
|
| 110 |
+
### v0.3.0 (planned)
|
| 111 |
+
- Game Sharing: share unique game URLs with friends to challenge them on the same word set.
|
| 112 |
+
- High Scores: local leaderboard tracking top scores by wordlist and game mode.
|
| 113 |
+
- Persistent Storage: all game results saved locally for personal statistics without accounts.
|
| 114 |
+
|
| 115 |
+
-0.2.18
|
| 116 |
+
- Fix sound effect volume wiring and apply volume to all effects (hit/miss/correct/incorrect)
|
| 117 |
+
- Respect "Enable music" and "Volume" when playing congratulations music and when resuming background music (uses selected track)
|
| 118 |
+
- Add "Enable Sound Effects" checkbox (on by default) and honor it across the app
|
| 119 |
+
- Save generated effects to `assets/audio/effects/` so they are picked up by the app
|
| 120 |
+
- Add `requests` dependency for sound effect generation
|
| 121 |
+
|
| 122 |
-0.2.17
|
| 123 |
+
- documentation updates and corrections
|
| 124 |
+
- updated CLAUDE.md with accurate feature status and project structure
|
| 125 |
+
- clarified v0.3.0 planned features vs current implementation
|
| 126 |
|
| 127 |
-0.2.16
|
| 128 |
+
- replace question marks in score panel with underscores
|
| 129 |
+
- add option to toggle incorrect guess history display in settings (enabled by default)
|
| 130 |
+
- game over popup updated to ensure it is fully visible on screen
|
| 131 |
+
|
| 132 |
-0.2.15
|
| 133 |
+
- fix music playing after game end
|
| 134 |
+
- change incorrect guesses icon
|
| 135 |
+
- fix sound effect and music volume issues
|
| 136 |
+
|
| 137 |
-0.2.14
|
| 138 |
+
- bug fix on final score popup
|
| 139 |
+
- score panel alignment centered
|
| 140 |
+
- change incorrect guess history UI
|
| 141 |
|
| 142 |
-0.2.13
|
| 143 |
+
- upgrade background ocean view
|
| 144 |
+
- apply volume control to sound effects
|
| 145 |
|
| 146 |
-0.2.12
|
| 147 |
+
- fix music looping on congratulations screen
|
| 148 |
|
| 149 |
-0.2.11
|
| 150 |
+
- update timer to be live during gameplay, but reset with each action
|
| 151 |
+
- compact design
|
| 152 |
+
- remove fullscreen image tooltip
|
| 153 |
|
| 154 |
-0.2.10
|
| 155 |
+
- reduce sonar graphic size
|
| 156 |
+
- update music and special effects file locations
|
| 157 |
+
- remove some music and sound effects
|
| 158 |
+
- change Guess Text input color
|
| 159 |
+
- incorrect guess UI update
|
| 160 |
+
- scoreboard update
|
| 161 |
|
| 162 |
-0.2.9
|
| 163 |
+
- fix sonar grid alignment issue on some browsers
|
| 164 |
+
- When all letters of a word are revealed, it is automatically marked as found.
|
| 165 |
|
| 166 |
-0.2.8
|
| 167 |
+
- Add10 incorrect guess limit per game
|
| 168 |
|
| 169 |
-0.2.7
|
| 170 |
+
- fix background music playback issue on some browsers
|
| 171 |
+
- add sound effects
|
| 172 |
+
- enhance sonar grid visualization
|
| 173 |
+
- add claude.md documentation
|
| 174 |
+
|
| 175 |
-0.2.6
|
| 176 |
+
- fix sonar grid alignment
|
| 177 |
+
- improve score summary layout and styling
|
| 178 |
+
- Add timer to game display in sidebar
|
| 179 |
|
| 180 |
+
-0.2.5
|
| 181 |
+
- fix finale pop up issue
|
| 182 |
+
- make grid cells square on wider devices
|
| 183 |
|
| 184 |
+
-0.2.4
|
| 185 |
+
- Add music files to repo
|
| 186 |
+
- disable music by default
|
| 187 |
|
| 188 |
+
-0.2.3
|
| 189 |
+
- Update version information display
|
| 190 |
+
- adjust sonar grid alignment
|
| 191 |
+
- fix settings scroll issue
|
| 192 |
+
|
| 193 |
+
-0.2.2
|
| 194 |
+
- Add Musical background and settings to toggle sound on/off.
|
| 195 |
|
| 196 |
+
-0.2.1
|
| 197 |
+
- Add Theme toggle (light/dark/custom) in sidebar.
|
| 198 |
|
| 199 |
+
-0.2.0
|
| 200 |
+
- Added a loading screen when starting a new game.
|
| 201 |
+
- Added a congratulations screen with your final score and tier when the game ends.
|
| 202 |
+
|
| 203 |
+
-0.1.13
|
| 204 |
+
- Improved score summary layout for clarity and style.
|
| 205 |
|
| 206 |
+
-0.1.12
|
| 207 |
+
- Improved score summary layout and styling.
|
| 208 |
+
- Enhanced overall appearance and readability.
|
| 209 |
|
| 210 |
+
-0.1.11
|
| 211 |
+
- Game now ends when all words are found or revealed.
|
| 212 |
+
- Added word spacing logic and improved settings.
|
| 213 |
|
| 214 |
+
-0.1.10
|
| 215 |
+
- Added game mode selector and improved UI feedback.
|
| 216 |
|
| 217 |
+
-0.1.9
|
| 218 |
+
- Improved background and mobile layout.
|
| 219 |
|
| 220 |
+
-0.1.8
|
| 221 |
+
- Updated to Python3.12.
|
| 222 |
|
| 223 |
+
-0.1.5
|
| 224 |
+
- Added hit/miss indicator and improved grid feedback.
|
| 225 |
|
| 226 |
+
-0.1.4
|
| 227 |
+
- Radar visualization improved and mobile layout enhanced.
|
| 228 |
|
| 229 |
+
-0.1.3
|
| 230 |
+
- Added wordlist picker and sort feature.
|
| 231 |
+
- Improved score panel and final score display.
|
| 232 |
|
| 233 |
## Known Issues / TODO
|
| 234 |
|
|
|
|
| 259 |
colorFrom: blue
|
| 260 |
colorTo: indigo
|
| 261 |
sdk: streamlit
|
| 262 |
+
sdk_version:1.25.0
|
| 263 |
+
python_version:3.10
|
| 264 |
app_file: app.py
|
| 265 |
tags:
|
| 266 |
+
- game
|
| 267 |
+
- vocabulary
|
| 268 |
+
- streamlit
|
| 269 |
+
- education
|
| 270 |
---
|
| 271 |
```
|
| 272 |
|
|
|
|
| 274 |
- `title`, `emoji`, `colorFrom`, `colorTo`: Visuals for your Space.
|
| 275 |
- `sdk`: Use `streamlit` for Streamlit apps.
|
| 276 |
- `sdk_version`: Latest supported Streamlit version.
|
| 277 |
+
- `python_version`: Python version (default is3.10).
|
| 278 |
- `app_file`: Entry point for your app.
|
| 279 |
- `tags`: List of descriptive tags.
|
| 280 |
|
| 281 |
+
**Dependencies:**
|
| 282 |
Add a `requirements.txt` with your Python dependencies (e.g., `streamlit`, etc.).
|
| 283 |
|
| 284 |
+
**Port:**
|
| 285 |
Streamlit Spaces use port `8501` by default.
|
| 286 |
|
| 287 |
+
**Embedding:**
|
| 288 |
Spaces can be embedded in other sites using an `<iframe>`:
|
| 289 |
|
| 290 |
```html
|
|
|
|
| 310 |
|
| 311 |
```python
|
| 312 |
def generate_sound_effect(effect: str, save_to_assets: bool = False, use_api: str = "huggingface") -> str:
|
| 313 |
+
"""
|
| 314 |
+
Generate a sound effect and save it as a file.
|
| 315 |
+
|
| 316 |
+
Parameters:
|
| 317 |
+
- `effect`: Name of the effect to generate.
|
| 318 |
+
- `save_to_assets`: If `True`, saves the effect to the assets directory;
|
| 319 |
+
if `False`, saves to a temporary location. Default is `False`.
|
| 320 |
+
- `use_api`: API to use for generation. Options are "huggingface" or "replicate". Default is "huggingface".
|
| 321 |
+
|
| 322 |
+
Returns:
|
| 323 |
+
- File path to the saved sound effect.
|
| 324 |
+
"""
|
| 325 |
+
|
| 326 |
+
# ... [sound generation code] ...
|
| 327 |
+
|
| 328 |
+
if save_to_assets:
|
| 329 |
+
# Save to effects directory
|
| 330 |
+
assets_dir = os.path.join(os.path.dirname(__file__), "assets", "audio", "effects")
|
| 331 |
+
os.makedirs(assets_dir, exist_ok=True)
|
| 332 |
+
filename = f"{effect}.wav"
|
| 333 |
+
path = os.path.join(assets_dir, filename)
|
| 334 |
+
with open(path, "wb") as f:
|
| 335 |
+
f.write(audio_bytes)
|
| 336 |
+
print(f" Saved to: {path}")
|
| 337 |
+
else:
|
| 338 |
+
# Save to temporary file
|
| 339 |
+
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpfile:
|
| 340 |
+
tmpfile.write(audio_bytes)
|
| 341 |
+
path = tmpfile.name
|
| 342 |
+
print(f" Saved to: {path}")
|
| 343 |
+
|
| 344 |
+
return path
|
| 345 |
```
|
| 346 |
|
| 347 |
## Parameters
|
|
|
|
| 387 |
### High Scores π
|
| 388 |
- Local high score tracking (stored in `~/.battlewords/data/`)
|
| 389 |
- Filter high scores by wordlist and game mode
|
| 390 |
+
- Top10 leaderboard display
|
| 391 |
- Player names supported (optional)
|
| 392 |
|
| 393 |
### Persistent Storage πΎ
|
|
|
|
| 399 |
|
| 400 |
- `app.py` β Streamlit entry point
|
| 401 |
- `battlewords/` β Python package
|
| 402 |
+
- `models.py` β data models and types
|
| 403 |
+
- `word_loader.py` β word list loading and validation
|
| 404 |
+
- `generator.py` β word placement logic
|
| 405 |
+
- `logic.py` β game mechanics (reveal, guess, scoring)
|
| 406 |
+
- `ui.py` β Streamlit UI composition
|
| 407 |
+
- `storage.py` β **NEW**: persistent storage and high scores
|
| 408 |
+
- `words/wordlist.txt` β candidate words
|
| 409 |
- `specs/` β documentation (`specs.md`, `requirements.md`)
|
| 410 |
- `tests/` β unit tests
|
| 411 |
|
|
|
|
| 419 |
|
| 420 |
**Files**:
|
| 421 |
- `game_results.json` - All completed games
|
| 422 |
+
- `highscores.json` - Top100 scores
|
| 423 |
|
| 424 |
You can delete these files at any time to reset your data.
|
| 425 |
|
battlewords/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
-
__version__ = "0.2.
|
| 2 |
__all__ = ["models", "generator", "logic", "ui"]
|
|
|
|
| 1 |
+
__version__ = "0.2.18"
|
| 2 |
__all__ = ["models", "generator", "logic", "ui"]
|
battlewords/audio.py
CHANGED
|
@@ -204,6 +204,13 @@ def play_sound_effect(effect_name: str, volume: float = 0.5) -> None:
|
|
| 204 |
"""
|
| 205 |
from streamlit.components.v1 import html as _html
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
sound_files = get_sound_effect_files()
|
| 208 |
|
| 209 |
if effect_name not in sound_files:
|
|
|
|
| 204 |
"""
|
| 205 |
from streamlit.components.v1 import html as _html
|
| 206 |
|
| 207 |
+
# Respect Enable Sound Effects setting from sidebar
|
| 208 |
+
try:
|
| 209 |
+
if not st.session_state.get("enable_sound_effects", True):
|
| 210 |
+
return
|
| 211 |
+
except Exception:
|
| 212 |
+
pass
|
| 213 |
+
|
| 214 |
sound_files = get_sound_effect_files()
|
| 215 |
|
| 216 |
if effect_name not in sound_files:
|
battlewords/sounds.py
CHANGED
|
@@ -47,7 +47,7 @@ def generate_sound_effect(effect: str, save_to_assets: bool = False, use_api: st
|
|
| 47 |
|
| 48 |
Args:
|
| 49 |
effect: Name of the effect (must be in EFFECT_PROMPTS)
|
| 50 |
-
save_to_assets: If True, save to battlewords/assets/audio/ instead of temp directory
|
| 51 |
use_api: API to use - "huggingface" (default) or "replicate"
|
| 52 |
"""
|
| 53 |
if effect not in EFFECT_PROMPTS:
|
|
@@ -79,8 +79,8 @@ def generate_sound_effect(effect: str, save_to_assets: bool = False, use_api: st
|
|
| 79 |
|
| 80 |
# Determine save location
|
| 81 |
if save_to_assets:
|
| 82 |
-
# Save to assets directory
|
| 83 |
-
assets_dir = os.path.join(os.path.dirname(__file__), "assets", "audio")
|
| 84 |
os.makedirs(assets_dir, exist_ok=True)
|
| 85 |
filename = f"{effect}.wav"
|
| 86 |
path = os.path.join(assets_dir, filename)
|
|
@@ -174,7 +174,7 @@ def generate_all_effects(save_to_assets: bool = True):
|
|
| 174 |
Generate all sound effects defined in EFFECT_PROMPTS.
|
| 175 |
|
| 176 |
Args:
|
| 177 |
-
save_to_assets: If True, save to battlewords/assets/audio/ directory
|
| 178 |
"""
|
| 179 |
print(f"\nGenerating {len(EFFECT_PROMPTS)} sound effects...")
|
| 180 |
print("=" * 60)
|
|
|
|
| 47 |
|
| 48 |
Args:
|
| 49 |
effect: Name of the effect (must be in EFFECT_PROMPTS)
|
| 50 |
+
save_to_assets: If True, save to battlewords/assets/audio/effects/ instead of temp directory
|
| 51 |
use_api: API to use - "huggingface" (default) or "replicate"
|
| 52 |
"""
|
| 53 |
if effect not in EFFECT_PROMPTS:
|
|
|
|
| 79 |
|
| 80 |
# Determine save location
|
| 81 |
if save_to_assets:
|
| 82 |
+
# Save to effects assets directory (preferred by audio.py)
|
| 83 |
+
assets_dir = os.path.join(os.path.dirname(__file__), "assets", "audio", "effects")
|
| 84 |
os.makedirs(assets_dir, exist_ok=True)
|
| 85 |
filename = f"{effect}.wav"
|
| 86 |
path = os.path.join(assets_dir, filename)
|
|
|
|
| 174 |
Generate all sound effects defined in EFFECT_PROMPTS.
|
| 175 |
|
| 176 |
Args:
|
| 177 |
+
save_to_assets: If True, save to battlewords/assets/audio/effects/ directory
|
| 178 |
"""
|
| 179 |
print(f"\nGenerating {len(EFFECT_PROMPTS)} sound effects...")
|
| 180 |
print("=" * 60)
|
battlewords/ui.py
CHANGED
|
@@ -108,6 +108,9 @@ ocean_background_css = """
|
|
| 108 |
opacity: 0.10;
|
| 109 |
animation: waveOverlayScroll 16s linear infinite;
|
| 110 |
}
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
@keyframes waveOverlayScroll {
|
| 113 |
0% { background-position: 0px 0px, 0px 0px; }
|
|
@@ -230,7 +233,7 @@ def inject_styles() -> None:
|
|
| 230 |
display: flex;
|
| 231 |
align-items: center;
|
| 232 |
justify-content: center;
|
| 233 |
-
border: 1px solid #
|
| 234 |
border-radius: 0;
|
| 235 |
font-weight: 700;
|
| 236 |
user-select: none;
|
|
@@ -395,34 +398,38 @@ def _init_session() -> None:
|
|
| 395 |
|
| 396 |
|
| 397 |
def _new_game() -> None:
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
st.session_state.
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
st.session_state.
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
|
| 427 |
|
| 428 |
def _to_state() -> GameState:
|
|
@@ -518,7 +525,7 @@ def _render_sidebar():
|
|
| 518 |
st.checkbox("Show Grid ticks", value=st.session_state.show_grid_ticks, key="show_grid_ticks")
|
| 519 |
|
| 520 |
# Add Spacer option
|
| 521 |
-
spacer_options = [0,
|
| 522 |
if "spacer" not in st.session_state:
|
| 523 |
st.session_state.spacer = 1
|
| 524 |
st.selectbox(
|
|
@@ -544,29 +551,33 @@ def _render_sidebar():
|
|
| 544 |
st.session_state.music_volume = 15
|
| 545 |
# --- Add sound effects volume ---
|
| 546 |
if "effects_volume" not in st.session_state:
|
| 547 |
-
st.session_state.effects_volume =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
|
| 549 |
enabled = st.checkbox("Enable music", value=st.session_state.music_enabled, key="music_enabled")
|
| 550 |
|
| 551 |
st.slider(
|
| 552 |
"Volume",
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
|
| 561 |
# --- Add sound effects volume slider ---
|
| 562 |
st.slider(
|
| 563 |
"Sound Effects Volume",
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
|
| 571 |
selected_path = None
|
| 572 |
if tracks:
|
|
@@ -1107,12 +1118,12 @@ def _render_guess_form(state: GameState):
|
|
| 1107 |
if correct:
|
| 1108 |
st.session_state.radar_gif_path = None
|
| 1109 |
st.session_state.radar_gif_signature = None
|
| 1110 |
-
play_sound_effect("correct_guess", volume=
|
| 1111 |
else:
|
| 1112 |
# Update incorrect guesses list - keep only last 10
|
| 1113 |
st.session_state.incorrect_guesses.append(guess_text)
|
| 1114 |
st.session_state.incorrect_guesses = st.session_state.incorrect_guesses[-10:]
|
| 1115 |
-
play_sound_effect("incorrect_guess", volume=
|
| 1116 |
st.rerun()
|
| 1117 |
|
| 1118 |
|
|
@@ -1244,13 +1255,13 @@ def _render_score_panel(state: GameState):
|
|
| 1244 |
# -------------------- Game Over Dialog --------------------
|
| 1245 |
|
| 1246 |
def _game_over_content(state: GameState) -> None:
|
| 1247 |
-
# Play congratulations music (not sound effect) as background
|
| 1248 |
music_dir = _get_music_dir()
|
| 1249 |
congrats_music_path = os.path.join(music_dir, "congratulations.mp3")
|
| 1250 |
-
if os.path.exists(congrats_music_path):
|
| 1251 |
src_url = _load_audio_data_url(congrats_music_path)
|
| 1252 |
# Play once (no loop) at configured volume
|
| 1253 |
-
_mount_background_audio(enabled=True,src_data_url=src_url, volume=(st.session_state.get("music_volume", 100)) / 100, loop=False)
|
| 1254 |
else:
|
| 1255 |
_mount_background_audio(False, None, 0.0)
|
| 1256 |
|
|
@@ -1388,9 +1399,9 @@ def _render_game_over(state: GameState):
|
|
| 1388 |
visible = bool(st.session_state.get("show_gameover_overlay", True)) and is_game_over(state)
|
| 1389 |
music_dir = _get_music_dir()
|
| 1390 |
if visible:
|
| 1391 |
-
# Mount congratulations music (play once, do not loop)
|
| 1392 |
congrats_music_path = os.path.join(music_dir, "congratulations.mp3")
|
| 1393 |
-
if os.path.exists(congrats_music_path):
|
| 1394 |
src_url = _load_audio_data_url(congrats_music_path)
|
| 1395 |
_mount_background_audio(enabled=True, src_data_url=src_url, volume=(st.session_state.get("music_volume", 100)) / 100, loop=False)
|
| 1396 |
else:
|
|
@@ -1399,35 +1410,23 @@ def _render_game_over(state: GameState):
|
|
| 1399 |
else:
|
| 1400 |
# Only play background music if music is enabled
|
| 1401 |
if st.session_state.get("music_enabled", False):
|
| 1402 |
-
|
| 1403 |
-
|
| 1404 |
-
|
|
|
|
| 1405 |
_mount_background_audio(True, src_url, (st.session_state.get("music_volume", 100)) / 100)
|
| 1406 |
else:
|
| 1407 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1408 |
else:
|
| 1409 |
# Disable all music playback
|
| 1410 |
_mount_background_audio(False, None, 0.0)
|
| 1411 |
|
| 1412 |
-
def _sort_wordlist(filename):
|
| 1413 |
-
import time # Add this import
|
| 1414 |
-
|
| 1415 |
-
WORDS_DIR = os.path.join(os.path.dirname(__file__), "words")
|
| 1416 |
-
filepath = os.path.join(WORDS_DIR, filename)
|
| 1417 |
-
sorted_words = sort_word_file(filepath)
|
| 1418 |
-
# Optionally, write sorted words back to file
|
| 1419 |
-
with open(filepath, "w", encoding="utf-8") as f:
|
| 1420 |
-
# Re-add header if needed
|
| 1421 |
-
f.write("# Optional: place a large AβZ word list here (one word per line).\n")
|
| 1422 |
-
f.write("# The app falls back to built-in pools if fewer than 500 words per length are found.\n")
|
| 1423 |
-
for word in sorted_words:
|
| 1424 |
-
f.write(f"{word}\n")
|
| 1425 |
-
# Show a message in Streamlit
|
| 1426 |
-
st.success(f"{filename} sorted by length and alphabetically. Starting new game in 5 seconds...")
|
| 1427 |
-
time.sleep(5) # 5 second delay before starting new game
|
| 1428 |
-
_new_game()
|
| 1429 |
-
|
| 1430 |
-
|
| 1431 |
def run_app():
|
| 1432 |
# Handle overlay dismissal via query params using new API
|
| 1433 |
try:
|
|
|
|
| 108 |
opacity: 0.10;
|
| 109 |
animation: waveOverlayScroll 16s linear infinite;
|
| 110 |
}
|
| 111 |
+
.stIFrame {
|
| 112 |
+
margin-bottom:25px;
|
| 113 |
+
}
|
| 114 |
|
| 115 |
@keyframes waveOverlayScroll {
|
| 116 |
0% { background-position: 0px 0px, 0px 0px; }
|
|
|
|
| 233 |
display: flex;
|
| 234 |
align-items: center;
|
| 235 |
justify-content: center;
|
| 236 |
+
border: 1px solid #1d64c8;
|
| 237 |
border-radius: 0;
|
| 238 |
font-weight: 700;
|
| 239 |
user-select: none;
|
|
|
|
| 398 |
|
| 399 |
|
| 400 |
def _new_game() -> None:
|
| 401 |
+
selected = st.session_state.get("selected_wordlist")
|
| 402 |
+
mode = st.session_state.get("game_mode")
|
| 403 |
+
show_grid_ticks = st.session_state.get("show_grid_ticks", False)
|
| 404 |
+
spacer = st.session_state.get("spacer",1)
|
| 405 |
+
show_incorrect_guesses = st.session_state.get("show_incorrect_guesses", False)
|
| 406 |
+
# --- Preserve music and effects settings ---
|
| 407 |
+
music_enabled = st.session_state.get("music_enabled", False)
|
| 408 |
+
music_track_path = st.session_state.get("music_track_path")
|
| 409 |
+
music_volume = st.session_state.get("music_volume",15)
|
| 410 |
+
effects_volume = st.session_state.get("effects_volume",25)
|
| 411 |
+
enable_sound_effects = st.session_state.get("enable_sound_effects", True)
|
| 412 |
+
st.session_state.clear()
|
| 413 |
+
if selected:
|
| 414 |
+
st.session_state.selected_wordlist = selected
|
| 415 |
+
if mode:
|
| 416 |
+
st.session_state.game_mode = mode
|
| 417 |
+
st.session_state.show_grid_ticks = show_grid_ticks
|
| 418 |
+
st.session_state.spacer = spacer
|
| 419 |
+
st.session_state.show_incorrect_guesses = show_incorrect_guesses
|
| 420 |
+
# --- Restore music/effects settings ---
|
| 421 |
+
st.session_state.music_enabled = music_enabled
|
| 422 |
+
if music_track_path:
|
| 423 |
+
st.session_state.music_track_path = music_track_path
|
| 424 |
+
st.session_state.music_volume = music_volume
|
| 425 |
+
st.session_state.effects_volume = effects_volume
|
| 426 |
+
st.session_state.enable_sound_effects = enable_sound_effects
|
| 427 |
+
st.session_state.radar_gif_path = None
|
| 428 |
+
st.session_state.radar_gif_signature = None
|
| 429 |
+
st.session_state.start_time = datetime.now() # Reset timer on new game
|
| 430 |
+
st.session_state.end_time = None
|
| 431 |
+
st.session_state.incorrect_guesses = [] # Clear incorrect guesses for new game
|
| 432 |
+
_init_session()
|
| 433 |
|
| 434 |
|
| 435 |
def _to_state() -> GameState:
|
|
|
|
| 525 |
st.checkbox("Show Grid ticks", value=st.session_state.show_grid_ticks, key="show_grid_ticks")
|
| 526 |
|
| 527 |
# Add Spacer option
|
| 528 |
+
spacer_options = [0,1,2]
|
| 529 |
if "spacer" not in st.session_state:
|
| 530 |
st.session_state.spacer = 1
|
| 531 |
st.selectbox(
|
|
|
|
| 551 |
st.session_state.music_volume = 15
|
| 552 |
# --- Add sound effects volume ---
|
| 553 |
if "effects_volume" not in st.session_state:
|
| 554 |
+
st.session_state.effects_volume = 25
|
| 555 |
+
# --- Add enable sound effects ---
|
| 556 |
+
if "enable_sound_effects" not in st.session_state:
|
| 557 |
+
st.session_state.enable_sound_effects = True
|
| 558 |
+
st.checkbox("Enable Sound Effects", value=st.session_state.enable_sound_effects, key="enable_sound_effects")
|
| 559 |
|
| 560 |
enabled = st.checkbox("Enable music", value=st.session_state.music_enabled, key="music_enabled")
|
| 561 |
|
| 562 |
st.slider(
|
| 563 |
"Volume",
|
| 564 |
+
0,
|
| 565 |
+
100,
|
| 566 |
+
value=int(st.session_state.music_volume),
|
| 567 |
+
step=1,
|
| 568 |
+
key="music_volume",
|
| 569 |
+
disabled=not (enabled and bool(tracks)),
|
| 570 |
+
)
|
| 571 |
|
| 572 |
# --- Add sound effects volume slider ---
|
| 573 |
st.slider(
|
| 574 |
"Sound Effects Volume",
|
| 575 |
+
0,
|
| 576 |
+
100,
|
| 577 |
+
value=int(st.session_state.effects_volume),
|
| 578 |
+
step=1,
|
| 579 |
+
key="effects_volume",
|
| 580 |
+
)
|
| 581 |
|
| 582 |
selected_path = None
|
| 583 |
if tracks:
|
|
|
|
| 1118 |
if correct:
|
| 1119 |
st.session_state.radar_gif_path = None
|
| 1120 |
st.session_state.radar_gif_signature = None
|
| 1121 |
+
play_sound_effect("correct_guess", volume=(st.session_state.get("effects_volume", 50) / 100))
|
| 1122 |
else:
|
| 1123 |
# Update incorrect guesses list - keep only last 10
|
| 1124 |
st.session_state.incorrect_guesses.append(guess_text)
|
| 1125 |
st.session_state.incorrect_guesses = st.session_state.incorrect_guesses[-10:]
|
| 1126 |
+
play_sound_effect("incorrect_guess", volume=(st.session_state.get("effects_volume", 50) / 100))
|
| 1127 |
st.rerun()
|
| 1128 |
|
| 1129 |
|
|
|
|
| 1255 |
# -------------------- Game Over Dialog --------------------
|
| 1256 |
|
| 1257 |
def _game_over_content(state: GameState) -> None:
|
| 1258 |
+
# Play congratulations music (not sound effect) as background if enabled
|
| 1259 |
music_dir = _get_music_dir()
|
| 1260 |
congrats_music_path = os.path.join(music_dir, "congratulations.mp3")
|
| 1261 |
+
if st.session_state.get("music_enabled", False) and os.path.exists(congrats_music_path):
|
| 1262 |
src_url = _load_audio_data_url(congrats_music_path)
|
| 1263 |
# Play once (no loop) at configured volume
|
| 1264 |
+
_mount_background_audio(enabled=True, src_data_url=src_url, volume=(st.session_state.get("music_volume", 100)) / 100, loop=False)
|
| 1265 |
else:
|
| 1266 |
_mount_background_audio(False, None, 0.0)
|
| 1267 |
|
|
|
|
| 1399 |
visible = bool(st.session_state.get("show_gameover_overlay", True)) and is_game_over(state)
|
| 1400 |
music_dir = _get_music_dir()
|
| 1401 |
if visible:
|
| 1402 |
+
# Mount congratulations music (play once, do not loop) only if music is enabled
|
| 1403 |
congrats_music_path = os.path.join(music_dir, "congratulations.mp3")
|
| 1404 |
+
if st.session_state.get("music_enabled", False) and os.path.exists(congrats_music_path):
|
| 1405 |
src_url = _load_audio_data_url(congrats_music_path)
|
| 1406 |
_mount_background_audio(enabled=True, src_data_url=src_url, volume=(st.session_state.get("music_volume", 100)) / 100, loop=False)
|
| 1407 |
else:
|
|
|
|
| 1410 |
else:
|
| 1411 |
# Only play background music if music is enabled
|
| 1412 |
if st.session_state.get("music_enabled", False):
|
| 1413 |
+
# Prefer user-selected track
|
| 1414 |
+
track_path = st.session_state.get("music_track_path")
|
| 1415 |
+
if track_path and os.path.exists(track_path):
|
| 1416 |
+
src_url = _load_audio_data_url(track_path)
|
| 1417 |
_mount_background_audio(True, src_url, (st.session_state.get("music_volume", 100)) / 100)
|
| 1418 |
else:
|
| 1419 |
+
# Fallback to a default background track if available
|
| 1420 |
+
background_path = os.path.join(music_dir, "background.mp3")
|
| 1421 |
+
if os.path.exists(background_path):
|
| 1422 |
+
src_url = _load_audio_data_url(background_path)
|
| 1423 |
+
_mount_background_audio(True, src_url, (st.session_state.get("music_volume", 100)) / 100)
|
| 1424 |
+
else:
|
| 1425 |
+
_mount_background_audio(False, None, 0.0)
|
| 1426 |
else:
|
| 1427 |
# Disable all music playback
|
| 1428 |
_mount_background_audio(False, None, 0.0)
|
| 1429 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1430 |
def run_app():
|
| 1431 |
# Handle overlay dismissal via query params using new API
|
| 1432 |
try:
|
battlewords/words/__init__.py
DELETED
|
File without changes
|
pyproject.toml
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
[project]
|
| 2 |
name = "battlewords"
|
| 3 |
-
version = "0.
|
| 4 |
description = "BattleWords vocabulary game"
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.12,<3.13"
|
| 7 |
dependencies = [
|
| 8 |
"streamlit>=1.50.0",
|
| 9 |
"matplotlib>=3.8",
|
|
|
|
| 10 |
]
|
| 11 |
|
| 12 |
[build-system]
|
|
|
|
| 1 |
[project]
|
| 2 |
name = "battlewords"
|
| 3 |
+
version = "0.2.18"
|
| 4 |
description = "BattleWords vocabulary game"
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.12,<3.13"
|
| 7 |
dependencies = [
|
| 8 |
"streamlit>=1.50.0",
|
| 9 |
"matplotlib>=3.8",
|
| 10 |
+
"requests>=2.31.0",
|
| 11 |
]
|
| 12 |
|
| 13 |
[build-system]
|