Surn commited on
Commit
c8aba37
·
1 Parent(s): f50a1b0

- Update miss and correct guess sound effects to new versions
- allow iframe hosted version to pass url as a query string parameter (&iframe_host=https%3A%2F%2Fwww.battlewords.com%2Fplaynow.html) url encoding is required.
- minimal security added to prevent users from changing the options in a challenge.

README.md CHANGED
@@ -121,6 +121,11 @@ docker run -p8501:8501 battlewords
121
  - Persistent Storage: all game results saved locally for personal statistics without accounts.
122
  - Challenge Mode: remote storage of challenge results, multi-user leaderboard, and shareable links.
123
 
 
 
 
 
 
124
  -0.2.22
125
  - fix challenge mode link
126
  - challenge mode UI improvements
 
121
  - Persistent Storage: all game results saved locally for personal statistics without accounts.
122
  - Challenge Mode: remote storage of challenge results, multi-user leaderboard, and shareable links.
123
 
124
+ -0.2.23
125
+ - Update miss and correct guess sound effects to new versions
126
+ - allow iframe hosted version to pass url as a query string parameter (&iframe_host=https%3A%2F%2Fwww.battlewords.com%2Fplaynow.html) url encoding is required.
127
+ - minimal security added to prevent users from changing the options in a challenge.
128
+
129
  -0.2.22
130
  - fix challenge mode link
131
  - challenge mode UI improvements
battlewords/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.2.22"
2
  __all__ = ["models", "generator", "logic", "ui", "game_storage"]
 
1
+ __version__ = "0.2.23"
2
  __all__ = ["models", "generator", "logic", "ui", "game_storage"]
battlewords/assets/audio/effects/correct_guess.mp3 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:397af68b9d343f146c1aa897f9bd64fdb646739edfabfb6ead6f2747fc089d2e
3
- size 50246
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:65fcf49f02fd7a6c70dd3c270c254c03dd286e9fb50e382d285b79cb5e24d22d
3
+ size 97255
battlewords/assets/audio/effects/miss.mp3 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e1d774c8241e9295a813f29127e2cb2c148b2ede6845b8ce1702ed970158fc04
3
- size 25834
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:07f70499881c735fc284e9a6b17f0f6d383b35b6e06f0c90aa672597110b916c
3
+ size 23449
battlewords/game_storage.py CHANGED
@@ -5,7 +5,7 @@ BattleWords-specific storage wrapper for HuggingFace storage operations.
5
  This module provides high-level functions for saving and loading BattleWords games
6
  using the shared storage module from battlewords.modules.
7
  """
8
- __version__ = "0.1.2"
9
 
10
  import json
11
  import tempfile
@@ -13,6 +13,7 @@ import os
13
  from datetime import datetime, timezone
14
  from typing import Dict, Any, List, Optional, Tuple
15
  import logging
 
16
 
17
  from battlewords.modules import (
18
  upload_files_to_repo,
@@ -423,6 +424,8 @@ def get_shareable_url(sid: str, base_url: str = None) -> str:
423
  """
424
  Generate a shareable URL from a short ID.
425
  If running locally, use localhost. Otherwise, use HuggingFace Space domain.
 
 
426
 
427
  Args:
428
  sid: Short ID (8 characters)
@@ -439,17 +442,38 @@ def get_shareable_url(sid: str, base_url: str = None) -> str:
439
  import os
440
  from battlewords.modules.constants import SPACE_NAME
441
 
442
- # 1. If base_url is provided, use it directly
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  if base_url:
444
- return f"{base_url.rstrip('/')}/?game_id={sid}"
 
445
 
446
- # 2. Check for local development (common Streamlit env vars)
447
  port = os.environ.get("PORT") or os.environ.get("STREAMLIT_SERVER_PORT") or "8501"
448
  host = os.environ.get("HOST") or os.environ.get("STREAMLIT_SERVER_ADDRESS") or "localhost"
449
  if host in ("localhost", "127.0.0.1") or os.environ.get("IS_LOCAL", "").lower() == "true":
450
  return f"http://{host}:{port}/?game_id={sid}"
451
 
