Spaces:
Running
Running
v0.1.9
Browse filesadd background
naming update
version info in settings
- app.py +1 -1
- battlewords/__init__.py +1 -1
- battlewords/logic.py +3 -1
- battlewords/ui.py +51 -2
- battlewords/version_info.py +37 -0
app.py
CHANGED
|
@@ -11,7 +11,7 @@ def _new_game() -> None:
|
|
| 11 |
|
| 12 |
def main(opened=False):
|
| 13 |
st.set_page_config(
|
| 14 |
-
page_title="Battlewords
|
| 15 |
layout="wide",
|
| 16 |
initial_sidebar_state="expanded" if opened else "collapsed"
|
| 17 |
)
|
|
|
|
| 11 |
|
| 12 |
def main(opened=False):
|
| 13 |
st.set_page_config(
|
| 14 |
+
page_title="Battlewords",
|
| 15 |
layout="wide",
|
| 16 |
initial_sidebar_state="expanded" if opened else "collapsed"
|
| 17 |
)
|
battlewords/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
-
__version__ = "0.1.
|
| 2 |
__all__ = ["models", "generator", "logic", "ui"]
|
|
|
|
| 1 |
+
__version__ = "0.1.9"
|
| 2 |
__all__ = ["models", "generator", "logic", "ui"]
|
battlewords/logic.py
CHANGED
|
@@ -18,8 +18,10 @@ def reveal_cell(state: GameState, letter_map: Dict[Coord, str], coord: Coord) ->
|
|
| 18 |
state.last_action = "Already revealed."
|
| 19 |
return
|
| 20 |
state.revealed.add(coord)
|
| 21 |
-
|
| 22 |
ch = letter_map.get(coord, "路")
|
|
|
|
|
|
|
| 23 |
if ch == "路":
|
| 24 |
state.last_action = f"Revealed empty at ({coord.x+1},{coord.y+1})."
|
| 25 |
else:
|
|
|
|
| 18 |
state.last_action = "Already revealed."
|
| 19 |
return
|
| 20 |
state.revealed.add(coord)
|
| 21 |
+
# Determine if this reveal uncovered a letter or an empty cell
|
| 22 |
ch = letter_map.get(coord, "路")
|
| 23 |
+
# Only allow guessing if a letter was revealed; preserve existing True (e.g., after a correct guess)
|
| 24 |
+
state.can_guess = state.can_guess or (ch != "路")
|
| 25 |
if ch == "路":
|
| 26 |
state.last_action = f"Revealed empty at ({coord.x+1},{coord.y+1})."
|
| 27 |
else:
|
battlewords/ui.py
CHANGED
|
@@ -15,6 +15,7 @@ from .generator import generate_puzzle, sort_word_file
|
|
| 15 |
from .logic import build_letter_map, reveal_cell, guess_word, is_game_over, compute_tier
|
| 16 |
from .models import Coord, GameState, Puzzle
|
| 17 |
from .word_loader import get_wordlist_files, load_word_list # use loader directly
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
CoordLike = Tuple[int, int]
|
|
@@ -50,11 +51,53 @@ def _build_letter_map(puzzle) -> dict[CoordLike, str]:
|
|
| 50 |
letters[xy] = text[i]
|
| 51 |
return letters
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
def inject_styles() -> None:
|
| 55 |
st.markdown(
|
| 56 |
"""
|
| 57 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
.stMainBlockContainer {
|
| 59 |
max-width: 1100px;
|
| 60 |
}
|
|
@@ -297,7 +340,7 @@ def _sync_back(state: GameState) -> None:
|
|
| 297 |
|
| 298 |
|
| 299 |
def _render_header():
|
| 300 |
-
st.title(f"Battlewords
|
| 301 |
st.subheader("Reveal cells, then guess the hidden words.")
|
| 302 |
st.markdown(
|
| 303 |
"- Grid is 12脳12 with 6 words (two 4-letter, two 5-letter, two 6-letter).\n"
|
|
@@ -349,6 +392,7 @@ def _render_sidebar():
|
|
| 349 |
_sort_wordlist(st.session_state.selected_wordlist)
|
| 350 |
else:
|
| 351 |
st.info("No word lists found in words/ directory. Using built-in fallback.")
|
|
|
|
| 352 |
|
| 353 |
def get_scope_image(size=4, bgcolor="none", scope_color="green", img_name="scope.gif"):
|
| 354 |
scope_path = os.path.join(os.path.dirname(__file__), img_name)
|
|
@@ -750,6 +794,9 @@ def _render_game_over(state: GameState):
|
|
| 750 |
row_cols[2].markdown(f"{extra_display}")
|
| 751 |
st.markdown(f"**Total**: {state.score}")
|
| 752 |
|
|
|
|
|
|
|
|
|
|
| 753 |
st.stop()
|
| 754 |
|
| 755 |
|
|
@@ -775,6 +822,7 @@ def _sort_wordlist(filename):
|
|
| 775 |
|
| 776 |
def run_app():
|
| 777 |
_init_session()
|
|
|
|
| 778 |
_render_header()
|
| 779 |
_render_sidebar()
|
| 780 |
|
|
@@ -802,4 +850,5 @@ def run_app():
|
|
| 802 |
# End condition
|
| 803 |
state = _to_state()
|
| 804 |
if is_game_over(state):
|
| 805 |
-
_render_game_over(state)
|
|
|
|
|
|
| 15 |
from .logic import build_letter_map, reveal_cell, guess_word, is_game_over, compute_tier
|
| 16 |
from .models import Coord, GameState, Puzzle
|
| 17 |
from .word_loader import get_wordlist_files, load_word_list # use loader directly
|
| 18 |
+
from .version_info import versions_html # version info footer
|
| 19 |
|
| 20 |
|
| 21 |
CoordLike = Tuple[int, int]
|
|
|
|
| 51 |
letters[xy] = text[i]
|
| 52 |
return letters
|
| 53 |
|
| 54 |
+
ocean_background_css = """
|
| 55 |
+
<style>
|
| 56 |
+
.stApp {
|
| 57 |
+
margin: 0;
|
| 58 |
+
height: 100vh;
|
| 59 |
+
background: linear-gradient(
|
| 60 |
+
45deg,
|
| 61 |
+
rgba(29, 100, 200, 0.6) 0%,
|
| 62 |
+
rgba(50, 120, 220, 0.8) 25%,
|
| 63 |
+
rgba(20, 80, 180, 0.95) 50%,
|
| 64 |
+
rgba(50, 120, 220, 0.8) 75%,
|
| 65 |
+
rgba(29, 100, 200, 0.6) 100%
|
| 66 |
+
);
|
| 67 |
+
background-size: 200% 200%;
|
| 68 |
+
animation: oceanFlow 12s ease-in-out infinite;
|
| 69 |
+
overflow: hidden;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
/* Animation for rolling water effect */
|
| 73 |
+
@keyframes oceanFlow {
|
| 74 |
+
0% {
|
| 75 |
+
background-position: 0% 50%;
|
| 76 |
+
}
|
| 77 |
+
50% {
|
| 78 |
+
background-position: 100% 50%;
|
| 79 |
+
}
|
| 80 |
+
100% {
|
| 81 |
+
background-position: 0% 50%;
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/* Ensure Streamlit content is visible above the background */
|
| 86 |
+
.stApp > div {
|
| 87 |
+
position: relative;
|
| 88 |
+
z-index: 1;
|
| 89 |
+
}
|
| 90 |
+
</style>
|
| 91 |
+
"""
|
| 92 |
|
| 93 |
def inject_styles() -> None:
|
| 94 |
st.markdown(
|
| 95 |
"""
|
| 96 |
<style>
|
| 97 |
+
/* Center main content and limit width */
|
| 98 |
+
# .stApp, body {
|
| 99 |
+
# background: rgba(29, 100, 200, 0.5);
|
| 100 |
+
# }
|
| 101 |
.stMainBlockContainer {
|
| 102 |
max-width: 1100px;
|
| 103 |
}
|
|
|
|
| 340 |
|
| 341 |
|
| 342 |
def _render_header():
|
| 343 |
+
st.title(f"Battlewords v{version}")
|
| 344 |
st.subheader("Reveal cells, then guess the hidden words.")
|
| 345 |
st.markdown(
|
| 346 |
"- Grid is 12脳12 with 6 words (two 4-letter, two 5-letter, two 6-letter).\n"
|
|
|
|
| 392 |
_sort_wordlist(st.session_state.selected_wordlist)
|
| 393 |
else:
|
| 394 |
st.info("No word lists found in words/ directory. Using built-in fallback.")
|
| 395 |
+
st.markdown(versions_html(), unsafe_allow_html=True)
|
| 396 |
|
| 397 |
def get_scope_image(size=4, bgcolor="none", scope_color="green", img_name="scope.gif"):
|
| 398 |
scope_path = os.path.join(os.path.dirname(__file__), img_name)
|
|
|
|
| 794 |
row_cols[2].markdown(f"{extra_display}")
|
| 795 |
st.markdown(f"**Total**: {state.score}")
|
| 796 |
|
| 797 |
+
# footer version info on game over as well
|
| 798 |
+
st.markdown(versions_html(), unsafe_allow_html=True)
|
| 799 |
+
|
| 800 |
st.stop()
|
| 801 |
|
| 802 |
|
|
|
|
| 822 |
|
| 823 |
def run_app():
|
| 824 |
_init_session()
|
| 825 |
+
st.markdown(ocean_background_css, unsafe_allow_html=True)
|
| 826 |
_render_header()
|
| 827 |
_render_sidebar()
|
| 828 |
|
|
|
|
| 850 |
# End condition
|
| 851 |
state = _to_state()
|
| 852 |
if is_game_over(state):
|
| 853 |
+
_render_game_over(state)
|
| 854 |
+
|
battlewords/version_info.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
try:
|
| 5 |
+
import streamlit as st
|
| 6 |
+
except Exception: # pragma: no cover
|
| 7 |
+
st = None
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def commit_hash() -> str:
|
| 11 |
+
"""Return current git commit hash or '<none>' if unavailable."""
|
| 12 |
+
try:
|
| 13 |
+
return subprocess.check_output(["git", "rev-parse", "HEAD"], shell=False, encoding="utf-8").strip()
|
| 14 |
+
except Exception:
|
| 15 |
+
return "<none>"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def versions_html() -> str:
|
| 19 |
+
"""Return a small HTML snippet with runtime and app environment info.
|
| 20 |
+
|
| 21 |
+
Includes: git commit, Python version, and Streamlit version.
|
| 22 |
+
"""
|
| 23 |
+
python_version = ".".join(str(x) for x in sys.version_info[:3])
|
| 24 |
+
full_python = sys.version.replace("\n", " ")
|
| 25 |
+
commit = commit_hash()
|
| 26 |
+
streamlit_version = getattr(st, "__version__", "not installed")
|
| 27 |
+
|
| 28 |
+
html = f"""
|
| 29 |
+
<div style="font-size: 0.85rem; color: #b7c3d0; margin-top: 1.5rem; padding-top: 0.5rem; border-top: 1px solid rgba(255,255,255,0.15);">
|
| 30 |
+
<span>commit: <code>{commit}</code></span>
|
| 31 |
+
|
|
| 32 |
+
<span>python: <span title="{full_python}">{python_version}</span></span>
|
| 33 |
+
|
|
| 34 |
+
<span>streamlit: {streamlit_version}</span>
|
| 35 |
+
</div>
|
| 36 |
+
"""
|
| 37 |
+
return html
|