Surn commited on
Commit
ca7de5e
·
1 Parent(s): d6fd282

Enhance UI with modal overlays and improve UX

Browse files

Updated `README.md` to reflect version `0.2.0` and document new features, including a loading screen and a congratulations screen for the final score and tier.

Replaced `st.expander`-based layouts in `_render_score_panel` and `_render_game_over` with HTML-based tables and modals for better styling, readability, and layout control. Added a modal overlay for the game-over screen with a detailed score summary, close button, and "New Game" button.

Introduced query parameter handling in `run_app` to allow dismissal of the game-over overlay, persisting the state in `st.session_state`.

Improved session state handling, fixed minor HTML inconsistencies, and enhanced the overall appearance and user experience.

Files changed (3) hide show
  1. README.md +20 -33
  2. battlewords/__init__.py +1 -1
  3. battlewords/ui.py +57 -38
README.md CHANGED
@@ -104,52 +104,39 @@ docker run -p 8501:8501 battlewords
104
  4. **The game ends when all six words are found or all word letters are revealed. Your score tier is displayed.**
105
 
106
  ## Changelog
107
- - 0.1.12
108
- - Updated the version to 0.1.12. Added new CSS classes `.bold-text` and `.blue-background` for improved text and background styling. Modified `.shiny-border` to adjust padding and border-radius for a more polished appearance.
109
- - Refactored `_render_score_panel` to replace the `st.expander`-based layout with an HTML-based table, introducing independent column backgrounds, visible gaps, and a new "extra points" calculation. Added a total row with enhanced styling.
110
- - Reorganized `run_app` for better layout structure and removed redundant state assignment.
111
 
 
 
 
 
 
 
 
112
  - 0.1.11
113
- - Added game-ending condition: the game now ends when all six words are guessed or all word letters are revealed.
114
- - implemented word spacing logic to prevent adjacent partial words and allow for future enhancements like overlaps.
115
- - implement loading html overlay and hide grid until ready.
116
- - Update to Game Summary table: shows words, base points, bonus points, total score; styled with Streamlit's `st.table`.
117
- - Show Grid Ticks and Space between words are now in settings.
118
- - Increase default font size for better readability.
119
- - Game end now modifies the grid display to show all words and disables further interaction.
120
- - Update to model for future enhancements
121
-
122
  - 0.1.10
123
- - Sidebar Game Mode selector: `standard` (allows guess chaining after correct guesses) and `too easy` (no chaining; score panel reveals words list).
124
- - Replaced Hit/Miss with Correct/Try Again status indicator for guesses.
125
- - UI polish: animated ocean background; metal-scope styled radar retained with session-cached GIF.
126
- - Footer shows version info (commit, Python, Streamlit).
127
- - Word list handling: defaults to `classic.txt` when present; selection persists across new games; sort action rewrites the file and restarts after 5s notice.
128
- - Documentation updated.
129
- - fixed wordlist selection persistence bug
130
 
131
  - 0.1.9
132
- - Background naming cleanup and consistency.
133
- - Version info surfaced in Settings panel.
134
- - Mobile layout improvements.
135
 
136
  - 0.1.8
137
- - Bumped runtime to Python 3.12 (and config/docs updated accordingly).
138
 
139
  - 0.1.5
140
- - Hit/Miss circular indicator.
141
- - Completed words render as non-interactive styled cells.
142
- - Tooltips show cell coordinates for buttons and revealed cells.
143
- - Stable letter map rebuild after reveals.
144
 
145
  - 0.1.4
146
- - Radar rewritten as an animated GIF with metallic gradient background and scope overlay.
147
- - Session-level caching of the generated radar GIF to avoid regeneration during a session.
148
- - Mobile layout improvements: radar above grid on small screens; tighter grid gaps and horizontal scrolling per row.
149
 
150
  - 0.1.3
151
- - Sidebar wordlist picker and `Sort Wordlist` action (length-first then alphabetic) with a 5-second feedback delay before restarting a new game.
152
- - Score panel improvements, per-word points, and emphasized final score styling.
153
 
154
  ## Known Issues / TODO
155
 
 
104
  4. **The game ends when all six words are found or all word letters are revealed. Your score tier is displayed.**
105
 
106
  ## Changelog
107
+ - 0.2.0
108
+ - Added a loading screen when starting a new game.
109
+ - Added a congratulations screen with your final score and tier when the game ends.
 
110
 
111
+ - 0.1.13
112
+ - Improved score summary layout for clarity and style.
113
+
114
+ - 0.1.12
115
+ - Improved score summary layout and styling.
116
+ - Enhanced overall appearance and readability.
117
+
118
  - 0.1.11
