Skip to content

Commit 37ff12d

Browse files
Enhance type hints and formatting in game code
Refactor Word Scramble game code to improve type hints and fix minor formatting issues.
1 parent a8acbd4 commit 37ff12d

File tree

1 file changed

+78
-81
lines changed

1 file changed

+78
-81
lines changed

web_programming/Word-Scramble-Game.py

Lines changed: 78 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ...existing code...
2-
"""
3-
Word Scramble — interactive CLI game
2+
"""Word Scramble — interactive CLI game.
3+
44
Features:
55
- Difficulty levels (easy / medium / hard)
66
- Multiple rounds per game based on difficulty
@@ -12,9 +12,10 @@
1212

1313
import json
1414
import random
15+
import sys
1516
import time
1617
from pathlib import Path
17-
from datetime import datetime
18+
from typing import Dict, List, Optional, Sequence, Union
1819

1920
# Files (stored in the same folder)
2021
DATA_DIR = Path(__file__).parent
@@ -68,34 +69,34 @@
6869
]
6970

7071
# Optional categories to make the game more interesting — each category is a small subset
72+
# small sets to keep comprehensions readable and within line length
73+
PROGRAMMING_SET = {
74+
"python",
75+
"function",
76+
"variable",
77+
"module",
78+
"algorithm",
79+
"debug",
80+
"compile",
81+
"object",
82+
"inheritance",
83+
"interface",
84+
"repository",
85+
"commit",
86+
"lambda",
87+
"generator",
88+
"concurrency",
89+
"thread",
90+
"process",
91+
}
92+
7193
CATEGORIES = {
7294
"general": WORD_LIST,
73-
"programming": [
95+
"programming": [w for w in WORD_LIST if w in PROGRAMMING_SET],
96+
"networking": [
7497
w
7598
for w in WORD_LIST
76-
if w
77-
in (
78-
"python",
79-
"function",
80-
"variable",
81-
"module",
82-
"algorithm",
83-
"debug",
84-
"compile",
85-
"object",
86-
"inheritance",
87-
"interface",
88-
"repository",
89-
"commit",
90-
"lambda",
91-
"generator",
92-
"concurrency",
93-
"thread",
94-
"process",
95-
)
96-
],
97-
"networking": [
98-
w for w in WORD_LIST if w in ("network", "protocol", "bandwidth", "packet")
99+
if w in ("network", "protocol", "bandwidth", "packet")
99100
],
100101
}
101102

@@ -164,44 +165,51 @@
164165
}
165166

166167

167-
def load_high_scores():
168-
"""Load high scores from disk; return list of dicts."""
168+
def load_high_scores() -> List[Dict[str, Union[str, int]]]:
169+
"""Load high scores from disk; return list of dicts.
170+
171+
Each entry is expected to contain at least 'name' and 'score'.
172+
"""
169173
if HIGH_SCORES_FILE.exists():
170174
try:
171175
with HIGH_SCORES_FILE.open("r", encoding="utf-8") as fh:
172176
return json.load(fh)
173-
except Exception:
177+
except (json.JSONDecodeError, OSError):
178+
# Corrupted file or IO issue — return empty scores
174179
return []
175180
return []
176181

177182

178-
def save_high_scores(scores):
183+
def save_high_scores(scores: Sequence[Dict[str, Union[str, int]]]) -> None:
179184
"""Save high scores to disk (keep only top 10)."""
180-
scores = sorted(scores, key=lambda s: s["score"], reverse=True)[:10]
185+
# Use descriptive name for sorting key
186+
top_scores: List[Dict[str, Union[str, int]]] = sorted(
187+
scores, key=lambda entry: entry["score"], reverse=True
188+
)[:10]
181189
with HIGH_SCORES_FILE.open("w", encoding="utf-8") as fh:
182-
json.dump(scores, fh, indent=2)
190+
json.dump(top_scores, fh, indent=2)
183191

184192

