Surn commited on
Commit
c2a19da
·
1 Parent(s): f6d7e8a

- remove background animation
- add "easy" mode (single guess per reveal)

README.md CHANGED
@@ -11,8 +11,9 @@ app_file: app.py
11
  tags:
12
  - game
13
  - vocabulary
14
- - streamlit
15
  - education
 
16
  ---
17
 
18
  # Wrdler
@@ -178,6 +179,10 @@ All test files must be placed in the `/tests` folder. This ensures a clean proje
178
 
179
  ## Changelog
180
 
 
 
 
 
181
  ### v0.0.7
182
  - fix guess bug - allowing guesses only after word guessed or letter revealed
183
 
 
11
  tags:
12
  - game
13
  - vocabulary
14
+ - streamlit
15
  - education
16
+ short_description: Fast paced word guessing game
17
  ---
18
 
19
  # Wrdler
 
179
 
180
  ## Changelog
181
 
182
+ ### v0.0.8
183
+ - remove background animation
184
+ - add "easy" mode (single guess per reveal)
185
+
186
  ### v0.0.7
187
  - fix guess bug - allowing guesses only after word guessed or letter revealed
188
 
wrdler/__init__.py CHANGED
@@ -8,5 +8,5 @@ Key differences from BattleWords:
8
  - 2 free letter guesses at game start
9
  """
10
 
11
- __version__ = "0.0.7"
12
  __all__ = ["models", "generator", "logic", "ui", "word_loader"]
 
8
  - 2 free letter guesses at game start
9
  """
10
 
11
+ __version__ = "0.0.8"
12
  __all__ = ["models", "generator", "logic", "ui", "word_loader"]
