Spaces:
Running
Running
debug reveal
Browse files- .gitignore +3 -0
- app.py +8 -0
- battlewords/__init__.py +1 -0
- battlewords/logic.py +3 -3
- battlewords/models.py +1 -1
- battlewords/ui.py +32 -9
.gitignore
CHANGED
|
@@ -342,6 +342,9 @@ paket-files/
|
|
| 342 |
# Python Tools for Visual Studio (PTVS)
|
| 343 |
**/__pycache__/
|
| 344 |
*.pyc
|
|
|
|
|
|
|
|
|
|
| 345 |
|
| 346 |
# Cake - Uncomment if you are using it
|
| 347 |
#tools/**
|
|
|
|
| 342 |
# Python Tools for Visual Studio (PTVS)
|
| 343 |
**/__pycache__/
|
| 344 |
*.pyc
|
| 345 |
+
**/**/__pycache__/
|
| 346 |
+
**/*.pyc
|
| 347 |
+
|
| 348 |
|
| 349 |
# Cake - Uncomment if you are using it
|
| 350 |
#tools/**
|
app.py
CHANGED
|
@@ -1,6 +1,14 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
|
| 3 |
from battlewords.ui import run_app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
def main():
|
| 5 |
st.set_page_config(page_title="Battlewords (POC)", layout="wide")
|
| 6 |
run_app()
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
|
| 3 |
from battlewords.ui import run_app
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def _new_game() -> None:
|
| 7 |
+
st.session_state.clear()
|
| 8 |
+
_init_session()
|
| 9 |
+
st.rerun()
|
| 10 |
+
|
| 11 |
+
|
| 12 |
def main():
|
| 13 |
st.set_page_config(page_title="Battlewords (POC)", layout="wide")
|
| 14 |
run_app()
|
battlewords/__init__.py
CHANGED
|
@@ -1 +1,2 @@
|
|
|
|
|
| 1 |
__all__ = ["models", "generator", "logic", "ui"]
|
|
|
|
| 1 |
+
__version__ = "0.1.0"
|
| 2 |
__all__ = ["models", "generator", "logic", "ui"]
|
battlewords/logic.py
CHANGED
|
@@ -19,8 +19,8 @@ def reveal_cell(state: GameState, letter_map: Dict[Coord, str], coord: Coord) ->
|
|
| 19 |
return
|
| 20 |
state.revealed.add(coord)
|
| 21 |
state.can_guess = True
|
| 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:
|
| 26 |
state.last_action = f"Revealed '{ch}' at ({coord.x+1},{coord.y+1})."
|
|
@@ -32,7 +32,7 @@ def guess_word(state: GameState, guess_text: str) -> Tuple[bool, int]:
|
|
| 32 |
return False, 0
|
| 33 |
guess = (guess_text or "").strip().upper()
|
| 34 |
if not (len(guess) in (4, 5, 6) and guess.isalpha()):
|
| 35 |
-
state.last_action = "Guess must be A
|
| 36 |
state.can_guess = False
|
| 37 |
return False, 0
|
| 38 |
if guess in state.guessed:
|
|
|
|
| 19 |
return
|
| 20 |
state.revealed.add(coord)
|
| 21 |
state.can_guess = True
|
| 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:
|
| 26 |
state.last_action = f"Revealed '{ch}' at ({coord.x+1},{coord.y+1})."
|
|
|
|
| 32 |
return False, 0
|
| 33 |
guess = (guess_text or "").strip().upper()
|
| 34 |
if not (len(guess) in (4, 5, 6) and guess.isalpha()):
|
| 35 |
+
state.last_action = "Guess must be A–Z and length 4, 5, or 6."
|
| 36 |
state.can_guess = False
|
| 37 |
return False, 0
|
| 38 |
if guess in state.guessed:
|
battlewords/models.py
CHANGED
|
@@ -28,7 +28,7 @@ class Word:
|
|
| 28 |
if self.direction not in ("H", "V"):
|
| 29 |
raise ValueError("direction must be 'H' or 'V'")
|
| 30 |
if not self.text.isalpha():
|
| 31 |
-
raise ValueError("word must be alphabetic A
|
| 32 |
# compute cells based on start and direction
|
| 33 |
length = len(self.text)
|
| 34 |
cells: List[Coord] = []
|
|
|
|
| 28 |
if self.direction not in ("H", "V"):
|
| 29 |
raise ValueError("direction must be 'H' or 'V'")
|
| 30 |
if not self.text.isalpha():
|
| 31 |
+
raise ValueError("word must be alphabetic A–Z only")
|
| 32 |
# compute cells based on start and direction
|
| 33 |
length = len(self.text)
|
| 34 |
cells: List[Coord] = []
|
battlewords/ui.py
CHANGED
|
@@ -32,7 +32,6 @@ def _init_session() -> None:
|
|
| 32 |
def _new_game() -> None:
|
| 33 |
st.session_state.clear()
|
| 34 |
_init_session()
|
| 35 |
-
st.rerun()
|
| 36 |
|
| 37 |
|
| 38 |
def _to_state() -> GameState:
|
|
@@ -63,13 +62,13 @@ def _render_header():
|
|
| 63 |
st.markdown(
|
| 64 |
"- Grid is 12×12 with 6 words (two 4-letter, two 5-letter, two 6-letter).\n"
|
| 65 |
"- After each reveal, you may submit one guess.\n"
|
| 66 |
-
"- Scoring: length + unrevealed letters of that word at guess time."
|
| 67 |
-
|
| 68 |
|
| 69 |
def _render_sidebar():
|
| 70 |
with st.sidebar:
|
| 71 |
st.header("Controls")
|
| 72 |
-
st.button("New Game",
|
| 73 |
st.markdown("Radar pulses show the last letter position of each hidden word.")
|
| 74 |
|
| 75 |
|
|
@@ -84,7 +83,7 @@ def _render_radar(puzzle: Puzzle, size: int):
|
|
| 84 |
ax.set_yticks(range(1, size + 1))
|
| 85 |
ax.grid(True, which="both", linestyle="--", alpha=0.3)
|
| 86 |
ax.set_title("Radar")
|
| 87 |
-
st.pyplot(fig,
|
| 88 |
plt.close(fig)
|
| 89 |
|
| 90 |
|
|
@@ -92,6 +91,30 @@ def _render_grid(state: GameState, letter_map):
|
|
| 92 |
size = state.grid_size
|
| 93 |
clicked: Optional[Coord] = None
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
grid_container = st.container()
|
| 96 |
with grid_container:
|
| 97 |
for r in range(size):
|
|
@@ -101,20 +124,20 @@ def _render_grid(state: GameState, letter_map):
|
|
| 101 |
revealed = coord in state.revealed
|
| 102 |
label = letter_map.get(coord, " ") if revealed else " "
|
| 103 |
key = f"cell_{r}_{c}"
|
| 104 |
-
# Make revealed cells look disabled via help text; but still keep them clickable for UX feedback
|
| 105 |
if cols[c].button(label, key=key, help=f"({r+1},{c+1})"):
|
| 106 |
-
|
|
|
|
| 107 |
|
| 108 |
if clicked is not None:
|
| 109 |
reveal_cell(state, letter_map, clicked)
|
|
|
|
| 110 |
_sync_back(state)
|
| 111 |
-
# No need to st.rerun(); Streamlit will rerun after button click
|
| 112 |
|
| 113 |
|
| 114 |
def _render_guess_form(state: GameState):
|
| 115 |
with st.form("guess_form"):
|
| 116 |
guess_text = st.text_input("Your guess", value="", max_chars=12)
|
| 117 |
-
submitted = st.form_submit_button("OK", disabled=not state.can_guess,
|
| 118 |
if submitted:
|
| 119 |
correct, _ = guess_word(state, guess_text)
|
| 120 |
_sync_back(state)
|
|
|
|
| 32 |
def _new_game() -> None:
|
| 33 |
st.session_state.clear()
|
| 34 |
_init_session()
|
|
|
|
| 35 |
|
| 36 |
|
| 37 |
def _to_state() -> GameState:
|
|
|
|
| 62 |
st.markdown(
|
| 63 |
"- Grid is 12×12 with 6 words (two 4-letter, two 5-letter, two 6-letter).\n"
|
| 64 |
"- After each reveal, you may submit one guess.\n"
|
| 65 |
+
"- Scoring: length + unrevealed letters of that word at guess time.")
|
| 66 |
+
|
| 67 |
|
| 68 |
def _render_sidebar():
|
| 69 |
with st.sidebar:
|
| 70 |
st.header("Controls")
|
| 71 |
+
st.button("New Game", width="stretch", on_click=_new_game)
|
| 72 |
st.markdown("Radar pulses show the last letter position of each hidden word.")
|
| 73 |
|
| 74 |
|
|
|
|
| 83 |
ax.set_yticks(range(1, size + 1))
|
| 84 |
ax.grid(True, which="both", linestyle="--", alpha=0.3)
|
| 85 |
ax.set_title("Radar")
|
| 86 |
+
st.pyplot(fig, width="stretch")
|
| 87 |
plt.close(fig)
|
| 88 |
|
| 89 |
|
|
|
|
| 91 |
size = state.grid_size
|
| 92 |
clicked: Optional[Coord] = None
|
| 93 |
|
| 94 |
+
# Inject CSS for grid lines and button styling
|
| 95 |
+
st.markdown(
|
| 96 |
+
"""
|
| 97 |
+
<style>
|
| 98 |
+
div[data-testid="column"] {
|
| 99 |
+
padding: 0 !important;
|
| 100 |
+
}
|
| 101 |
+
button[data-testid="stButton"] {
|
| 102 |
+
width: 32px !important;
|
| 103 |
+
height: 32px !important;
|
| 104 |
+
min-width: 32px !important;
|
| 105 |
+
min-height: 32px !important;
|
| 106 |
+
padding: 0 !important;
|
| 107 |
+
margin: 0 !important;
|
| 108 |
+
border: 1px solid #888 !important;
|
| 109 |
+
background: #fff !important;
|
| 110 |
+
font-weight: bold;
|
| 111 |
+
font-size: 1rem;
|
| 112 |
+
}
|
| 113 |
+
</style>
|
| 114 |
+
""",
|
| 115 |
+
unsafe_allow_html=True,
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
grid_container = st.container()
|
| 119 |
with grid_container:
|
| 120 |
for r in range(size):
|
|
|
|
| 124 |
revealed = coord in state.revealed
|
| 125 |
label = letter_map.get(coord, " ") if revealed else " "
|
| 126 |
key = f"cell_{r}_{c}"
|
|
|
|
| 127 |
if cols[c].button(label, key=key, help=f"({r+1},{c+1})"):
|
| 128 |
+
if not revealed:
|
| 129 |
+
clicked = coord
|
| 130 |
|
| 131 |
if clicked is not None:
|
| 132 |
reveal_cell(state, letter_map, clicked)
|
| 133 |
+
st.session_state.letter_map = build_letter_map(st.session_state.puzzle)
|
| 134 |
_sync_back(state)
|
|
|
|
| 135 |
|
| 136 |
|
| 137 |
def _render_guess_form(state: GameState):
|
| 138 |
with st.form("guess_form"):
|
| 139 |
guess_text = st.text_input("Your guess", value="", max_chars=12)
|
| 140 |
+
submitted = st.form_submit_button("OK", disabled=not state.can_guess, width="stretch")
|
| 141 |
if submitted:
|
| 142 |
correct, _ = guess_word(state, guess_text)
|
| 143 |
_sync_back(state)
|