119
+ - Game now ends when all words are found or revealed.
120
+ - Added word spacing logic and improved settings.
121
+
 
 
 
 
 
 
122
  - 0.1.10
123
+ - Added game mode selector and improved UI feedback.
 
 
 
 
 
 
124
 
125
  - 0.1.9
126
+ - Improved background and mobile layout.
 
 
127
 
128
  - 0.1.8
129
+ - Updated to Python 3.12.
130
 
131
  - 0.1.5
132
+ - Added hit/miss indicator and improved grid feedback.
 
 
 
133
 
134
  - 0.1.4
135
+ - Radar visualization improved and mobile layout enhanced.
 
 
136
 
137
  - 0.1.3
138
+ - Added wordlist picker and sort feature.
139
+ - Improved score panel and final score display.
140
 
141
  ## Known Issues / TODO
142
 
battlewords/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.1.12"
2
  __all__ = ["models", "generator", "logic", "ui"]
 
1
+ __version__ = "0.2.0"
2
  __all__ = ["models", "generator", "logic", "ui"]
battlewords/ui.py CHANGED
@@ -834,12 +834,8 @@ def _render_guess_form(state: GameState):
834
 
835
 
836
  def _render_score_panel(state: GameState):
837
- # col1, col2 = st.columns([1, 3])
838
- # with col1:
839
- # st.metric("Score", state.score)
840
- # with col2:
841
- # st.markdown(f"Last action: {state.last_action}")
842
- if is_game_over(state):
843
  _render_game_over(state)
844
  else:
845
  # Build a simple table with independent column backgrounds and visible gaps
@@ -863,9 +859,9 @@ def _render_score_panel(state: GameState):
863
  extra_pts = max(0, pts - letters_display)
864
  row_html = (
865
  "<tr>"
866
- f"<td class=\"blue-background \">{word_display}</td>"
867
- f"<td class=\"blue-background \">{letters_display}</td>"
868
- f"<td class=\"blue-background \">{extra_pts}</td>"
869
  "</tr>"
870
  )
871
  rows_html.append(row_html)
@@ -881,33 +877,44 @@ def _render_score_panel(state: GameState):
881
 
882
 
883
  def _render_game_over(state: GameState):
884
- st.subheader("Game Over")
885
- tier = compute_tier(state.score)
886
- # Final score in green
887
- st.markdown(
888
- f"<span class=\"bw-final-score\">Final score: {state.score}</span> Tier: <strong>{tier}</strong>",
889
- unsafe_allow_html=True,
 
 
 
 
 
 
 
 
 
 
 
 
890
  )
891
-
892
- with st.expander("Game summary", expanded=True):
893
- header_cols = st.columns([2, 1, 2])
894
- header_cols[0].markdown("**Word**")
895
- header_cols[1].markdown("**Letters**")
896
- header_cols[2].markdown("**Extra**")
897
- for w in state.puzzle.words:
898
- pts = state.points_by_word.get(w.text, 0)
899
- word_display = w.text
900
- letters_display = str(len(w.text))
901
- extra_display = f"+{pts} points"
902
- row_cols = st.columns([1, 1, 1])
903
- row_cols[0].markdown(f"{word_display}")
904
- row_cols[1].markdown(f"{letters_display}")
905
- row_cols[2].markdown(f"{extra_display}")
906
- st.markdown(f"**Total**: {state.score}")
907
-
908
- # footer version info on game over as well
909
  st.markdown(versions_html(), unsafe_allow_html=True)
910
-
911
  st.stop()
912
 
913
 
@@ -932,6 +939,19 @@ def _sort_wordlist(filename):
932
 
933
 
934
  def run_app():
 
 
 
 
 
 
 
 
 
 
 
 
 
935
  _init_session()
936
  st.markdown(ocean_background_css, unsafe_allow_html=True)
937
  _render_header()
@@ -956,8 +976,7 @@ def run_app():
956
  _render_grid(state, st.session_state.letter_map, show_grid_ticks=st.session_state.get("show_grid_ticks", True))
957
  st.button("New Game", width=125, on_click=_new_game, key="new_game_btn")
958
 
959
-
960
- # End condition
961
  state = _to_state()
962
- if is_game_over(state):
963
- _render_game_over(state)
 
834
 
835
 
836
  def _render_score_panel(state: GameState):
837
+ # Only show modal from here if game is over AND overlay wasn't dismissed
838
+ if is_game_over(state) and not st.session_state.get("hide_gameover_overlay", False):
 
 
 
 
839
  _render_game_over(state)
840
  else:
841
  # Build a simple table with independent column backgrounds and visible gaps
 
859
  extra_pts = max(0, pts - letters_display)