452
- # 3. Otherwise, build HuggingFace Space URL from SPACE_NAME
453
  space = (SPACE_NAME or "surn/battlewords").lower().replace("/", "-")
454
  return f"https://{space}.hf.space/?game_id={sid}"
455
 
 
5
  This module provides high-level functions for saving and loading BattleWords games
6
  using the shared storage module from battlewords.modules.
7
  """
8
+ __version__ = "0.1.3"
9
 
10
  import json
11
  import tempfile
 
13
  from datetime import datetime, timezone
14
  from typing import Dict, Any, List, Optional, Tuple
15
  import logging
16
+ from urllib.parse import unquote
17
 
18
  from battlewords.modules import (
19
  upload_files_to_repo,
 
424
  """
425
  Generate a shareable URL from a short ID.
426
  If running locally, use localhost. Otherwise, use HuggingFace Space domain.
427
+ Additionally, if an "iframe_host" query parameter is present in the current
428
+ Streamlit request, it takes precedence and will be used as the base URL.
429
 
430
  Args:
431
  sid: Short ID (8 characters)
 
442
  import os
443
  from battlewords.modules.constants import SPACE_NAME
444
 
445
+ # 0) If not explicitly provided, try to read iframe_host from Streamlit query params
446
+ if base_url is None:
447
+ try:
448
+ import streamlit as st # local import to avoid hard dependency
449
+ params = getattr(st, "query_params", None)
450
+ if params is None and hasattr(st, "experimental_get_query_params"):
451
+ params = st.experimental_get_query_params()
452
+ if params and "iframe_host" in params:
453
+ raw_host = params.get("iframe_host")
454
+ # st.query_params may return str or list[str]
455
+ if isinstance(raw_host, (list, tuple)):
456
+ raw_host = raw_host[0] if raw_host else None
457
+ if raw_host:
458
+ decoded = unquote(str(raw_host))
459
+ if decoded:
460
+ base_url = decoded
461
+ except Exception:
462
+ # Ignore any errors here and fall back to defaults below
463
+ pass
464
+
465
+ # 1) If base_url is provided (either parameter or iframe_host), use it directly
466
  if base_url:
467
+ sep = '&' if '?' in base_url else '?'
468
+ return f"{base_url}{sep}game_id={sid}"
469
 
470
+ # 2) Check for local development (common Streamlit env vars)
471
  port = os.environ.get("PORT") or os.environ.get("STREAMLIT_SERVER_PORT") or "8501"
472
  host = os.environ.get("HOST") or os.environ.get("STREAMLIT_SERVER_ADDRESS") or "localhost"
473
  if host in ("localhost", "127.0.0.1") or os.environ.get("IS_LOCAL", "").lower() == "true":
474
  return f"http://{host}:{port}/?game_id={sid}"
475
 
476
+ # 3) Otherwise, build HuggingFace Space URL from SPACE_NAME
477
  space = (SPACE_NAME or "surn/battlewords").lower().replace("/", "-")
478
  return f"https://{space}.hf.space/?game_id={sid}"
479
 
battlewords/ui.py CHANGED
@@ -255,7 +255,7 @@ def inject_styles() -> None:
255
 
256
  /* Final score style */
257
  .bw-final-score { color: #1ca41c !important; font-weight: 800; }
258
- .stExpander {z-index: 10;}
259
  div[data-testid="stToastContainer"], div[data-testid="stToast"] {
260
  margin: 0 auto;
261
  }
@@ -596,7 +596,7 @@ def _render_sidebar():
596
  options=game_modes,
597
  index=game_modes.index(current_mode) if current_mode in game_modes else 0,
598
  key="game_mode",
599
- on_change=_new_game,
600
  )
601
 
602
  st.header("Wordlist Controls")
@@ -615,7 +615,7 @@ def _render_sidebar():
615
  index=current_index,
616
  format_func=lambda f: f.rsplit(".", 1)[0],
617
  key="selected_wordlist",
618
- on_change=_new_game, # immediately start a new game with the selected list
619
  )
620
 
621
  if st.button("Sort Wordlist", width=125, key="sort_wordlist_btn"):
@@ -636,7 +636,8 @@ def _render_sidebar():
636
  "Spacer (space between words)",
637
  options=spacer_options,
