Skip to content

Commit 84d7e48

Browse files
Merge pull request #111 from Naveen-Boddepalli/feature/whack-a-mole-game
Add: Whack-a-Mole game with web app integration
2 parents b2b3e32 + 0982882 commit 84d7e48

3 files changed

Lines changed: 916 additions & 382 deletions

File tree

games/Whack-a-Mole/Whack-a-Mole.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
"""
2+
🔨 Whack-a-Mole
3+
Type the number of the mole before it disappears!
4+
"""
5+
6+
import random
7+
import time
8+
import os
9+
import threading
10+
11+
12+
# ── Config ────────────────────────────────────────────────────────────────────
13+
GRID_SIZE = 9 # 3×3 grid
14+
MOLE = "🐭"
15+
HOLE = "🕳️ "
16+
HAMMER = "🔨"
17+
18+
LEVELS = {
19+
1: {"name": "Easy", "window": 2.5, "rounds": 10, "moles": 1},
20+
2: {"name": "Medium", "window": 1.5, "rounds": 15, "moles": 2},
21+
3: {"name": "Hard", "window": 0.8, "rounds": 20, "moles": 3},
22+
}
23+
24+
25+
def clear():
26+
os.system("cls" if os.name == "nt" else "clear")
27+
28+
29+
def draw_grid(active: set[int], hit: set[int]):
30+
"""Print 3×3 grid. active=mole positions, hit=just-whacked."""
31+
print()
32+
for row in range(3):
33+
line = " "
34+
for col in range(3):
35+
idx = row * 3 + col
36+
if idx in hit:
37+
line += f" {HAMMER}[{idx+1}] "
38+
elif idx in active:
39+
line += f" {MOLE}[{idx+1}] "
40+
else:
41+
line += f" {HOLE}[{idx+1}] "
42+
print(line)
43+
print()
44+
45+
46+
def choose_level() -> dict:
47+
clear()
48+
print("╔══════════════════════════════╗")
49+
print("║ 🔨 WHACK-A-MOLE 🐭 ║")
50+
print("╚══════════════════════════════╝\n")
51+
print(" Difficulty:")
52+
for k, v in LEVELS.items():
53+
print(f" {k}{v['name']:8s} ({v['window']}s window, {v['moles']} mole(s), {v['rounds']} rounds)")
54+
print()
55+
while True:
56+
try:
57+
c = int(input(" Enter 1 / 2 / 3: ").strip())
58+
if c in LEVELS:
59+
return LEVELS[c]
60+
print(" ⚠️ Enter 1, 2, or 3.")
61+
except ValueError:
62+
print(" ⚠️ Numbers only.")
63+
64+
65+
def play():
66+
level = choose_level()
67+
window = level["window"]
68+
rounds = level["rounds"]
69+
n_moles = level["moles"]
70+
71+
score = 0
72+
misses = 0
73+
reaction_times = []
74+
75+
clear()
76+
print(f"\n 🐭 {level['name']} mode — {rounds} rounds — {window}s per mole")
77+
print(" Type the number(s) and press Enter fast!\n")
78+
time.sleep(2)
79+
80+
for rnd in range(1, rounds + 1):
81+
# Random mole positions (no duplicates)
82+
active = set(random.sample(range(GRID_SIZE), n_moles))
83+
hit = set()
84+
85+
clear()
86+
print(f" Round {rnd}/{rounds} | Score: {score} | Misses: {misses}\n")
87+
draw_grid(active, set())
88+
89+
# ── Timed input using threading ───────────────────────────────────────
90+
user_input = []
91+
input_event = threading.Event()
92+
93+
def get_input():
94+
try:
95+
raw = input(" Whack! Enter number(s) e.g. '2' or '1 3': ").strip()
96+
user_input.append(raw)
97+
except EOFError:
98+
pass
99+
input_event.set()
100+
101+
t = threading.Thread(target=get_input, daemon=True)
102+
start_ts = time.time()
103+
t.start()
104+
input_event.wait(timeout=window)
105+
elapsed = time.time() - start_ts
106+
107+
# ── Evaluate ──────────────────────────────────────────────────────────
108+
if user_input:
109+
try:
110+
chosen = set(int(x) - 1 for x in user_input[0].split())
111+
except ValueError:
112+
chosen = set()
113+
114+
valid_hits = chosen & active # correct moles hit
115+
wrong_hits = chosen - active # wrong holes hit
116+
117+
hit = valid_hits
118+
round_score = len(valid_hits) * 10
119+
round_miss = len(active - valid_hits) + len(wrong_hits)
120+
121+
if elapsed <= window:
122+
reaction_times.append(elapsed)
123+
124+
score += round_score
125+
misses += round_miss
126+
127+
clear()
128+
print(f" Round {rnd}/{rounds} | Score: {score} | Misses: {misses}\n")
129+
draw_grid(active, hit)
130+
131+
if valid_hits == active and not wrong_hits:
132+
print(f" ✅ Perfect hit! +{round_score}{elapsed:.2f}s")
133+
elif valid_hits:
134+
print(f" ⚠️ Partial hit! +{round_score} Missed: {len(active - valid_hits)}")
135+
else:
136+
print(" ❌ Missed!")
137+
else:
138+
# Timed out
139+
clear()
140+
print(f" Round {rnd}/{rounds} | Score: {score} | Misses: {misses}\n")
141+
draw_grid(active, set())
142+
print(" ⏰ Too slow!")
143+
misses += n_moles
144+
145+
time.sleep(0.9)
146+
147+
# Gap between rounds
148+
gap = random.uniform(0.4, 1.0)
149+
time.sleep(gap)
150+
151+
# ── Final screen ──────────────────────────────────────────────────────────
152+
clear()
153+
print("\n╔══════════════════════════════════════╗")
154+
print("║ 🏁 GAME OVER! ║")
155+
print("╚══════════════════════════════════════╝\n")
156+
print(f" 🎯 Final Score : {score}")
157+
print(f" ❌ Total Misses: {misses}")
158+
max_score = rounds * n_moles * 10
159+
accuracy = round((score / max_score) * 100) if max_score else 0
160+
print(f" 📊 Accuracy : {accuracy}%")
161+
162+
if reaction_times:
163+
avg_rt = sum(reaction_times) / len(reaction_times)
164+
print(f" ⚡ Avg Reaction: {avg_rt:.2f}s")
165+
166+
if accuracy >= 90:
167+
print("\n 🌟 Legendary reflexes!")
168+
elif accuracy >= 70:
169+
print("\n 👏 Great job!")
170+
elif accuracy >= 50:
171+
print("\n 😅 Not bad — keep practising!")
172+
else:
173+
print("\n 🐭 The moles win this round...")
174+
175+
176+
def main():
177+
while True:
178+
play()
179+
print("\n Play again? (y / n): ", end="")
180+
if input().strip().lower() != "y":
181+
print("\n Thanks for playing! 🔨\n")
182+
break
183+
184+
185+
if __name__ == "__main__":
186+
main()

0 commit comments

Comments
 (0)