Surn commited on
Commit
7606f52
Β·
1 Parent(s): 9ecb8cf

- 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 CHANGED
@@ -9,10 +9,10 @@ python_version: 3.12.8
9
  app_port: 8501
10
  app_file: app.py
11
  tags:
12
- - game
13
- - vocabulary
14
- - streamlit
15
- - education
16
  ---
17
 
18
  # BattleWords
@@ -23,7 +23,7 @@ BattleWords is a vocabulary learning game inspired by classic Battleship mechani
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,19 +41,19 @@ BattleWords is a vocabulary learning game inspired by classic Battleship mechani
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,20 +81,20 @@ To deploy on Hugging Face Spaces:
81
  For local Docker runs:
82
  ```sh
83
  docker build -t battlewords .
84
- docker run -p 8501: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,116 +107,128 @@ docker run -p 8501:8501 battlewords
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)
118
- - game over popup updated to ensure it is fully visible on screen
119
-
120
  -0.2.15
121
- - fix music playing after game end
122
- - change incorrect guesses icon
123
- - fix sound effect and music volume issues
124
-
125
  -0.2.14
126
- - bug fix on final score popup
127
- - score panel alignment centered
128
- - change incorrect guess history UI
129
 
130
  -0.2.13
131
- - upgrade background ocean view
132
- - apply volume control to sound effects
133
 
134
  -0.2.12
135
- - fix music looping on congratulations screen
136
 
137
  -0.2.11
138
- - update timer to be live during gameplay, but reset with each action
139
- - compact design
140
- - remove fullscreen image tooltip
141
 
142
  -0.2.10
143
- - reduce sonar graphic size
144
- - update music and special effects file locations
145
- - remove some music and sound effects
146
- - change Guess Text input color
147
- - incorrect guess UI update
148
- - scoreboard update
149
 
150
  -0.2.9
151
- - fix sonar grid alignment issue on some browsers
152
- - When all letters of a word are revealed, it is automatically marked as found.
153
 
154
  -0.2.8
155
- - Add 10 incorrect guess limit per game
156
 
157
  -0.2.7
158
- - fix background music playback issue on some browsers
159
- - add sound effects
160
- - enhance sonar grid visualization
161
- - add claude.md documentation
162
-
163
  -0.2.6
164
- - fix sonar grid alignment
165
- - improve score summary layout and styling
166
- - Add timer to game display in sidebar
167
 
168
- - 0.2.5
169
- - fix finale pop up issue
170
- - make grid cells square on wider devices
171
 
172
- - 0.2.4
173
- - Add music files to repo
174
- - disable music by default
175
 
176
- - 0.2.3
177
- - Update version information display
178
- - adjust sonar grid alignment
179
- - fix settings scroll issue
180
-
181
- - 0.2.2
182
- - Add Musical background and settings to toggle sound on/off.
183
 
184
- - 0.2.1
185
- - Add Theme toggle (light/dark/custom) in sidebar.
186
 
187
- - 0.2.0
188
- - Added a loading screen when starting a new game.
189
- - Added a congratulations screen with your final score and tier when the game ends.
190
-
191
- - 0.1.13
192
- - Improved score summary layout for clarity and style.
193
 
194
- - 0.1.12
195
- - Improved score summary layout and styling.
196
- - Enhanced overall appearance and readability.
197
 
198
- - 0.1.11
199
- - Game now ends when all words are found or revealed.
200
- - Added word spacing logic and improved settings.
201
 
202
- - 0.1.10
203
- - Added game mode selector and improved UI feedback.
204
 
205
- - 0.1.9
206
- - Improved background and mobile layout.
207
 
208
- - 0.1.8
209
- - Updated to Python 3.12.
210
 
211
- - 0.1.5
212
- - Added hit/miss indicator and improved grid feedback.
213
 
214
- - 0.1.4
215
- - Radar visualization improved and mobile layout enhanced.
216
 
217
- - 0.1.3
218
- - Added wordlist picker and sort feature.
219
- - Improved score panel and final score display.
220
 
221
  ## Known Issues / TODO
222
 
@@ -247,14 +259,14 @@ emoji: 🎲
247
  colorFrom: blue
248
  colorTo: indigo
249
  sdk: streamlit
250
- sdk_version: 1.25.0
251
- python_version: 3.10
252
  app_file: app.py
253
  tags:
254
- - game
255
- - vocabulary
256
- - streamlit
257
- - education
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 is 3.10).
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
- Generate a sound effect and save it as a file.
303
-
304
- Parameters:
305
- - `effect`: Name of the effect to generate.
306
- - `save_to_assets`: If `True`, saves the effect to the assets directory;
307
- if `False`, saves to a temporary location. Default is `False`.
308
- - `use_api`: API to use for generation. Options are "huggingface" or "replicate". Default is "huggingface".
309
-
310
- Returns:
311
- - File path to the saved sound effect.
312
- """
313
-
314
- # ... [sound generation code] ...
315
-
316
- if save_to_assets:
317
- # Save to effects directory
318
- assets_dir = os.path.join(os.path.dirname(__file__), "assets", "audio", "effects")
319
- os.makedirs(assets_dir, exist_ok=True)
320
- filename = f"{effect}.wav"
321
- path = os.path.join(assets_dir, filename)
322
- with open(path, "wb") as f:
323
- f.write(audio_bytes)
324
- print(f" Saved to: {path}")
325
- else:
326
- # Save to temporary file
327
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpfile:
328
- tmpfile.write(audio_bytes)
329
- path = tmpfile.name
330
- print(f" Saved to: {path}")
331
-
332
- return path
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
- - Top 10 leaderboard display
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
- - `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
 