860
  row_html = (
861
  "<tr>"
862
+ f"<td class=\"blue-background \"'>{word_display}</td>"
863
+ f"<td class=\"blue-background \"'>{letters_display}</td>"
864
+ f"<td class=\"blue-background \"'>{extra_pts}</td>"
865
  "</tr>"
866
  )
867
  rows_html.append(row_html)
 
877
 
878
 
879
  def _render_game_over(state: GameState):
880
+ # Prepare table rows for words
881
+ word_rows = []
882
+ for w in state.puzzle.words:
883
+ pts = state.points_by_word.get(w.text, 0)
884
+ extra_pts = max(0, pts - len(w.text))
885
+ word_rows.append(
886
+ f"<tr><td class='blue-background'>{w.text}</td><td class='blue-background'>{len(w.text)}</td><td class='blue-background'>{extra_pts}</td></tr>"
887
+ )
888
+ table_html = (
889
+ "<table class='shiny-border' style=\"background: linear-gradient(-45deg, #a1a1a1, #ffffff, #a1a1a1, #666666); width:100%; margin: 0 auto;border-collapse:separate; border-spacing:0;\">"
890
+ "<tr>"
891
+ "<th class='blue-background bold-text'>Word</th>"
892
+ "<th class='blue-background bold-text'>Letters</th>"
893
+ "<th class='blue-background bold-text'>Extra</th>"
894
+ "</tr>"
895
+ f"{''.join(word_rows)}"
896
+ f"<tr class='blue-background'><td colspan='3'><h3 class='bold-text'>Total: {state.score}</h3></td></tr>"
897
+ "</table>"
898
  )
899
+ # Overlay HTML with close link (sets ?overlay=0)
900
+ st.markdown(
901
+ f'''
902
+ <div id="bw-modal-overlay" style="position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999;background:rgba(20,80,180,0.95);display:flex;flex-direction:column;justify-content:center;align-items:center;">
903
+ <div class="shiny-border" style="position:relative;background: linear-gradient(-45deg, #1d64c8, #ffffff, #1d64c8, #666666);padding:2rem 3rem;box-shadow:0 0 32px #1d64c8;min-width:340px;max-width:90vw; margin:8vh auto 5vh;">
904
+ <a href="?overlay=0" title="Close" style="position:absolute;top:12px;right:12px;display:inline-grid;place-items:center;width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,0.25);color:#fff;text-decoration:none;font-size:1.6rem;font-weight:700;">&times;</a>
905
+ <h1 style="color:#fff;font-size:2.5rem;margin-bottom:0.5rem;">Congratulations!</h1>
906
+ <h2 style="color:#fff;font-size:2rem;margin-bottom:1rem;">Game Over</h2>
907
+ <div style="font-size:1.5rem;color:#1d64c8;margin-bottom:1rem;">Final score: <span style="color:#1d64c8;font-weight:800;">{state.score}</span></div>
908
+ <div style="font-size:1.2rem;color:#fff;margin-bottom:2rem;">Tier: <strong>{compute_tier(state.score)}</strong></div>
909
+ <div style="margin-bottom:2rem;">{table_html}</div>
910
+ <div style="color:#fff;opacity:0.7;font-size:1rem;margin-bottom:2rem;background:#1d64c8;text-align:center;">Thank you for playing BattleWords!</div>
911
+ </div>
912
+ </div>
913
+ ''', unsafe_allow_html=True)
914
+ st.markdown("<div style='height:32px'></div>", unsafe_allow_html=True)
915
+ if st.button("New Game", key="modal_new_game_btn", help="Start a new game", type="primary"):
916
+ _new_game()
917
  st.markdown(versions_html(), unsafe_allow_html=True)
 
918
  st.stop()
919
 
920
 
 
939
 
940
 
941
  def run_app():
942
+ # Handle overlay dismissal via query params using new API
943
+ try:
944
+ params = st.query_params
945
+ except Exception:
946
+ params = {}
947
+ if params.get("overlay") == "0":
948
+ # Clear param and remember to hide overlay this session
949
+ try:
950
+ st.query_params.clear()
951
+ except Exception:
952
+ pass
953
+ st.session_state["hide_gameover_overlay"] = True
954
+
955
  _init_session()
956
  st.markdown(ocean_background_css, unsafe_allow_html=True)
957
  _render_header()
 
976
  _render_grid(state, st.session_state.letter_map, show_grid_ticks=st.session_state.get("show_grid_ticks", True))
977
  st.button("New Game", width=125, on_click=_new_game, key="new_game_btn")
978
 
979
+ # End condition (only show overlay if not dismissed)
 
980
  state = _to_state()
981
+ if is_game_over(state) and not st.session_state.get("hide_gameover_overlay", False):
982
+ _render_game_over(state)