wrdler/game_storage.py CHANGED
@@ -84,7 +84,7 @@ def serialize_game_settings(
84
  username: Player's name
85
  score: Final score achieved
86
  time_seconds: Time taken to complete (in seconds)
87
- game_mode: Game mode ("classic" or "too_easy")
88
  grid_size: Grid width in columns (default: 8 for Wrdler's 8x6 grid)
89
  spacer: Word spacing configuration (not used in Wrdler, kept for compatibility)
90
  may_overlap: Whether words can overlap (always False in Wrdler)
@@ -291,7 +291,7 @@ def save_game_to_hf(
291
  username: Player's name
292
  score: Final score achieved
293
  time_seconds: Time taken to complete (in seconds)
294
- game_mode: Game mode ("classic" or "too_easy")
295
  grid_size: Grid width (default: 8 for 8x6 Wrdler grid)
296
  spacer: Word spacing (not used in Wrdler, kept for compatibility)
297
  may_overlap: Whether words can overlap (always False in Wrdler)
@@ -403,7 +403,7 @@ def load_game_from_sid(
403
  dict: Challenge settings containing:
404
  - challenge_id: Unique challenge identifier
405
  - wordlist_source: Source wordlist file (e.g., "classic.txt")
406
- - game_mode: Game mode ("classic" or "too_easy")
407
  - grid_size: Grid width (8 for Wrdler's 8x6 grid)
408
  - puzzle_options: Puzzle configuration (spacer=0, may_overlap=False)
409
  - users: Array of user results, each with:
 
84
  username: Player's name
85
  score: Final score achieved
86
  time_seconds: Time taken to complete (in seconds)
87
+ game_mode: Game mode ("classic", "easy" or "too easy")
88
  grid_size: Grid width in columns (default: 8 for Wrdler's 8x6 grid)
89
  spacer: Word spacing configuration (not used in Wrdler, kept for compatibility)
90
  may_overlap: Whether words can overlap (always False in Wrdler)
 
291
  username: Player's name
292
  score: Final score achieved
293
  time_seconds: Time taken to complete (in seconds)
294
+ game_mode: Game mode ("classic", "easy" or "too easy")
295
  grid_size: Grid width (default: 8 for 8x6 Wrdler grid)
296
  spacer: Word spacing (not used in Wrdler, kept for compatibility)
297
  may_overlap: Whether words can overlap (always False in Wrdler)
 
403
  dict: Challenge settings containing:
404
  - challenge_id: Unique challenge identifier
405
  - wordlist_source: Source wordlist file (e.g., "classic.txt")
406
+ - game_mode: Game mode ("classic", "easy" or "too easy")
407
  - grid_size: Grid width (8 for Wrdler's 8x6 grid)
408
  - puzzle_options: Puzzle configuration (spacer=0, may_overlap=False)
409
  - users: Array of user results, each with:
wrdler/logic.py CHANGED
@@ -74,8 +74,16 @@ def reveal_cell(state: GameState, letter_map: Dict[Coord, str], coord: Coord) ->
74
  state.revealed.add(coord)
75
  # Determine if this reveal uncovered a letter or an empty cell
76
  ch = letter_map.get(coord, "·")
77
- # Only allow guessing if a letter was revealed; preserve existing True (e.g., after a correct guess)
78
- state.can_guess = state.can_guess or (ch != "·")
 
 
 
 
 
 
 
 
79
  if ch == "·":
80
  state.last_action = f"Revealed empty at ({coord.x+1},{coord.y+1})."
81
  else:
@@ -184,11 +192,13 @@ def is_game_over(state: GameState) -> bool:
184
 
185
 
186
  def compute_tier(score: int) -> str:
187
- if score >= 42:
 
 
188
  return "Fantastic"
189
- if 38 <= score <= 41:
190
  return "Great"
191
- if 34 <= score <= 37:
192
  return "Good"
193
  return "Keep practicing"
194
 
 
74
  state.revealed.add(coord)
75
  # Determine if this reveal uncovered a letter or an empty cell
76
  ch = letter_map.get(coord, "·")
77
+
78
+ # Allow guessing after reveal depending on game mode:
79
+ # - classic: only if a letter was revealed
80
+ # - easy / too easy: after any reveal (letter or empty)
81
+ if state.game_mode in ("easy", "too easy"):
82
+ state.can_guess = True
83
+ else:
84
+ # Only allow guessing if a letter was revealed; preserve existing True
85
+ state.can_guess = state.can_guess or (ch != "·")
86
+
87
  if ch == "·":
88
  state.last_action = f"Revealed empty at ({coord.x+1},{coord.y+1})."
89
  else:
 
192
 
193
 
194
  def compute_tier(score: int) -> str:
195
+ if score >= 45:
196
+ return "Legendary"
197
+ if 42 <= score <= 44:
198
  return "Fantastic"
199
+ if 39 <= score <= 41:
200
  return "Great"
201
+ if 35 <= score <= 38:
202
  return "Good"
203
  return "Keep practicing"
204
 
wrdler/models.py CHANGED
@@ -99,7 +99,7 @@ class GameState:
99
  - score: Current score.
100
  - last_action: Last action message.
101
  - can_guess: Whether player can guess.
102
- - game_mode: Game mode ("classic" or "too easy").
103
  - points_by_word: Points earned per word.
104
  - start_time: Game start time.
105
  - end_time: Game end time.
@@ -114,7 +114,7 @@ class GameState:
114
  score: int = 0
115
  last_action: str = ""
116
  can_guess: bool = False
117
- game_mode: Literal["classic", "too easy"] = "classic"
118
  points_by_word: Dict[str, int] = field(default_factory=dict)
119
  start_time: Optional[datetime] = None
120
  end_time: Optional[datetime] = None
 
99
  - score: Current score.
100
  - last_action: Last action message.
101
  - can_guess: Whether player can guess.
102
+ - game_mode: Game mode ("classic", "easy" or "too easy").
103
  - points_by_word: Points earned per word.
104
  - start_time: Game start time.
105
  - end_time: Game end time.
 
114
  score: int = 0
115
  last_action: str = ""
116
  can_guess: bool = False
117
+ game_mode: Literal["classic", "easy", "too easy"] = "classic"
118
  points_by_word: Dict[str, int] = field(default_factory=dict)
119
  start_time: Optional[datetime] = None
120
  end_time: Optional[datetime] = None
wrdler/ui.py CHANGED
@@ -234,7 +234,7 @@ ocean_background_css = """
234
  repeating-linear-gradient(90deg, rgba(255,255,255,0.06) 0 1px, transparent 1px 18px);
235
  mix-blend-mode: screen;
236
  opacity: 0.10;
237
- animation: waveOverlayScroll 16s linear infinite;
238
  }
239
  .stIFrame {
240
  margin-bottom:25px;
@@ -300,27 +300,27 @@ def inject_ocean_layers() -> None:
300
  z-index: 11;
301
  background: radial-gradient(150% 100% at 50% -20%, rgba(255,255,255,0.10) 0%, transparent 60%);
302
  background-size: 150% 150%; /* reduced from 300% */
303
- animation: oceanHighlight 12s ease-in-out infinite; /* doubled from 6s */
304
  }
305
  .bw-bg-long {
306
  z-index: 12;
307
  background: repeating-linear-gradient(-6deg, rgba(255,255,255,0.08) 0px, rgba(255,255,255,0.08) 18px, rgba(0,0,0,0.04) 18px, rgba(0,0,0,0.04) 48px);
308
  background-size: 150% 150%; /* reduced from 320% */
309
- animation: oceanLong 36s linear infinite; /* doubled from 18s */
310
  opacity: 0.2;
311
  }
312
  .bw-bg-mid {
313
  z-index: 13;
314
  background: repeating-linear-gradient(-12deg, rgba(255,255,255,0.10) 0px, rgba(255,255,255,0.10) 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 26px);
315
  background-size: 150% 150%; /* reduced from 260% */
316
- animation: oceanMid 24s linear infinite; /* doubled from 12s */
317
  opacity: 0.2;
318
  }
319
  .bw-bg-fine {
320
  z-index: 14;
321
  background: repeating-linear-gradient(-18deg, var(--foam) 0px, var(--foam) 4px, transparent 4px, transparent 12px);
322
  background-size: 120% 120%; /* reduced from 200% */
323
- animation: oceanFine 14s linear infinite; /* doubled from 7s */
324
  opacity: 0.15;
325
  }
326
  </style>
@@ -844,7 +844,7 @@ def _render_sidebar():
844
  st.header("SETTINGS")
845
 
846
  st.header("Game Mode")
847
- game_modes = ["classic", "too easy"]
848
  default_mode = "classic"
849
  if "game_mode" not in st.session_state:
850
  st.session_state.game_mode = default_mode
@@ -854,7 +854,7 @@ def _render_sidebar():
854
  options=game_modes,
855
  index=game_modes.index(current_mode) if current_mode in game_modes else 0,
856
  key="game_mode",
857
- on_change=_on_game_option_change, # was _new_game
858
  )
859
 
860
  st.header("Wordlist Controls")
@@ -1169,11 +1169,16 @@ def _render_grid(state: GameState, letter_map, show_grid_ticks: bool = True):
1169
  if auto_mark_completed_words(state):
1170
  _sync_back(state)
1171
 
1172
- # Only allow guessing if a letter was revealed or after a correct guess
 
 
1173
  action = (state.last_action or "").strip()
1174
- if action.startswith("Revealed '"):
1175
- st.session_state.can_guess = True
1176
- # Do NOT set can_guess to True for empty cell reveals
 
 
 
1177
 
1178
  # Play sound effect based on hit or miss
1179
  if action.startswith("Revealed '"):
@@ -1963,4 +1968,24 @@ def _on_game_option_change() -> None:
1963
  st.session_state.pop("share_sid", None)
1964
 
1965
  # Start a fresh game with updated options
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1966
  _new_game()
 
234
  repeating-linear-gradient(90deg, rgba(255,255,255,0.06) 0 1px, transparent 1px 18px);
235
  mix-blend-mode: screen;
236
  opacity: 0.10;
237
+ /* animation: waveOverlayScroll 16s linear infinite; */
238
  }
239
  .stIFrame {
240
  margin-bottom:25px;
 
300
  z-index: 11;
301
  background: radial-gradient(150% 100% at 50% -20%, rgba(255,255,255,0.10) 0%, transparent 60%);
302
  background-size: 150% 150%; /* reduced from 300% */
303
+ /* animation: oceanHighlight 12s ease-in-out infinite; */ /* doubled from 6s */
304
  }
305
  .bw-bg-long {
306
  z-index: 12;
307
  background: repeating-linear-gradient(-6deg, rgba(255,255,255,0.08) 0px, rgba(255,255,255,0.08) 18px, rgba(0,0,0,0.04) 18px, rgba(0,0,0,0.04) 48px);
308
  background-size: 150% 150%; /* reduced from 320% */
309
+ /* animation: oceanLong 36s linear infinite; */ /* doubled from 18s */
310
  opacity: 0.2;
311
  }
312
  .bw-bg-mid {
313
  z-index: 13;
314
  background: repeating-linear-gradient(-12deg, rgba(255,255,255,0.10) 0px, rgba(255,255,255,0.10) 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 26px);
315
  background-size: 150% 150%; /* reduced from 260% */
316
+ /* animation: oceanMid 24s linear infinite; */ /* doubled from 12s */
317
  opacity: 0.2;
318
  }
319
  .bw-bg-fine {
320
  z-index: 14;
321
  background: repeating-linear-gradient(-18deg, var(--foam) 0px, var(--foam) 4px, transparent 4px, transparent 12px);
322
  background-size: 120% 120%; /* reduced from 200% */
323
+ /* animation: oceanFine 14s linear infinite; */ /* doubled from 7s */
324
  opacity: 0.15;
325
  }
326
  </style>
 
844
  st.header("SETTINGS")
845
 
846
  st.header("Game Mode")
847
+ game_modes = ["classic", "easy", "too easy"] # <-- added "easy"
848
  default_mode = "classic"
849
  if "game_mode" not in st.session_state:
850
  st.session_state.game_mode = default_mode
 
854
  options=game_modes,
855
  index=game_modes.index(current_mode) if current_mode in game_modes else 0,
856
  key="game_mode",
857
+ on_change=_on_game_option_change,
858
  )
859
 
860
  st.header("Wordlist Controls")
 
1169
  if auto_mark_completed_words(state):
1170
  _sync_back(state)
1171
 
1172
+ # Allow guessing after reveal per mode:
1173
+ # - classic: only after a letter reveal
1174
+ # - easy/too easy: after any reveal (letter or empty)
1175
  action = (state.last_action or "").strip()
1176
+ if state.game_mode in ("easy", "too easy"):
1177
+ if action.startswith("Revealed '") or action.startswith("Revealed empty"):
1178
+ st.session_state.can_guess = True
1179
+ else:
1180
+ if action.startswith("Revealed '"):
1181
+ st.session_state.can_guess = True
1182
 
1183
  # Play sound effect based on hit or miss
1184
  if action.startswith("Revealed '"):
 
1968
  st.session_state.pop("share_sid", None)
1969
 
1970
  # Start a fresh game with updated options
1971
+ # Replace only the shown section in wrdler/ui.py
1972
+
1973
+ def _render_guess_form(state: GameState):
1974
+ # Initialize incorrect guesses list in session state (safety check)
1975
+ if "incorrect_guesses" not in st.session_state:
1976
+ st.session_state.incorrect_guesses = []
1977
+
1978
+ # Enable guessing after correct guess or reveal per mode
1979
+ action = (state.last_action or "").strip()
1980
+ if action.startswith("Correct!"):
1981
+ st.session_state.can_guess = True
1982
+ else:
1983
+ if state.game_mode in ("easy", "too easy"):
1984
+ if action.startswith("Revealed '") or action.startswith("Revealed empty"):
1985
+ st.session_state.can_guess = True
1986
+ else:
1987
+ if action.startswith("Revealed '"):
1988
+ st.session_state.can_guess = True
1989
+
1990
+ # ... rest of function unchanged ...
1991
  _new_game()