@@ -407,7 +419,7 @@ Happy gaming and sound designing!
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
 
 
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.17"
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 #3a3a3a;
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
- selected = st.session_state.get("selected_wordlist")
399
- mode = st.session_state.get("game_mode")
400
- show_grid_ticks = st.session_state.get("show_grid_ticks", False)
401
- spacer = st.session_state.get("spacer", 1)
402
- show_incorrect_guesses = st.session_state.get("show_incorrect_guesses", False)
403
- # --- Preserve music settings ---
404
- music_enabled = st.session_state.get("music_enabled", False)
405
- music_track_path = st.session_state.get("music_track_path")
406
- music_volume = st.session_state.get("music_volume", 15)
407
- st.session_state.clear()
408
- if selected:
409
- st.session_state.selected_wordlist = selected
410
- if mode:
411
- st.session_state.game_mode = mode
412
- st.session_state.show_grid_ticks = show_grid_ticks
413
- st.session_state.spacer = spacer
414
- st.session_state.show_incorrect_guesses = show_incorrect_guesses
415
- # --- Restore music settings ---
416
- st.session_state.music_enabled = music_enabled
417
- if music_track_path:
418
- st.session_state.music_track_path = music_track_path
419
- st.session_state.music_volume = music_volume
420
- st.session_state.radar_gif_path = None
421
- st.session_state.radar_gif_signature = None
422
- st.session_state.start_time = datetime.now() # Reset timer on new game
423
- st.session_state.end_time = None
424
- st.session_state.incorrect_guesses = [] # Clear incorrect guesses for new game
425
- _init_session()
 
 
 
 
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, 1, 2]
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 = 50
 
 
 
 
548
 
549
  enabled = st.checkbox("Enable music", value=st.session_state.music_enabled, key="music_enabled")
550
 
551
  st.slider(
552
  "Volume",
553
- 0,
554
- 100,
555
- value=int(st.session_state.music_volume),
556
- step=1,
557
- key="music_volume",
558
- disabled=not (enabled and bool(tracks)),
559
- )
560
 
561
  # --- Add sound effects volume slider ---
562
  st.slider(
563
  "Sound Effects Volume",
564
- 0,
565
- 100,
566
- value=int(st.session_state.effects_volume),
567
- step=1,
568
- key="effects_volume",
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=0.5)
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=0.5)
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
- background_path = os.path.join(music_dir, "background.mp3")
1403
- if os.path.exists(background_path):
1404
- src_url = _load_audio_data_url(background_path)
 
1405
  _mount_background_audio(True, src_url, (st.session_state.get("music_volume", 100)) / 100)
1406
  else:
1407
- _mount_background_audio(False, None, 0.0)
 
 
 
 
 
 
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.1.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]