638
  index=spacer_options.index(st.session_state.spacer),
639
- key="spacer"
 
640
  )
641
 
642
  # Add Show Incorrect Guesses option - now enabled by default
@@ -1434,6 +1435,8 @@ def _game_over_content(state: GameState) -> None:
1434
  /*filter: invert(1);*/
1435
  }
1436
  .st-bb {background-color: rgba(29, 100, 200, 0.5);}
 
 
1437
  </style>
1438
  """,
1439
  unsafe_allow_html=True,
@@ -1715,4 +1718,42 @@ def run_app():
1715
  # End condition (only show overlay if dismissed)
1716
  state = _to_state()
1717
  if is_game_over(state) and not st.session_state.get("hide_gameover_overlay", False):
1718
- _render_game_over(state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
  /* Final score style */
257
  .bw-final-score { color: #1ca41c !important; font-weight: 800; }
258
+ .stExpander {z-index: 10;width: 50%;}
259
  div[data-testid="stToastContainer"], div[data-testid="stToast"] {
260
  margin: 0 auto;
261
  }
 
596
  options=game_modes,
597
  index=game_modes.index(current_mode) if current_mode in game_modes else 0,
598
  key="game_mode",
599
+ on_change=_on_game_option_change, # was _new_game
600
  )
601
 
602
  st.header("Wordlist Controls")
 
615
  index=current_index,
616
  format_func=lambda f: f.rsplit(".", 1)[0],
617
  key="selected_wordlist",
618
+ on_change=_on_game_option_change, # was _new_game
619
  )
620
 
621
  if st.button("Sort Wordlist", width=125, key="sort_wordlist_btn"):
 
636
  "Spacer (space between words)",
637
  options=spacer_options,
638
  index=spacer_options.index(st.session_state.spacer),
639
+ key="spacer",
640
+ on_change=_on_game_option_change, # add callback
641
  )
642
 
643
  # Add Show Incorrect Guesses option - now enabled by default
 
1435
  /*filter: invert(1);*/
1436
  }
1437
  .st-bb {background-color: rgba(29, 100, 200, 0.5);}
1438
+ .st-key-generate_share_link div[data-testid="stButton"] button { aspect-ratio: auto;}
1439
+ .st-key-generate_share_link div[data-testid="stButton"] button:hover { color: #1d64c8;}
1440
  </style>
1441
  """,
1442
  unsafe_allow_html=True,
 
1718
  # End condition (only show overlay if dismissed)
1719
  state = _to_state()
1720
  if is_game_over(state) and not st.session_state.get("hide_gameover_overlay", False):
1721
+ _render_game_over(state)
1722
+
1723
+ def _on_game_option_change() -> None:
1724
+ """
1725
+ Unified callback for game option changes.
1726
+ If currently in a loaded challenge, break the link by resetting challenge state
1727
+ and removing the game_id query param. Then start a new game with the updated options.
1728
+ """
1729
+ try:
1730
+ # Remove challenge-specific query param if present
1731
+ if hasattr(st, "query_params"):
1732
+ qp = st.query_params
1733
+ # st.query_params may be a Mapping; pop safely if supported
1734
+ try:
1735
+ if "game_id" in qp:
1736
+ qp.pop("game_id")
1737
+ except Exception:
1738
+ # Fallback: clear all params if pop not supported
1739
+ try:
1740
+ st.query_params.clear()
1741
+ except Exception:
1742
+ pass
1743
+ except Exception:
1744
+ pass
1745
+
1746
+ # Clear challenge session flags and links
1747
+ if st.session_state.get("loaded_game_sid") is not None:
1748
+ st.session_state.loaded_game_sid = None
1749
+ # Remove loaded challenge settings so UI no longer treats session as challenge mode
1750
+ st.session_state.pop("shared_game_settings", None)
1751
+ # Ensure the loader won't auto-reload challenge on rerun within this session
1752
+ st.session_state["shared_game_loaded"] = True
1753
+
1754
+ # Clear any existing generated share link tied to the previous challenge
1755
+ st.session_state.pop("share_url", None)
1756
+ st.session_state.pop("share_sid", None)
1757
+
1758
+ # Start a fresh game with updated options
1759
+ _new_game()