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