185-
def load_stats():
193+
def load_stats() -> Dict[str, int]:
186194
"""Load simple player stats (games_played, best_streak)."""
187195
if STATS_FILE.exists():
188196
try:
189197
with STATS_FILE.open("r", encoding="utf-8") as fh:
190198
return json.load(fh)
191-
except Exception:
199+
except (json.JSONDecodeError, OSError):
192200
return {"games_played": 0, "best_streak": 0}
193201
return {"games_played": 0, "best_streak": 0}
194202

195203

196-
def save_stats(stats):
204+
def save_stats(stats: Dict[str, int]) -> None:
197205
try:
198206
with STATS_FILE.open("w", encoding="utf-8") as fh:
199207
json.dump(stats, fh, indent=2)
200-
except Exception:
201-
pass
208+
except OSError as exc: # disk write / permissions issue
209+
print(f"Failed to save stats: {exc}", file=sys.stderr)
202210

203211

204-
def show_high_scores():
212+
def show_high_scores() -> None:
205213
scores = load_high_scores()
206214
if not scores:
207215
print("\nNo high scores yet. Be the first!\n")
@@ -213,7 +221,7 @@ def show_high_scores():
213221
print()
214222

215223

216-
def scramble_word(word):
224+
def scramble_word(word: str) -> str:
217225
"""Return a scrambled version of word that's not identical to original if possible."""
218226
if len(set(word)) == 1:
219227
# all same letter (rare) — return as-is
@@ -229,12 +237,12 @@ def scramble_word(word):
229237
return scrambled # fallback
230238

231239

232-
def color(text, code):
240+
def color(text: str, code: str) -> str:
233241
"""Lightweight colored output using ANSI codes. Works on modern terminals."""
234242
return f"\x1b[{code}m{text}\x1b[0m"
235243

236244

237-
def compute_score(word, elapsed, difficulty):
245+
def compute_score(word: str, elapsed: float, difficulty: str) -> int:
238246
"""Compute score for a correct guess."""
239247
base = len(word) * 10
240248
mult = DIFFICULTIES[difficulty]["multiplier"]
@@ -244,8 +252,12 @@ def compute_score(word, elapsed, difficulty):
244252
return int(base * mult + time_bonus)
245253

246254

247-
def play_round(word, difficulty):
248-
"""Play one scramble round. Returns points earned (0 if failed/skip)."""
255+
def play_round(word: str, difficulty: str) -> Union[int, tuple]:
256+
"""Play one scramble round. Returns points earned (0 if failed/skip).
257+
258+
- Returns an int for points.
259+
- Returns ("quit", 0) if the player chose to quit.
260+
"""
249261
max_time = DIFFICULTIES[difficulty]["max_time"]
250262
scrambled = scramble_word(word)
251263
hint_mask = ["_"] * len(word)
@@ -254,7 +266,8 @@ def play_round(word, difficulty):
254266

255267
print("\nScrambled:", " ".join(scrambled))
256268
print(
257-
f"(Type your guess, or 'hint', 'skip', 'quit'. Time suggested: {max_time}s)\n"
269+
"(Type your guess, or 'hint', 'skip', 'quit'."
270+
f" Time suggested: {max_time}s)\n"
258271
)
259272

260273
start = time.perf_counter()
@@ -282,61 +295,52 @@ def play_round(word, difficulty):
282295
# check answer
283296
if guess == word:
284297
points = compute_score(word, elapsed, difficulty)
285-
print(
286-
color(
287-
f"Correct! +{points} pts (time: {int(elapsed)}s, attempts: {attempts})",
288-
"32",
289-
)
290-
)
298+
print(color(f"Correct! +{points} pts (time: {int(elapsed)}s, attempts: {attempts})", '32'))
291299
return points
292300
else:
293301
# small helpful feedback
294302
same_positions = sum(1 for a, b in zip(guess, word) if a == b)
295-
print(
296-
f"Not quite. {same_positions} letter(s) in the correct position. Try again."
297-
)
303+
print(f"Not quite. {same_positions} letter(s) in the correct position. Try again.")
298304

299305

