Spaces:
Running
Running
0.2.27
Browse files- Add "Show Challenge Share Links" setting (default: off)
- When disabled:
- Header Challenge Mode: hides the Share Challenge link
- Game Over: allows submitting results but suppresses displaying the generated share URL
- The setting is saved in session state and preserved across "New Game"
- No changes to game logic or storage; only UI visibility behavior
- README.md +110 -63
- battlewords/__init__.py +1 -1
- battlewords/ui.py +55 -95
- claude.md +86 -24
- specs/requirements.md +84 -48
- specs/specs.md +63 -2
README.md
CHANGED
|
@@ -23,22 +23,60 @@ 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 |
-
-
|
| 29 |
- Reveal grid cells and guess words for points
|
| 30 |
- Scoring tiers: Good (34–37), Great (38–41), Fantastic (42+)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
- Responsive UI built with Streamlit
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
- Wordlist sidebar controls (picker + one-click sort)
|
| 34 |
-
-
|
| 35 |
-
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
- **
|
| 39 |
-
- **
|
| 40 |
-
- **
|
| 41 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
## Challenge Mode & Leaderboard
|
| 44 |
|
|
@@ -89,6 +127,27 @@ docker build -t battlewords .
|
|
| 89 |
docker run -p8501:8501 battlewords
|
| 90 |
```
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
## Folder Structure
|
| 93 |
|
| 94 |
- `app.py` – Streamlit entry point
|
|
@@ -116,12 +175,19 @@ docker run -p8501:8501 battlewords
|
|
| 116 |
## Changelog
|
| 117 |
|
| 118 |
### v0.3.0 (planned)
|
| 119 |
-
-
|
| 120 |
-
-
|
| 121 |
-
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
-
-
|
| 125 |
- fix copy/share link button
|
| 126 |
|
| 127 |
-0.2.25
|
|
@@ -438,52 +504,33 @@ For any issues or enhancements, please refer to the project documentation or con
|
|
| 438 |
|
| 439 |
Happy gaming and sound designing!
|
| 440 |
|
| 441 |
-
## What's New in v0.
|
| 442 |
-
|
| 443 |
-
###
|
| 444 |
-
-
|
| 445 |
-
-
|
| 446 |
-
-
|
| 447 |
-
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
-
|
| 452 |
-
-
|
| 453 |
-
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
-
|
| 458 |
-
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
-
|
| 467 |
-
|
| 468 |
-
-
|
| 469 |
-
|
| 470 |
-
- `local_storage.py` – local JSON storage for results and high scores
|
| 471 |
-
- `storage.py` – (legacy) local storage and high scores
|
| 472 |
-
- `words/wordlist.txt` – candidate words
|
| 473 |
-
- `specs/` – documentation (`specs.md`, `requirements.md`)
|
| 474 |
-
- `tests/` – unit tests
|
| 475 |
-
|
| 476 |
-
## Data Storage
|
| 477 |
-
|
| 478 |
-
**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".
|
| 479 |
-
|
| 480 |
-
**Storage Location**:
|
| 481 |
-
- Windows: `C:\Users\<username>\.battlewords\data\`
|
| 482 |
-
- Linux/Mac: `~/.battlewords/data/`
|
| 483 |
-
|
| 484 |
-
**Files**:
|
| 485 |
-
- `game_results.json` - All completed games
|
| 486 |
-
- `highscores.json` - Top100 scores
|
| 487 |
-
|
| 488 |
-
You can delete these files at any time to reset your data.
|
| 489 |
|
|
|
|
| 23 |
|
| 24 |
## Features
|
| 25 |
|
| 26 |
+
### Core Gameplay
|
| 27 |
- 12x12 grid with six hidden words (2x4-letter, 2x5-letter, 2x6-letter)
|
| 28 |
- Words placed horizontally or vertically
|
| 29 |
+
- Animated radar visualization showing word boundaries
|
| 30 |
- Reveal grid cells and guess words for points
|
| 31 |
- Scoring tiers: Good (34–37), Great (38–41), Fantastic (42+)
|
| 32 |
+
- Game ends when all words are guessed or all word letters are revealed
|
| 33 |
+
- Incorrect guess history with tooltip and optional display (enabled by default)
|
| 34 |
+
- 10 incorrect guess limit per game
|
| 35 |
+
- Two game modes: Classic (chain guesses) and Too Easy (single guess per reveal)
|
| 36 |
+
|
| 37 |
+
### Audio & Visuals
|
| 38 |
+
- Ocean-themed gradient background with wave animations
|
| 39 |
+
- Background music system (toggleable with volume control)
|
| 40 |
+
- Sound effects for hits, misses, correct/incorrect guesses
|
| 41 |
- Responsive UI built with Streamlit
|
| 42 |
+
|
| 43 |
+
### Customization
|
| 44 |
+
- Multiple word lists (classic, fourth_grade, wordlist)
|
| 45 |
- Wordlist sidebar controls (picker + one-click sort)
|
| 46 |
+
- Configurable word spacing (0-2 cells between words)
|
| 47 |
+
- Audio volume controls (music and effects separate)
|
| 48 |
+
|
| 49 |
+
### ✅ Challenge Mode (v0.2.20+)
|
| 50 |
+
- **Shareable challenge links** via short URLs (`?game_id=<sid>`)
|
| 51 |
+
- **Multi-user leaderboards** sorted by score and time
|
| 52 |
+
- **Remote storage** via Hugging Face datasets
|
| 53 |
+
- **Word list difficulty calculation** and display
|
| 54 |
+
- **Submit results** to existing challenges or create new ones
|
| 55 |
+
- **Top 5 leaderboard** display in Challenge Mode banner
|
| 56 |
+
- **"Show Challenge Share Links" toggle** (default OFF) to control URL visibility
|
| 57 |
+
- Each player gets different random words from the same wordlist
|
| 58 |
+
|
| 59 |
+
### Deployment & Technical
|
| 60 |
+
- **Dockerfile-based deployment** supported for Hugging Face Spaces and other container platforms
|
| 61 |
+
- **Environment variables** for Challenge Mode (HF_API_TOKEN, HF_REPO_ID, SPACE_NAME)
|
| 62 |
+
- Works offline without HF credentials (Challenge Mode features disabled gracefully)
|
| 63 |
+
|
| 64 |
+
### Planned (v0.3.0)
|
| 65 |
+
- Local persistent storage for personal game history
|
| 66 |
+
- Personal high scores sidebar (offline-capable)
|
| 67 |
+
- Player statistics tracking
|
| 68 |
+
- Deterministic seed UI for custom puzzles
|
| 69 |
+
|
| 70 |
+
### Beta (v0.5.0)
|
| 71 |
+
- Word overlaps on shared letters (crossword-style)
|
| 72 |
+
- Enhanced responsive layout for mobile/tablet
|
| 73 |
+
- Keyboard navigation and guessing
|
| 74 |
+
|
| 75 |
+
### Full (v1.0.0)
|
| 76 |
+
- Daily puzzle mode with global leaderboards
|
| 77 |
+
- Practice mode with hints
|
| 78 |
+
- Multiple difficulty levels
|
| 79 |
+
- Internationalization (i18n) support
|
| 80 |
|
| 81 |
## Challenge Mode & Leaderboard
|
| 82 |
|
|
|
|
| 127 |
docker run -p8501:8501 battlewords
|
| 128 |
```
|
| 129 |
|
| 130 |
+
### Environment Variables (for Challenge Mode)
|
| 131 |
+
|
| 132 |
+
Challenge Mode requires a `.env` file in the project root with HuggingFace Hub credentials:
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
# Required for Challenge Mode
|
| 136 |
+
HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx # or HF_TOKEN
|
| 137 |
+
HF_REPO_ID=YourUsername/YourRepo # Target HF dataset repo
|
| 138 |
+
SPACE_NAME=YourUsername/BattleWords # Your HF Space name
|
| 139 |
+
|
| 140 |
+
# Optional
|
| 141 |
+
CRYPTO_PK= # Reserved for future signing
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
**How to get your HF_API_TOKEN:**
|
| 145 |
+
1. Go to https://huggingface.co/settings/tokens
|
| 146 |
+
2. Create a new token with `write` access
|
| 147 |
+
3. Add to `.env` file as `HF_API_TOKEN=hf_...`
|
| 148 |
+
|
| 149 |
+
**Note:** The app works without these variables, but Challenge Mode features (sharing, leaderboards) will be disabled.
|
| 150 |
+
|
| 151 |
## Folder Structure
|
| 152 |
|
| 153 |
- `app.py` – Streamlit entry point
|
|
|
|
| 175 |
## Changelog
|
| 176 |
|
| 177 |
### v0.3.0 (planned)
|
| 178 |
+
- Local persistent storage for personal game history (offline-capable)
|
| 179 |
+
- Personal high scores sidebar with filtering
|
| 180 |
+
- Player statistics tracking (games played, averages, bests)
|
| 181 |
+
|
| 182 |
+
-0.2.27
|
| 183 |
+
- Add "Show Challenge Share Links" setting (default: off)
|
| 184 |
+
- When disabled:
|
| 185 |
+
- Header Challenge Mode: hides the Share Challenge link
|
| 186 |
+
- Game Over: allows submitting results but suppresses displaying the generated share URL
|
| 187 |
+
- The setting is saved in session state and preserved across "New Game"
|
| 188 |
+
- No changes to game logic or storage; only UI visibility behavior
|
| 189 |
|
| 190 |
+
-0.2.26
|
| 191 |
- fix copy/share link button
|
| 192 |
|
| 193 |
-0.2.25
|
|
|
|
| 504 |
|
| 505 |
Happy gaming and sound designing!
|
| 506 |
|
| 507 |
+
## What's New in v0.2.20-0.2.27: Challenge Mode 🎯
|
| 508 |
+
|
| 509 |
+
### Remote Challenge Sharing 🔗
|
| 510 |
+
- Share challenges with friends via short URLs (`?game_id=<sid>`)
|
| 511 |
+
- Each player gets different random words from the same wordlist
|
| 512 |
+
- Multi-user leaderboards sorted by score and time
|
| 513 |
+
- Word list difficulty calculation and display
|
| 514 |
+
- Compare your performance against others!
|
| 515 |
+
|
| 516 |
+
### Leaderboards 🏆
|
| 517 |
+
- Top 5 players displayed in Challenge Mode banner
|
| 518 |
+
- Results sorted by: highest score → fastest time → highest difficulty
|
| 519 |
+
- Submit results to existing challenges or create new ones
|
| 520 |
+
- Player names supported (optional, defaults to "Anonymous")
|
| 521 |
+
|
| 522 |
+
### Remote Storage 💾
|
| 523 |
+
- Challenge data stored in Hugging Face dataset repositories
|
| 524 |
+
- Automatic save on game completion (with user consent)
|
| 525 |
+
- "Show Challenge Share Links" toggle for privacy control (default OFF)
|
| 526 |
+
- Works offline when HF credentials not configured
|
| 527 |
+
|
| 528 |
+
## What's Planned for v0.3.0
|
| 529 |
+
|
| 530 |
+
### Local Player History (Coming Soon)
|
| 531 |
+
- Personal game results saved locally in `~/.battlewords/data/`
|
| 532 |
+
- Offline-capable high score tracking
|
| 533 |
+
- Player statistics (games played, averages, bests)
|
| 534 |
+
- Privacy-first: no cloud dependency for personal data
|
| 535 |
+
- Easy data management (delete `~/.battlewords/data/` to reset)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
|
battlewords/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
-
__version__ = "0.2.
|
| 2 |
__all__ = ["models", "generator", "logic", "ui", "game_storage"]
|
|
|
|
| 1 |
+
__version__ = "0.2.27"
|
| 2 |
__all__ = ["models", "generator", "logic", "ui", "game_storage"]
|
battlewords/ui.py
CHANGED
|
@@ -430,6 +430,9 @@ def _init_session() -> None:
|
|
| 430 |
if "show_incorrect_guesses" not in st.session_state:
|
| 431 |
st.session_state.show_incorrect_guesses = True
|
| 432 |
|
|
|
|
|
|
|
|
|
|
| 433 |
|
| 434 |
def _new_game() -> None:
|
| 435 |
selected = st.session_state.get("selected_wordlist")
|
|
@@ -443,6 +446,9 @@ def _new_game() -> None:
|
|
| 443 |
music_volume = st.session_state.get("music_volume",15)
|
| 444 |
effects_volume = st.session_state.get("effects_volume",25)
|
| 445 |
enable_sound_effects = st.session_state.get("enable_sound_effects", True)
|
|
|
|
|
|
|
|
|
|
| 446 |
st.session_state.clear()
|
| 447 |
if selected:
|
| 448 |
st.session_state.selected_wordlist = selected
|
|
@@ -458,6 +464,9 @@ def _new_game() -> None:
|
|
| 458 |
st.session_state.music_volume = music_volume
|
| 459 |
st.session_state.effects_volume = effects_volume
|
| 460 |
st.session_state.enable_sound_effects = enable_sound_effects
|
|
|
|
|
|
|
|
|
|
| 461 |
st.session_state.radar_gif_path = None
|
| 462 |
st.session_state.radar_gif_signature = None
|
| 463 |
st.session_state.start_time = datetime.now() # Reset timer on new game
|
|
@@ -541,7 +550,8 @@ def _render_header():
|
|
| 541 |
# Get the challenge SID from session state
|
| 542 |
sid = st.session_state.get("loaded_game_sid")
|
| 543 |
share_html = ""
|
| 544 |
-
|
|
|
|
| 545 |
share_url = get_shareable_url(sid)
|
| 546 |
share_html = f"<div style='margin-top:1rem;margin-bottom:0.5rem;font-size: 0.9rem;'><a href='{share_url}' target='_blank' style='color:#FFF;text-decoration:underline;'><strong>🔗 Share this challenge</a></strong<br/><br/><span style='font-size:0.85em;color:#ddd;'>{share_url}</span>"
|
| 547 |
|
|
@@ -657,6 +667,11 @@ def _render_sidebar():
|
|
| 657 |
st.session_state.show_incorrect_guesses = True
|
| 658 |
st.checkbox("Show incorrect guesses", value=st.session_state.show_incorrect_guesses, key="show_incorrect_guesses")
|
| 659 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
# Audio settings
|
| 661 |
st.header("Audio")
|
| 662 |
tracks = get_audio_tracks()
|
|
@@ -1644,100 +1659,45 @@ def _game_over_content(state: GameState) -> None:
|
|
| 1644 |
except Exception as e:
|
| 1645 |
st.error(f"Failed to save game: {e}")
|
| 1646 |
else:
|
| 1647 |
-
#
|
| 1648 |
-
|
| 1649 |
-
|
| 1650 |
-
|
| 1651 |
-
|
| 1652 |
-
|
| 1653 |
-
|
| 1654 |
-
|
| 1655 |
-
|
| 1656 |
-
|
| 1657 |
-
|
| 1658 |
-
|
| 1659 |
-
|
| 1660 |
-
|
| 1661 |
-
|
| 1662 |
-
|
| 1663 |
-
|
| 1664 |
-
|
| 1665 |
-
|
| 1666 |
-
|
| 1667 |
-
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
|
| 1674 |
-
|
| 1675 |
-
|
| 1676 |
-
|
| 1677 |
-
|
| 1678 |
-
|
| 1679 |
-
|
| 1680 |
-
|
| 1681 |
-
|
| 1682 |
-
|
| 1683 |
-
|
| 1684 |
-
|
| 1685 |
-
|
| 1686 |
-
if (!navigator.clipboard || !window.isSecureContext) {{
|
| 1687 |
-
throw new Error('Clipboard API unavailable in this context');
|
| 1688 |
-
}}
|
| 1689 |
-
await navigator.clipboard.writeText(text);
|
| 1690 |
-
}}
|
| 1691 |
-
|
| 1692 |
-
function legacyCopy(text) {{
|
| 1693 |
-
const ta = document.createElement('textarea');
|
| 1694 |
-
ta.value = text;
|
| 1695 |
-
ta.setAttribute('readonly', '');
|
| 1696 |
-
ta.style.position = 'fixed';
|
| 1697 |
-
ta.style.top = '-1000px';
|
| 1698 |
-
ta.style.left = '-1000px';
|
| 1699 |
-
document.body.appendChild(ta);
|
| 1700 |
-
ta.focus();
|
| 1701 |
-
ta.select();
|
| 1702 |
-
let ok = false;
|
| 1703 |
-
try {{
|
| 1704 |
-
ok = document.execCommand('copy');
|
| 1705 |
-
}} finally {{
|
| 1706 |
-
document.body.removeChild(ta);
|
| 1707 |
-
}}
|
| 1708 |
-
if (!ok) throw new Error('execCommand failed');
|
| 1709 |
-
}}
|
| 1710 |
-
|
| 1711 |
-
btn.addEventListener('click', async () => {{
|
| 1712 |
-
try {{
|
| 1713 |
-
try {{
|
| 1714 |
-
await modernCopy(SHARE_URL);
|
| 1715 |
-
}} catch (e) {{
|
| 1716 |
-
legacyCopy(SHARE_URL);
|
| 1717 |
-
}}
|
| 1718 |
-
setStatus('Copied!');
|
| 1719 |
-
}} catch (e) {{
|
| 1720 |
-
// Final fallback: select input text and ask user to copy
|
| 1721 |
-
try {{
|
| 1722 |
-
input.removeAttribute('readonly');
|
| 1723 |
-
input.focus();
|
| 1724 |
-
input.select();
|
| 1725 |
-
input.setSelectionRange(0, input.value.length);
|
| 1726 |
-
document.execCommand('copy'); // may still work on some browsers
|
| 1727 |
-
input.setAttribute('readonly', '');
|
| 1728 |
-
setStatus('Copied!');
|
| 1729 |
-
}} catch (err) {{
|
| 1730 |
-
input.focus();
|
| 1731 |
-
input.select();
|
| 1732 |
-
setStatus('Press Ctrl+C');
|
| 1733 |
-
}}
|
| 1734 |
-
}}
|
| 1735 |
-
}});
|
| 1736 |
-
}})();
|
| 1737 |
-
</script>
|
| 1738 |
-
""",
|
| 1739 |
-
height=80
|
| 1740 |
-
)
|
| 1741 |
|
| 1742 |
st.markdown("---")
|
| 1743 |
|
|
|
|
| 430 |
if "show_incorrect_guesses" not in st.session_state:
|
| 431 |
st.session_state.show_incorrect_guesses = True
|
| 432 |
|
| 433 |
+
# NEW: Initialize Show Challenge Share Links (default OFF)
|
| 434 |
+
if "show_challenge_share_links" not in st.session_state:
|
| 435 |
+
st.session_state.show_challenge_share_links = False
|
| 436 |
|
| 437 |
def _new_game() -> None:
|
| 438 |
selected = st.session_state.get("selected_wordlist")
|
|
|
|
| 446 |
music_volume = st.session_state.get("music_volume",15)
|
| 447 |
effects_volume = st.session_state.get("effects_volume",25)
|
| 448 |
enable_sound_effects = st.session_state.get("enable_sound_effects", True)
|
| 449 |
+
# NEW: Preserve Show Challenge Share Links
|
| 450 |
+
show_challenge_share_links = st.session_state.get("show_challenge_share_links", False)
|
| 451 |
+
|
| 452 |
st.session_state.clear()
|
| 453 |
if selected:
|
| 454 |
st.session_state.selected_wordlist = selected
|
|
|
|
| 464 |
st.session_state.music_volume = music_volume
|
| 465 |
st.session_state.effects_volume = effects_volume
|
| 466 |
st.session_state.enable_sound_effects = enable_sound_effects
|
| 467 |
+
# NEW: Restore Show Challenge Share Links
|
| 468 |
+
st.session_state.show_challenge_share_links = show_challenge_share_links
|
| 469 |
+
|
| 470 |
st.session_state.radar_gif_path = None
|
| 471 |
st.session_state.radar_gif_signature = None
|
| 472 |
st.session_state.start_time = datetime.now() # Reset timer on new game
|
|
|
|
| 550 |
# Get the challenge SID from session state
|
| 551 |
sid = st.session_state.get("loaded_game_sid")
|
| 552 |
share_html = ""
|
| 553 |
+
# NEW: Only render share link when setting enabled
|
| 554 |
+
if sid and st.session_state.get("show_challenge_share_links", False):
|
| 555 |
share_url = get_shareable_url(sid)
|
| 556 |
share_html = f"<div style='margin-top:1rem;margin-bottom:0.5rem;font-size: 0.9rem;'><a href='{share_url}' target='_blank' style='color:#FFF;text-decoration:underline;'><strong>🔗 Share this challenge</a></strong<br/><br/><span style='font-size:0.85em;color:#ddd;'>{share_url}</span>"
|
| 557 |
|
|
|
|
| 667 |
st.session_state.show_incorrect_guesses = True
|
| 668 |
st.checkbox("Show incorrect guesses", value=st.session_state.show_incorrect_guesses, key="show_incorrect_guesses")
|
| 669 |
|
| 670 |
+
# NEW: Add Show Challenge Share Links option - default OFF
|
| 671 |
+
if "show_challenge_share_links" not in st.session_state:
|
| 672 |
+
st.session_state.show_challenge_share_links = False
|
| 673 |
+
st.checkbox("Show Challenge Share Links", value=st.session_state.show_challenge_share_links, key="show_challenge_share_links")
|
| 674 |
+
|
| 675 |
# Audio settings
|
| 676 |
st.header("Audio")
|
| 677 |
tracks = get_audio_tracks()
|
|
|
|
| 1659 |
except Exception as e:
|
| 1660 |
st.error(f"Failed to save game: {e}")
|
| 1661 |
else:
|
| 1662 |
+
# Conditionally display the generated share URL
|
| 1663 |
+
if st.session_state.get("show_challenge_share_links", False):
|
| 1664 |
+
# Display generated share URL
|
| 1665 |
+
share_url = st.session_state["share_url"]
|
| 1666 |
+
st.success("✅ Share link generated!")
|
| 1667 |
+
st.code(share_url, language=None)
|
| 1668 |
+
|
| 1669 |
+
# More robust copy-to-clipboard implementation with fallbacks
|
| 1670 |
+
import json as _json
|
| 1671 |
+
import html as _html
|
| 1672 |
+
|
| 1673 |
+
_share_url_json = _json.dumps(share_url) # safe for JS
|
| 1674 |
+
_share_url_attr = _html.escape(share_url, quote=True) # safe for HTML attribute
|
| 1675 |
+
_share_url_text = _html.escape(share_url)
|
| 1676 |
+
|
| 1677 |
+
components.html(
|
| 1678 |
+
f"""
|
| 1679 |
+
<div id="bw-copy-container" style="
|
| 1680 |
+
display:flex;
|
| 1681 |
+
gap:8px;
|
| 1682 |
+
width:100%;
|
| 1683 |
+
align-items:center;
|
| 1684 |
+
margin-top:6px;
|
| 1685 |
+
justify-content:center;
|
| 1686 |
+
">
|
| 1687 |
+
|
| 1688 |
+
<strong><a href="{_share_url_attr}"
|
| 1689 |
+
target="_blank"
|
| 1690 |
+
rel="noopener noreferrer"
|
| 1691 |
+
style="text-decoration: underline; color: #fff; word-break: break-all; filter: drop-shadow(1px 1px 2px #003);">
|
| 1692 |
+
{_share_url_text}
|
| 1693 |
+
</a></strong>
|
| 1694 |
+
</div>
|
| 1695 |
+
""",
|
| 1696 |
+
height=80
|
| 1697 |
+
)
|
| 1698 |
+
else:
|
| 1699 |
+
# Do not display the share URL, but confirm it’s saved/submitted
|
| 1700 |
+
st.success("✅ Your result has been saved.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1701 |
|
| 1702 |
st.markdown("---")
|
| 1703 |
|
claude.md
CHANGED
|
@@ -3,11 +3,20 @@
|
|
| 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 |
-
**Next Version:** 0.3.0 (In Development -
|
| 8 |
**Repository:** https://github.com/Oncorporation/BattleWords.git
|
| 9 |
**Live Demo:** https://huggingface.co/spaces/Surn/BattleWords
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
## Core Gameplay
|
| 12 |
- 12x12 grid with 6 hidden words (2×4-letter, 2×5-letter, 2×6-letter)
|
| 13 |
- Words placed horizontally or vertically, no overlaps
|
|
@@ -17,8 +26,9 @@ BattleWords is a vocabulary learning game inspired by Battleship mechanics, buil
|
|
| 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 |
-
-
|
| 21 |
-
-
|
|
|
|
| 22 |
|
| 23 |
### Scoring Tiers
|
| 24 |
- **Fantastic:** 42+ points
|
|
@@ -90,23 +100,38 @@ battlewords/
|
|
| 90 |
- **Ocean Theme:** Gradient animated background with wave effects
|
| 91 |
- **Incorrect Guess History:** Visual display of wrong guesses (toggleable in settings)
|
| 92 |
|
| 93 |
-
###
|
| 94 |
-
- **Game ID System:**
|
| 95 |
-
- Format: `?game_id
|
| 96 |
-
-
|
| 97 |
- Enables fair challenges between players
|
| 98 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
- Location: `~/.battlewords/data/`
|
| 100 |
- Files: `game_results.json`, `highscores.json`
|
| 101 |
-
- Privacy-first: no cloud dependency
|
| 102 |
-
- **High Scores:**
|
| 103 |
-
- Top 100 scores tracked automatically
|
| 104 |
- Filterable by wordlist and game mode
|
| 105 |
-
-
|
| 106 |
- **Player Statistics:**
|
| 107 |
- Games played, average score, best score
|
| 108 |
- Fastest completion time
|
| 109 |
-
- Per-player history
|
| 110 |
|
| 111 |
### Puzzle Generation
|
| 112 |
- Deterministic seeding support for reproducible puzzles
|
|
@@ -115,7 +140,6 @@ battlewords/
|
|
| 115 |
- 1: At least 1 blank cell between words (default)
|
| 116 |
- 2: At least 2 blank cells between words
|
| 117 |
- Validation ensures no overlaps, proper bounds, correct word distribution
|
| 118 |
-
- **v0.3.0:** Game ID system planned (not yet integrated into Puzzle model)
|
| 119 |
|
| 120 |
### UI Components (Current)
|
| 121 |
- **Radar Visualization:** Animated matplotlib GIF showing word boundaries
|
|
@@ -134,9 +158,14 @@ battlewords/
|
|
| 134 |
- **Theme System:** Ocean gradient background with CSS animations
|
| 135 |
- **Game Over Dialog:** Final score display with tier ranking
|
| 136 |
- **Incorrect Guess Display:** Shows history of wrong guesses with count
|
| 137 |
-
-
|
| 138 |
-
-
|
| 139 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
### Recent Changes & Branch Status
|
| 142 |
**Branch:** cc-01 (Storage and sharing features - v0.3.0 development)
|
|
@@ -159,7 +188,7 @@ battlewords/
|
|
| 159 |
- Set `fig.patch.set_alpha(0.0)` for transparent background
|
| 160 |
- Maintains 2% margin for tick visibility while ensuring consistent layer alignment
|
| 161 |
|
| 162 |
-
**
|
| 163 |
- ✅ Imported storage modules from OpenBadge project:
|
| 164 |
- `battlewords/modules/storage.py` (v0.1.5) - HuggingFace storage & URL shortener
|
| 165 |
- `battlewords/modules/constants.py` (trimmed) - Storage-related constants
|
|
@@ -171,18 +200,25 @@ battlewords/
|
|
| 171 |
- `generate_uid()` - Generate unique game identifiers
|
| 172 |
- `serialize_game_settings()` - Convert game data to JSON
|
| 173 |
- `get_shareable_url()` - Generate shareable URLs
|
|
|
|
| 174 |
- ✅ UI integration complete (`battlewords/ui.py`):
|
| 175 |
- Query parameter parsing for `?game_id=<sid>` on app load
|
| 176 |
- Load shared game settings into session state
|
| 177 |
-
- Challenge Mode banner
|
| 178 |
-
- Share button in game over dialog with "Generate Share Link"
|
| 179 |
-
-
|
| 180 |
- Automatic save to HuggingFace on game completion
|
|
|
|
| 181 |
- ✅ Generator updates (`battlewords/generator.py`):
|
| 182 |
- Added `target_words` parameter for loading specific words
|
| 183 |
- Added `may_overlap` parameter (for future crossword mode)
|
| 184 |
-
- Support for shared game replay with
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
|
| 187 |
## Data Models
|
| 188 |
|
|
@@ -247,6 +283,32 @@ docker run -p 8501:8501 battlewords
|
|
| 247 |
pytest tests/
|
| 248 |
```
|
| 249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
## Git Configuration & Deployment
|
| 251 |
**Current Branch:** cc-01
|
| 252 |
**Purpose:** Storage and sharing features (v0.3.0 development)
|
|
|
|
| 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.27 (Stable - Challenge Mode & Remote Storage)
|
| 7 |
+
**Next Version:** 0.3.0 (In Development - Local Player History & Statistics)
|
| 8 |
**Repository:** https://github.com/Oncorporation/BattleWords.git
|
| 9 |
**Live Demo:** https://huggingface.co/spaces/Surn/BattleWords
|
| 10 |
|
| 11 |
+
## Recent Changes & Branch Status
|
| 12 |
+
|
| 13 |
+
**Latest (v0.2.27):**
|
| 14 |
+
- New setting: "Show Challenge Share Links" (default OFF)
|
| 15 |
+
- Header Challenge Mode banner hides the "Share this challenge" link when disabled
|
| 16 |
+
- Game Over dialog still lets you submit or create a challenge, but hides the generated share URL when disabled (shows a success message instead)
|
| 17 |
+
- Setting is stored in Streamlit session state and preserved across "New Game"
|
| 18 |
+
- No changes to `logic.py` or `game_storage.py`; this is a UI-only visibility feature
|
| 19 |
+
|
| 20 |
## Core Gameplay
|
| 21 |
- 12x12 grid with 6 hidden words (2×4-letter, 2×5-letter, 2×6-letter)
|
| 22 |
- Words placed horizontally or vertically, no overlaps
|
|
|
|
| 26 |
- Game ends when all words are guessed or all word letters revealed
|
| 27 |
- Incorrect guess history with optional display (enabled by default)
|
| 28 |
- 10 incorrect guess limit per game
|
| 29 |
+
- **✅ IMPLEMENTED (v0.2.20+):** Challenge Mode with game sharing via short URLs
|
| 30 |
+
- **✅ IMPLEMENTED (v0.2.20+):** Remote storage via Hugging Face datasets for challenges and leaderboards
|
| 31 |
+
- **PLANNED (v0.3.0):** Local persistent storage for individual player results and high scores
|
| 32 |
|
| 33 |
### Scoring Tiers
|
| 34 |
- **Fantastic:** 42+ points
|
|
|
|
| 100 |
- **Ocean Theme:** Gradient animated background with wave effects
|
| 101 |
- **Incorrect Guess History:** Visual display of wrong guesses (toggleable in settings)
|
| 102 |
|
| 103 |
+
### ✅ Challenge Mode & Remote Storage (v0.2.20+)
|
| 104 |
+
- **Game ID System:** Short URL-based challenge sharing
|
| 105 |
+
- Format: `?game_id=<sid>` in URL (shortened URL reference)
|
| 106 |
+
- Each player gets different random words from the same wordlist
|
| 107 |
- Enables fair challenges between players
|
| 108 |
+
- Stored in Hugging Face dataset repository
|
| 109 |
+
- **Remote Storage via HuggingFace Hub:**
|
| 110 |
+
- Per-game settings JSON in `games/{uid}/settings.json`
|
| 111 |
+
- Shortened URL mapping in `shortener.json`
|
| 112 |
+
- Multi-user leaderboards with score, time, and difficulty tracking
|
| 113 |
+
- Results sorted by: highest score → fastest time → highest difficulty
|
| 114 |
+
- **Challenge Features:**
|
| 115 |
+
- Submit results to existing challenges
|
| 116 |
+
- Create new challenges from any completed game
|
| 117 |
+
- Top 5 leaderboard display in Challenge Mode banner
|
| 118 |
+
- Optional player names (defaults to "Anonymous")
|
| 119 |
+
- Word list difficulty calculation and display
|
| 120 |
+
- "Show Challenge Share Links" toggle (default OFF) to control URL visibility
|
| 121 |
+
|
| 122 |
+
### PLANNED: Local Player Storage (v0.3.0)
|
| 123 |
+
- **Local Storage:**
|
| 124 |
- Location: `~/.battlewords/data/`
|
| 125 |
- Files: `game_results.json`, `highscores.json`
|
| 126 |
+
- Privacy-first: no cloud dependency, offline-capable
|
| 127 |
+
- **Personal High Scores:**
|
| 128 |
+
- Top 100 scores tracked automatically on local machine
|
| 129 |
- Filterable by wordlist and game mode
|
| 130 |
+
- High score sidebar expander display
|
| 131 |
- **Player Statistics:**
|
| 132 |
- Games played, average score, best score
|
| 133 |
- Fastest completion time
|
| 134 |
+
- Per-player history on local device
|
| 135 |
|
| 136 |
### Puzzle Generation
|
| 137 |
- Deterministic seeding support for reproducible puzzles
|
|
|
|
| 140 |
- 1: At least 1 blank cell between words (default)
|
| 141 |
- 2: At least 2 blank cells between words
|
| 142 |
- Validation ensures no overlaps, proper bounds, correct word distribution
|
|
|
|
| 143 |
|
| 144 |
### UI Components (Current)
|
| 145 |
- **Radar Visualization:** Animated matplotlib GIF showing word boundaries
|
|
|
|
| 158 |
- **Theme System:** Ocean gradient background with CSS animations
|
| 159 |
- **Game Over Dialog:** Final score display with tier ranking
|
| 160 |
- **Incorrect Guess Display:** Shows history of wrong guesses with count
|
| 161 |
+
- **✅ Challenge Mode UI (v0.2.20+):**
|
| 162 |
+
- Challenge Mode banner with leaderboard (top 5 players)
|
| 163 |
+
- Share challenge button in game over dialog
|
| 164 |
+
- Submit result or create new challenge options
|
| 165 |
+
- Word list difficulty display
|
| 166 |
+
- Conditional share URL visibility toggle
|
| 167 |
+
- **PLANNED (v0.3.0):** Local high scores expander in sidebar
|
| 168 |
+
- **PLANNED (v0.3.0):** Personal statistics display
|
| 169 |
|
| 170 |
### Recent Changes & Branch Status
|
| 171 |
**Branch:** cc-01 (Storage and sharing features - v0.3.0 development)
|
|
|
|
| 188 |
- Set `fig.patch.set_alpha(0.0)` for transparent background
|
| 189 |
- Maintains 2% margin for tick visibility while ensuring consistent layer alignment
|
| 190 |
|
| 191 |
+
**Completed (v0.2.20-0.2.27 - Challenge Mode):**
|
| 192 |
- ✅ Imported storage modules from OpenBadge project:
|
| 193 |
- `battlewords/modules/storage.py` (v0.1.5) - HuggingFace storage & URL shortener
|
| 194 |
- `battlewords/modules/constants.py` (trimmed) - Storage-related constants
|
|
|
|
| 200 |
- `generate_uid()` - Generate unique game identifiers
|
| 201 |
- `serialize_game_settings()` - Convert game data to JSON
|
| 202 |
- `get_shareable_url()` - Generate shareable URLs
|
| 203 |
+
- `add_user_result_to_game()` - Append results to existing challenges
|
| 204 |
- ✅ UI integration complete (`battlewords/ui.py`):
|
| 205 |
- Query parameter parsing for `?game_id=<sid>` on app load
|
| 206 |
- Load shared game settings into session state
|
| 207 |
+
- Challenge Mode banner with leaderboard (top 5)
|
| 208 |
+
- Share button in game over dialog with "Generate Share Link" or "Submit Result"
|
| 209 |
+
- Conditional share URL display based on settings toggle
|
| 210 |
- Automatic save to HuggingFace on game completion
|
| 211 |
+
- Word list difficulty calculation and display
|
| 212 |
- ✅ Generator updates (`battlewords/generator.py`):
|
| 213 |
- Added `target_words` parameter for loading specific words
|
| 214 |
- Added `may_overlap` parameter (for future crossword mode)
|
| 215 |
+
- Support for shared game replay with randomized word positions
|
| 216 |
+
|
| 217 |
+
**In Progress (v0.3.0 - Local Player History):**
|
| 218 |
+
- ⏳ Local storage module (`battlewords/local_storage.py`)
|
| 219 |
+
- ⏳ Personal high score tracking (local JSON files)
|
| 220 |
+
- ⏳ High score sidebar UI display
|
| 221 |
+
- ⏳ Player statistics tracking and display
|
| 222 |
|
| 223 |
## Data Models
|
| 224 |
|
|
|
|
| 283 |
pytest tests/
|
| 284 |
```
|
| 285 |
|
| 286 |
+
### Environment Variables (for Challenge Mode)
|
| 287 |
+
Challenge Mode requires HuggingFace Hub access for remote storage. Create a `.env` file in the project root:
|
| 288 |
+
|
| 289 |
+
```bash
|
| 290 |
+
# Required for Challenge Mode
|
| 291 |
+
HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx # or HF_TOKEN
|
| 292 |
+
HF_REPO_ID=YourUsername/YourRepo # Target HF dataset repo
|
| 293 |
+
SPACE_NAME=YourUsername/BattleWords # Your HF Space name
|
| 294 |
+
|
| 295 |
+
# Optional
|
| 296 |
+
CRYPTO_PK= # Reserved for future signing
|
| 297 |
+
```
|
| 298 |
+
|
| 299 |
+
**How to get your HF_API_TOKEN:**
|
| 300 |
+
1. Go to https://huggingface.co/settings/tokens
|
| 301 |
+
2. Create a new token with `write` access
|
| 302 |
+
3. Add to `.env` file as `HF_API_TOKEN=hf_...`
|
| 303 |
+
|
| 304 |
+
**HF_REPO_ID Structure:**
|
| 305 |
+
The dataset repository will contain:
|
| 306 |
+
- `shortener.json` - Short URL mappings
|
| 307 |
+
- `games/{uid}/settings.json` - Per-game challenge data
|
| 308 |
+
- `games/{uid}/result.json` - Optional detailed results
|
| 309 |
+
|
| 310 |
+
**Note:** The app will work without these variables but Challenge Mode features (sharing, leaderboards) will be disabled.
|
| 311 |
+
|
| 312 |
## Git Configuration & Deployment
|
| 313 |
**Current Branch:** cc-01
|
| 314 |
**Purpose:** Storage and sharing features (v0.3.0 development)
|
specs/requirements.md
CHANGED
|
@@ -15,7 +15,9 @@ Streamlit key components (API usage plan)
|
|
| 15 |
- `st.session_state.letter_map` derived from puzzle.
|
| 16 |
- `st.session_state.selected_wordlist` for sidebar picker.
|
| 17 |
- `st.session_state.radar_gif_path` for session-persistent radar animation.
|
| 18 |
-
- `st.
|
|
|
|
|
|
|
| 19 |
- Layout & structure
|
| 20 |
- `st.title`, `st.subheader`, `st.markdown` for headers/instructions.
|
| 21 |
- `st.columns(12)` to render the 12×12 grid; `st.container` for grouping; `st.sidebar` for secondary controls/help.
|
|
@@ -189,8 +191,10 @@ D) Tests
|
|
| 189 |
- Test high score filtering and ranking
|
| 190 |
|
| 191 |
Milestones and Estimates (High-level)
|
| 192 |
-
- Phase 1 (POC): 2–4 days
|
| 193 |
-
- Phase 1.5 (Storage & Sharing): 2–3 days ⏳
|
|
|
|
|
|
|
| 194 |
- Beta (0.5.0): 3–5 days (overlaps, responsive UI, keyboard, deterministic seed)
|
| 195 |
- Phase 2 (Full): 1–2 weeks depending on features selected
|
| 196 |
|
|
@@ -212,52 +216,84 @@ Definitions of Done (per task)
|
|
| 212 |
- The app applies all settings from the file for a true replay.
|
| 213 |
- No direct encoding of game data in the query string; only the reference is shared.
|
| 214 |
|
| 215 |
-
## Phase 1.6: Remote Storage &
|
| 216 |
|
| 217 |
-
Goal
|
| 218 |
-
|
| 219 |
-
- Use a shortened `game_id` (sid) that resolves to settings.json for replay/sharing.
|
| 220 |
|
| 221 |
-
A) Storage Server Integration
|
| 222 |
-
-
|
| 223 |
- `upload_files_to_repo(...)` to write JSON to `HF_REPO_ID`
|
| 224 |
- `gen_full_url(...)` for shortener lookups/creation backed by `shortener.json`
|
| 225 |
-
-
|
| 226 |
-
- `
|
| 227 |
-
- (
|
| 228 |
-
-
|
| 229 |
-
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
-
|
| 233 |
-
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
-
|
| 245 |
-
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
-
|
| 251 |
-
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
-
|
| 255 |
-
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
-
|
| 263 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
- `st.session_state.letter_map` derived from puzzle.
|
| 16 |
- `st.session_state.selected_wordlist` for sidebar picker.
|
| 17 |
- `st.session_state.radar_gif_path` for session-persistent radar animation.
|
| 18 |
+
- `st.session_state.show_incorrect_guesses` toggle.
|
| 19 |
+
- `st.session_state.show_challenge_share_links` toggle (v0.2.27, default OFF) to control visibility of challenge share links in the header and Game Over dialog.
|
| 20 |
+
|
| 21 |
- Layout & structure
|
| 22 |
- `st.title`, `st.subheader`, `st.markdown` for headers/instructions.
|
| 23 |
- `st.columns(12)` to render the 12×12 grid; `st.container` for grouping; `st.sidebar` for secondary controls/help.
|
|
|
|
| 191 |
- Test high score filtering and ranking
|
| 192 |
|
| 193 |
Milestones and Estimates (High-level)
|
| 194 |
+
- Phase 1 (POC): 2–4 days ✅ COMPLETE
|
| 195 |
+
- Phase 1.5 (Local Storage & Sharing - planned): 2–3 days ⏳ PENDING (deferred to v0.3.0)
|
| 196 |
+
- Phase 1.6 (Remote Storage & Challenge Mode): 3–4 days ✅ COMPLETE (v0.2.20-0.2.27)
|
| 197 |
+
- Phase 1.7 (Local Player History): 2–3 days ⏳ IN PROGRESS (v0.3.0)
|
| 198 |
- Beta (0.5.0): 3–5 days (overlaps, responsive UI, keyboard, deterministic seed)
|
| 199 |
- Phase 2 (Full): 1–2 weeks depending on features selected
|
| 200 |
|
|
|
|
| 216 |
- The app applies all settings from the file for a true replay.
|
| 217 |
- No direct encoding of game data in the query string; only the reference is shared.
|
| 218 |
|
| 219 |
+
## Phase 1.6: Remote Storage & Challenge Mode (v0.2.20-0.2.27) ✅ COMPLETE
|
| 220 |
|
| 221 |
+
### Goal
|
| 222 |
+
Persist per-game settings and leaderboards on a storage server (Hugging Face Hub repo) with shortened URLs for challenge sharing.
|
|
|
|
| 223 |
|
| 224 |
+
### A) Storage Server Integration ✅
|
| 225 |
+
- Imported modules from OpenBadge `modules/storage.py`:
|
| 226 |
- `upload_files_to_repo(...)` to write JSON to `HF_REPO_ID`
|
| 227 |
- `gen_full_url(...)` for shortener lookups/creation backed by `shortener.json`
|
| 228 |
+
- Created `battlewords/game_storage.py` wrapper with functions:
|
| 229 |
+
- `save_game_to_hf()` - Save challenge and get short URL
|
| 230 |
+
- `load_game_from_sid()` - Load challenge from short ID
|
| 231 |
+
- `add_user_result_to_game()` - Append user result to existing challenge
|
| 232 |
+
- `get_shareable_url()` - Generate shareable URLs
|
| 233 |
+
- Repository structure in HF dataset:
|
| 234 |
+
- `shortener.json` - Short URL mappings
|
| 235 |
+
- `games/{uid}/settings.json` - Per-game challenge data with users array
|
| 236 |
+
- Required env vars (.env): `HF_API_TOKEN` (or `HF_TOKEN`), `HF_REPO_ID`, `SPACE_NAME`
|
| 237 |
+
|
| 238 |
+
### B) Sharing Link (game_id) ✅
|
| 239 |
+
- Shortened URL flow: `gen_full_url(full_url=...)` returns short id (sid)
|
| 240 |
+
- Shareable link format: `https://<SPACE_NAME>/?game_id=<sid>`
|
| 241 |
+
- On app load with `game_id`: fetch JSON, apply settings, show Challenge Mode banner
|
| 242 |
+
|
| 243 |
+
### C) Challenge Mode Features ✅
|
| 244 |
+
- Multi-user leaderboards with score, time, and difficulty tracking
|
| 245 |
+
- Results sorted by: highest score → fastest time → highest difficulty
|
| 246 |
+
- Challenge Mode UI banner showing top 5 players
|
| 247 |
+
- Submit result to existing challenge or create new challenge
|
| 248 |
+
- Word list difficulty calculation and display
|
| 249 |
+
- "Show Challenge Share Links" toggle (default OFF) for URL visibility control
|
| 250 |
+
- Each player gets different random words from the same wordlist source
|
| 251 |
+
|
| 252 |
+
### D) Dependencies ✅
|
| 253 |
+
- Added `huggingface_hub` and `python-dotenv` to requirements
|
| 254 |
+
- Module imports in `ui.py:30`
|
| 255 |
+
|
| 256 |
+
### E) Acceptance Criteria ✅
|
| 257 |
+
- ✅ Completed game produces working share link with `game_id` sid
|
| 258 |
+
- ✅ Visiting link reconstructs challenge with leaderboard
|
| 259 |
+
- ✅ Multiple users can submit results to same challenge
|
| 260 |
+
- ✅ Leaderboard displays and sorts correctly
|
| 261 |
+
- ✅ Documentation updated with env vars and flows
|
| 262 |
+
- ✅ App works without HF credentials (Challenge Mode features disabled gracefully)
|
| 263 |
+
|
| 264 |
+
### F) Implementation Files
|
| 265 |
+
- `battlewords/game_storage.py` - HF storage wrapper (v0.1.0)
|
| 266 |
+
- `battlewords/modules/storage.py` - Generic HF storage (v0.1.5)
|
| 267 |
+
- `battlewords/ui.py` - Challenge Mode UI integration (lines 508-601, 1588-1701)
|
| 268 |
+
- `battlewords/generator.py` - Support for target_words parameter
|
| 269 |
+
|
| 270 |
+
## Phase 1.7: Local Player History (v0.3.0) ⏳ IN PROGRESS
|
| 271 |
+
|
| 272 |
+
### Goal
|
| 273 |
+
Add local persistent storage for individual player game results and personal high scores (offline-capable, privacy-first).
|
| 274 |
+
|
| 275 |
+
### A) Local Storage Module
|
| 276 |
+
- Create `battlewords/local_storage.py` with:
|
| 277 |
+
- `GameResult` and `HighScoreEntry` dataclasses
|
| 278 |
+
- JSON-based storage in `~/.battlewords/data/`
|
| 279 |
+
- Functions: `save_game_result()`, `load_high_scores()`, `get_player_stats()`
|
| 280 |
+
- Storage location: `~/.battlewords/data/` (game_results.json, highscores.json)
|
| 281 |
+
|
| 282 |
+
### B) High Score Display
|
| 283 |
+
- Sidebar expander for personal high scores
|
| 284 |
+
- Filter by: All-time, Current Wordlist, Current Mode
|
| 285 |
+
- Top 10 entries with: Rank, Player, Score, Tier, Time
|
| 286 |
+
- Player name input (optional, defaults to "Anonymous")
|
| 287 |
+
|
| 288 |
+
### C) Player Statistics
|
| 289 |
+
- Games played, average score, best score
|
| 290 |
+
- Fastest completion time
|
| 291 |
+
- Per-player history tracking
|
| 292 |
+
|
| 293 |
+
### D) Acceptance Criteria
|
| 294 |
+
- Local JSON files created and updated on game completion
|
| 295 |
+
- High scores display correctly in sidebar
|
| 296 |
+
- Filters work as expected
|
| 297 |
+
- Player names saved with results
|
| 298 |
+
- No cloud dependency required
|
| 299 |
+
- Easy data deletion (remove ~/.battlewords/data/)
|
specs/specs.md
CHANGED
|
@@ -78,11 +78,20 @@ Battlewords is inspired by the classic Battleship game, but uses words instead o
|
|
| 78 |
- Game ID display and share button in game over dialog.
|
| 79 |
- High score expander in sidebar.
|
| 80 |
- Player name input in sidebar.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
## Word List
|
| 83 |
- External list at `battlewords/words/wordlist.txt`.
|
| 84 |
- Loaded by `battlewords.word_loader.load_word_list()` with caching.
|
| 85 |
-
- Filtered to uppercase A�Z, lengths in {4,5,6}; falls back if < 25 per length.
|
| 86 |
|
| 87 |
## Generator
|
| 88 |
- Centralized word loader.
|
|
@@ -92,6 +101,58 @@ Battlewords is inspired by the classic Battleship game, but uses words instead o
|
|
| 92 |
- The Streamlit entry point is `app.py`.
|
| 93 |
- **A `Dockerfile` can be used for containerized deployment (recommended for Hugging Face Spaces).**
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
## Copyright
|
| 96 |
BattlewordsTM. All Rights Reserved. All content, trademarks and logos are copyrighted by the owner.
|
| 97 |
|
|
@@ -113,7 +174,7 @@ High Scores
|
|
| 113 |
- Local highscores remain supported for offline use
|
| 114 |
|
| 115 |
UI/UX
|
| 116 |
-
- Show the current `game_id` (sid) and a �Share Challenge� link
|
| 117 |
- When loading with a `game_id`, indicate the puzzle is a shared challenge
|
| 118 |
|
| 119 |
Security/Privacy
|
|
|
|
| 78 |
- Game ID display and share button in game over dialog.
|
| 79 |
- High score expander in sidebar.
|
| 80 |
- Player name input in sidebar.
|
| 81 |
+
- Checkbox: "Show Challenge Share Links" (v0.2.27, default OFF)
|
| 82 |
+
- When OFF:
|
| 83 |
+
- Challenge Mode header hides the Share Challenge link
|
| 84 |
+
- Game Over dialog still supports submitting/creating challenges, but does not display the generated share URL
|
| 85 |
+
- Persisted in session state and preserved across "New Game"
|
| 86 |
+
|
| 87 |
+
## New Features (v0.2.27)
|
| 88 |
+
- Added "Show Challenge Share Links" visibility toggle for Challenge Mode sharing UI
|
| 89 |
+
- Purely a UI change; gameplay logic and storage behavior unchanged
|
| 90 |
|
| 91 |
## Word List
|
| 92 |
- External list at `battlewords/words/wordlist.txt`.
|
| 93 |
- Loaded by `battlewords.word_loader.load_word_list()` with caching.
|
| 94 |
+
- Filtered to uppercase A�Z, lengths in {4,5,6}; falls back if < 25 per length.
|
| 95 |
|
| 96 |
## Generator
|
| 97 |
- Centralized word loader.
|
|
|
|
| 101 |
- The Streamlit entry point is `app.py`.
|
| 102 |
- **A `Dockerfile` can be used for containerized deployment (recommended for Hugging Face Spaces).**
|
| 103 |
|
| 104 |
+
## Deployment Requirements
|
| 105 |
+
|
| 106 |
+
### Basic Deployment (Offline Mode)
|
| 107 |
+
No special configuration needed. The app will run with all core gameplay features.
|
| 108 |
+
|
| 109 |
+
### Challenge Mode Deployment (Remote Storage)
|
| 110 |
+
Requires HuggingFace Hub integration for challenge sharing and leaderboards.
|
| 111 |
+
|
| 112 |
+
**Required Environment Variables:**
|
| 113 |
+
```bash
|
| 114 |
+
HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx # or HF_TOKEN (write access required)
|
| 115 |
+
HF_REPO_ID=YourUsername/YourRepo # Target HF dataset repository
|
| 116 |
+
SPACE_NAME=YourUsername/BattleWords # Your HF Space name for URL generation
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
**Optional Environment Variables:**
|
| 120 |
+
```bash
|
| 121 |
+
CRYPTO_PK= # Reserved for future challenge signing
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
**Setup Steps:**
|
| 125 |
+
1. Create a HuggingFace account at https://huggingface.co
|
| 126 |
+
2. Create a dataset repository (e.g., `YourUsername/BattleWordsStorage`)
|
| 127 |
+
3. Generate an access token with `write` permissions:
|
| 128 |
+
- Go to https://huggingface.co/settings/tokens
|
| 129 |
+
- Click "New token"
|
| 130 |
+
- Select "Write" access
|
| 131 |
+
- Copy the token (starts with `hf_`)
|
| 132 |
+
4. Create a `.env` file in project root with the variables above
|
| 133 |
+
5. For Hugging Face Spaces deployment, add these as Space secrets
|
| 134 |
+
|
| 135 |
+
**Repository Structure (automatically created):**
|
| 136 |
+
```
|
| 137 |
+
HF_REPO_ID/
|
| 138 |
+
├── shortener.json # Short URL mappings (sid -> full URL)
|
| 139 |
+
└── games/
|
| 140 |
+
└── {uid}/
|
| 141 |
+
└── settings.json # Challenge data with users array
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
**Data Privacy:**
|
| 145 |
+
- Challenge Mode stores: word lists, scores, times, game modes, player names
|
| 146 |
+
- No PII beyond optional player name (defaults to "Anonymous")
|
| 147 |
+
- Players control URL visibility via "Show Challenge Share Links" setting
|
| 148 |
+
- App functions fully offline when HF credentials not configured
|
| 149 |
+
|
| 150 |
+
**Deployment Platforms:**
|
| 151 |
+
- Local development: Run with `streamlit run app.py`
|
| 152 |
+
- Docker: Use provided `Dockerfile`
|
| 153 |
+
- Hugging Face Spaces: Dockerfile deployment (recommended)
|
| 154 |
+
- Any Python 3.10+ hosting with Streamlit support
|
| 155 |
+
|
| 156 |
## Copyright
|
| 157 |
BattlewordsTM. All Rights Reserved. All content, trademarks and logos are copyrighted by the owner.
|
| 158 |
|
|
|
|
| 174 |
- Local highscores remain supported for offline use
|
| 175 |
|
| 176 |
UI/UX
|
| 177 |
+
- Show the current `game_id` (sid) and a �Share Challenge� link
|
| 178 |
- When loading with a `game_id`, indicate the puzzle is a shared challenge
|
| 179 |
|
| 180 |
Security/Privacy
|