Spaces:
Running
Running
Ron Notes 9/29/2025
Browse files- battlewords/logic.py +1 -1
- battlewords/ui.py +90 -19
- battlewords/words/classic.txt +1 -0
battlewords/logic.py
CHANGED
|
@@ -48,7 +48,7 @@ def guess_word(state: GameState, guess_text: str) -> Tuple[bool, int]:
|
|
| 48 |
break
|
| 49 |
|
| 50 |
if target is None:
|
| 51 |
-
state.last_action = f"'{guess}' is not in the puzzle."
|
| 52 |
state.can_guess = False
|
| 53 |
return False, 0
|
| 54 |
|
|
|
|
| 48 |
break
|
| 49 |
|
| 50 |
if target is None:
|
| 51 |
+
state.last_action = f"Try Again! '{guess}' is not in the puzzle."
|
| 52 |
state.can_guess = False
|
| 53 |
return False, 0
|
| 54 |
|
battlewords/ui.py
CHANGED
|
@@ -82,11 +82,16 @@ def inject_styles() -> None:
|
|
| 82 |
.bw-final-score { color: #1ca41c !important; font-weight: 800; }
|
| 83 |
|
| 84 |
/* Make grid buttons square and fill their column */
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
div[data-testid="stButton"] button {
|
| 86 |
-
width: 100%;
|
|
|
|
| 87 |
aspect-ratio: 1 / 1;
|
| 88 |
border-radius: 0;
|
| 89 |
-
border: 1px solid #1d64c8;
|
| 90 |
background: #1d64c8;
|
| 91 |
color: #ffffff;
|
| 92 |
font-weight: 700;
|
|
@@ -220,7 +225,7 @@ def _init_session() -> None:
|
|
| 220 |
# Ensure a default selection exists before creating the puzzle
|
| 221 |
files = get_wordlist_files()
|
| 222 |
if "selected_wordlist" not in st.session_state and files:
|
| 223 |
-
st.session_state.selected_wordlist = "
|
| 224 |
if "game_mode" not in st.session_state:
|
| 225 |
st.session_state.game_mode = "standard"
|
| 226 |
|
|
@@ -292,12 +297,7 @@ def _render_header():
|
|
| 292 |
|
| 293 |
def _render_sidebar():
|
| 294 |
with st.sidebar:
|
| 295 |
-
st.header("
|
| 296 |
-
st.button("New Game", width="stretch", on_click=_new_game)
|
| 297 |
-
st.markdown(
|
| 298 |
-
"- Radar pulses show the last letter position of each hidden word.\n"
|
| 299 |
-
"- After each reveal, you may submit one word guess below.\n"
|
| 300 |
-
"- Scoring: length + unrevealed letters of that word at guess time.")
|
| 301 |
|
| 302 |
st.header("Game Mode")
|
| 303 |
game_modes = ["standard", "easy"]
|
|
@@ -332,7 +332,7 @@ def _render_sidebar():
|
|
| 332 |
on_change=_new_game, # immediately start a new game with the selected list
|
| 333 |
)
|
| 334 |
|
| 335 |
-
if st.button("Sort Wordlist"):
|
| 336 |
_sort_wordlist(st.session_state.selected_wordlist)
|
| 337 |
else:
|
| 338 |
st.info("No word lists found in words/ directory. Using built-in fallback.")
|
|
@@ -542,6 +542,18 @@ def _render_grid(state: GameState, letter_map):
|
|
| 542 |
max-width:300px !important;
|
| 543 |
margin: 0 auto !important;
|
| 544 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
</style>
|
| 546 |
""",
|
| 547 |
unsafe_allow_html=True,
|
|
@@ -623,13 +635,50 @@ def _render_hit_miss(state: GameState):
|
|
| 623 |
)
|
| 624 |
|
| 625 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
def _render_guess_form(state: GameState):
|
| 627 |
with st.form("guess_form"):
|
| 628 |
guess_text = st.text_input("Your guess", value="", max_chars=12)
|
| 629 |
submitted = st.form_submit_button("OK", disabled=not state.can_guess, width="stretch")
|
| 630 |
if submitted:
|
| 631 |
correct, _ = guess_word(state, guess_text)
|
| 632 |
-
_sync_back(state)
|
|
|
|
| 633 |
|
| 634 |
|
| 635 |
def _render_score_panel(state: GameState):
|
|
@@ -642,10 +691,20 @@ def _render_score_panel(state: GameState):
|
|
| 642 |
_render_game_over(state)
|
| 643 |
else:
|
| 644 |
with st.expander("Game summary", expanded=True):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
for w in state.puzzle.words:
|
| 646 |
pts = state.points_by_word.get(w.text, 0)
|
| 647 |
if pts > 0 or state.game_mode=="easy":
|
| 648 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
st.markdown(f"**Total**: {state.score}")
|
| 650 |
|
| 651 |
|
|
@@ -659,9 +718,19 @@ def _render_game_over(state: GameState):
|
|
| 659 |
)
|
| 660 |
|
| 661 |
with st.expander("Game summary", expanded=True):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
for w in state.puzzle.words:
|
| 663 |
pts = state.points_by_word.get(w.text, 0)
|
| 664 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 665 |
st.markdown(f"**Total**: {state.score}")
|
| 666 |
|
| 667 |
st.stop()
|
|
@@ -699,15 +768,17 @@ def run_app():
|
|
| 699 |
left, right = st.columns([2, 2], gap="medium")
|
| 700 |
with left:
|
| 701 |
_render_grid(state, st.session_state.letter_map)
|
|
|
|
| 702 |
|
| 703 |
with right:
|
| 704 |
-
|
|
|
|
| 705 |
with one:
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
_render_score_panel(state)
|
| 712 |
|
| 713 |
|
|
|
|
| 82 |
.bw-final-score { color: #1ca41c !important; font-weight: 800; }
|
| 83 |
|
| 84 |
/* Make grid buttons square and fill their column */
|
| 85 |
+
div[data-testid="stButton"]{
|
| 86 |
+
margin: 0 auto;
|
| 87 |
+
text-align: center;
|
| 88 |
+
}
|
| 89 |
div[data-testid="stButton"] button {
|
| 90 |
+
max-width: 100%;
|
| 91 |
+
width: auto;
|
| 92 |
aspect-ratio: 1 / 1;
|
| 93 |
border-radius: 0;
|
| 94 |
+
#border: 1px solid #1d64c8;
|
| 95 |
background: #1d64c8;
|
| 96 |
color: #ffffff;
|
| 97 |
font-weight: 700;
|
|
|
|
| 225 |
# Ensure a default selection exists before creating the puzzle
|
| 226 |
files = get_wordlist_files()
|
| 227 |
if "selected_wordlist" not in st.session_state and files:
|
| 228 |
+
st.session_state.selected_wordlist = "classic.txt"
|
| 229 |
if "game_mode" not in st.session_state:
|
| 230 |
st.session_state.game_mode = "standard"
|
| 231 |
|
|
|
|
| 297 |
|
| 298 |
def _render_sidebar():
|
| 299 |
with st.sidebar:
|
| 300 |
+
st.header("SETTINGS")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
st.header("Game Mode")
|
| 303 |
game_modes = ["standard", "easy"]
|
|
|
|
| 332 |
on_change=_new_game, # immediately start a new game with the selected list
|
| 333 |
)
|
| 334 |
|
| 335 |
+
if st.button("Sort Wordlist", width="content"):
|
| 336 |
_sort_wordlist(st.session_state.selected_wordlist)
|
| 337 |
else:
|
| 338 |
st.info("No word lists found in words/ directory. Using built-in fallback.")
|
|
|
|
| 542 |
max-width:300px !important;
|
| 543 |
margin: 0 auto !important;
|
| 544 |
}
|
| 545 |
+
.st-emotion-cache-ig7yu6 {
|
| 546 |
+
width: calc(30% - 1rem);
|
| 547 |
+
flex: 1 1 calc(20% - 1rem);
|
| 548 |
+
}
|
| 549 |
+
@media (max-width: 640px) {
|
| 550 |
+
.st-emotion-cache-1s1xxaz {
|
| 551 |
+
min-width: calc(33% - 1.5rem);
|
| 552 |
+
}
|
| 553 |
+
.st-emotion-cache-ig7yu6 {
|
| 554 |
+
min-width: calc(30% - 1.5rem);
|
| 555 |
+
}
|
| 556 |
+
}
|
| 557 |
</style>
|
| 558 |
""",
|
| 559 |
unsafe_allow_html=True,
|
|
|
|
| 635 |
)
|
| 636 |
|
| 637 |
|
| 638 |
+
def _render_correct_try_again(state: GameState):
|
| 639 |
+
# Determine last guess outcome from last_action string
|
| 640 |
+
action = (state.last_action or "").strip()
|
| 641 |
+
is_correct = action.startswith("Correct!")
|
| 642 |
+
is_try_again = action.startswith("Try Again!")
|
| 643 |
+
|
| 644 |
+
st.markdown(
|
| 645 |
+
"""
|
| 646 |
+
<style>
|
| 647 |
+
.bw-radio-caption.inactive { display: none !important; }
|
| 648 |
+
</style>
|
| 649 |
+
""",
|
| 650 |
+
unsafe_allow_html=True,
|
| 651 |
+
)
|
| 652 |
+
|
| 653 |
+
st.markdown(
|
| 654 |
+
f"""
|
| 655 |
+
<div class="bw-radio-group" role="radiogroup" aria-label="Correct or Try Again">
|
| 656 |
+
<div class="bw-radio-item">
|
| 657 |
+
<div class="bw-radio-circle {'active hit' if is_correct else ''}" role="radio" aria-checked="{'true' if is_correct else 'false'}" aria-label="Correct">
|
| 658 |
+
<span class="dot"></span>
|
| 659 |
+
</div>
|
| 660 |
+
<div class="bw-radio-caption{' inactive' if not is_correct else ''}">CORRECT!</div>
|
| 661 |
+
</div>
|
| 662 |
+
<div class="bw-radio-item">
|
| 663 |
+
<div class="bw-radio-circle {'active miss' if is_try_again else ''}" role="radio" aria-checked="{'true' if is_try_again else 'false'}" aria-label="Try Again">
|
| 664 |
+
<span class="dot"></span>
|
| 665 |
+
</div>
|
| 666 |
+
<div class="bw-radio-caption{' inactive' if not is_try_again else ''}">TRY AGAIN</div>
|
| 667 |
+
</div>
|
| 668 |
+
</div>
|
| 669 |
+
""",
|
| 670 |
+
unsafe_allow_html=True,
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
|
| 674 |
def _render_guess_form(state: GameState):
|
| 675 |
with st.form("guess_form"):
|
| 676 |
guess_text = st.text_input("Your guess", value="", max_chars=12)
|
| 677 |
submitted = st.form_submit_button("OK", disabled=not state.can_guess, width="stretch")
|
| 678 |
if submitted:
|
| 679 |
correct, _ = guess_word(state, guess_text)
|
| 680 |
+
_sync_back(state)
|
| 681 |
+
st.rerun() # Immediately rerun to reflect guess result in UI
|
| 682 |
|
| 683 |
|
| 684 |
def _render_score_panel(state: GameState):
|
|
|
|
| 691 |
_render_game_over(state)
|
| 692 |
else:
|
| 693 |
with st.expander("Game summary", expanded=True):
|
| 694 |
+
header_cols = st.columns([1, 1, 1])
|
| 695 |
+
header_cols[0].markdown("**Word**")
|
| 696 |
+
header_cols[1].markdown("**Letters**")
|
| 697 |
+
header_cols[2].markdown("**Extra**")
|
| 698 |
for w in state.puzzle.words:
|
| 699 |
pts = state.points_by_word.get(w.text, 0)
|
| 700 |
if pts > 0 or state.game_mode=="easy":
|
| 701 |
+
word_display = w.text
|
| 702 |
+
letters_display = str(len(w.text))
|
| 703 |
+
extra_display = f"+{pts} points"
|
| 704 |
+
row_cols = st.columns([1, 1, 1])
|
| 705 |
+
row_cols[0].markdown(f"{word_display}")
|
| 706 |
+
row_cols[1].markdown(f"{letters_display}")
|
| 707 |
+
row_cols[2].markdown(f"{extra_display}")
|
| 708 |
st.markdown(f"**Total**: {state.score}")
|
| 709 |
|
| 710 |
|
|
|
|
| 718 |
)
|
| 719 |
|
| 720 |
with st.expander("Game summary", expanded=True):
|
| 721 |
+
header_cols = st.columns([2, 1, 2])
|
| 722 |
+
header_cols[0].markdown("**Word**")
|
| 723 |
+
header_cols[1].markdown("**Letters**")
|
| 724 |
+
header_cols[2].markdown("**Extra**")
|
| 725 |
for w in state.puzzle.words:
|
| 726 |
pts = state.points_by_word.get(w.text, 0)
|
| 727 |
+
word_display = w.text
|
| 728 |
+
letters_display = str(len(w.text))
|
| 729 |
+
extra_display = f"+{pts} points"
|
| 730 |
+
row_cols = st.columns([1, 1, 1])
|
| 731 |
+
row_cols[0].markdown(f"{word_display}")
|
| 732 |
+
row_cols[1].markdown(f"{letters_display}")
|
| 733 |
+
row_cols[2].markdown(f"{extra_display}")
|
| 734 |
st.markdown(f"**Total**: {state.score}")
|
| 735 |
|
| 736 |
st.stop()
|
|
|
|
| 768 |
left, right = st.columns([2, 2], gap="medium")
|
| 769 |
with left:
|
| 770 |
_render_grid(state, st.session_state.letter_map)
|
| 771 |
+
st.button("New Game", width="content", on_click=_new_game)
|
| 772 |
|
| 773 |
with right:
|
| 774 |
+
_render_radar(state.puzzle, size=state.grid_size, r_max=1.6, max_frames=60, sinusoid_expand=False, stagger_radar=True)
|
| 775 |
+
one, two = st.columns([1, 5], gap="medium")
|
| 776 |
with one:
|
| 777 |
+
_render_correct_try_again(state)
|
| 778 |
+
#_render_hit_miss(state)
|
| 779 |
+
with two:
|
| 780 |
+
_render_guess_form(state)
|
| 781 |
+
#st.divider()
|
| 782 |
_render_score_panel(state)
|
| 783 |
|
| 784 |
|
battlewords/words/classic.txt
CHANGED
|
@@ -266,6 +266,7 @@ WORD
|
|
| 266 |
YARD
|
| 267 |
YELL
|
| 268 |
ZONE
|
|
|
|
| 269 |
ABOVE
|
| 270 |
AGAIN
|
| 271 |
AGREE
|
|
|
|
| 266 |
YARD
|
| 267 |
YELL
|
| 268 |
ZONE
|
| 269 |
+
ABOUT
|
| 270 |
ABOVE
|
| 271 |
AGAIN
|
| 272 |
AGREE
|