300-
def pick_word_by_difficulty_and_category(difficulty, category=None):
306+
def pick_word_by_difficulty_and_category(
307+
difficulty: str, category: Optional[str] = None
308+
) -> str:
301309
"""Pick a word influenced by difficulty and optional category (longer words for harder)."""
302310
if category and category in CATEGORIES:
303311
pool = CATEGORIES[category]
312+
elif difficulty == "easy":
313+
pool = [w for w in WORD_LIST if 4 <= len(w) <= 6]
314+
elif difficulty == "medium":
315+
pool = [w for w in WORD_LIST if 5 <= len(w) <= 8]
304316
else:
305-
if difficulty == "easy":
306-
pool = [w for w in WORD_LIST if 4 <= len(w) <= 6]
307-
elif difficulty == "medium":
308-
pool = [w for w in WORD_LIST if 5 <= len(w) <= 8]
309-
else:
310-
pool = [w for w in WORD_LIST if len(w) >= 6]
317+
pool = [w for w in WORD_LIST if len(w) >= 6]
311318
if not pool:
312319
pool = WORD_LIST
313320
return random.choice(pool)
314321

315322

316-
def celebratory_art():
323+
def celebratory_art() -> None:
317324
art = [
318325
"\n ★ Congratulations! ★\n",
319-
"\n (\_/)",
326+
"\n (\\_/)",
320327
"\n ( •_•)",
321328
"\n / >💥 You did it!\n",
322329
]
323330
print(color("\n".join(art), "35"))
324331

325332

326-
def play_game():
327-
print(color("WELCOME TO WORD SCRAMBLE", "36"))
328-
print(color("------------------------", "36"))
333+
def play_game() -> None:
334+
print(color("WELCOME TO WORD SCRAMBLE", '36'))
335+
print(color("------------------------", '36'))
329336
# choose difficulty
330337
while True:
331-
diff = (
332-
input("Choose difficulty (easy / medium / hard) [medium]: ").strip().lower()
333-
or "medium"
334-
)
338+
diff = input("Choose difficulty (easy / medium / hard) [medium]: ").strip().lower() or "medium"
335339
if diff in DIFFICULTIES:
336340
break
337341
print("Invalid choice.")
338342
# choose optional category
339-
print("Available categories:", ", ".join(CATEGORIES.keys()))
343+
print("Available categories:", ', '.join(CATEGORIES.keys()))
340344
cat = input("Pick category (or press Enter for mixed): ").strip().lower() or None
341345
if cat and cat not in CATEGORIES:
342346
print("Unknown category, using mixed words.")
@@ -364,12 +368,7 @@ def play_game():
364368
# combo bonus for streaks: +5% per consecutive correct (rounded)
365369
combo_bonus = int(points * (0.05 * (streak - 1))) if streak > 1 else 0
366370
if combo_bonus:
367-
print(
368-
color(
369-
f"Combo! +{combo_bonus} bonus points for a streak of {streak}",
370-
"33",
371-
)
372-
)
371+
print(color(f"Combo! +{combo_bonus} bonus points for a streak of {streak}", '33'))
373372
points += combo_bonus
374373
total_score += points
375374
celebratory_art()
@@ -383,7 +382,7 @@ def play_game():
383382
save_stats(stats)
384383

385384

386-
def main_menu():
385+
def main_menu() -> None:
387386
while True:
388387
print("Word Scramble — Main Menu")
389388
print("1) Play")
@@ -398,9 +397,7 @@ def main_menu():
398397
elif choice == "3":
399398
print("\nInstructions:")
400399
print("- Guess the original word from the scrambled letters.")
401-
print(
402-
"- Commands during a round: 'hint' (reveals one letter), 'skip', 'quit'."
403-
)
400+
print("- Commands during a round: 'hint' (reveals one letter), 'skip', 'quit'.")
404401
print("- Faster correct answers score more points.")
405402
print("- High scores are saved locally in high_scores.json.\n")
406403
elif choice == "4":

0 commit comments

Comments
 (0)