11import random
22import time
33import os
4+ import sys
5+ import threading
46
5- def main ():
6- global HIGH_SCORE_FILE , choice , display_time , emojis , high_score , i , level , play_again , replay , score , sequence , user_input , user_input_raw
7- emojis = ["🍎" , "🚗" , "⚽" , "🐍" , "🎧" , "🔥" , "🌈" , "🚀" ]
8- HIGH_SCORE_FILE = "highscore.txt"
7+ EMOJIS = ["🍎" , "🚗" , "⚽" , "🐍" , "🎧" , "🔥" , "🌈" , "🚀" ]
8+ HIGH_SCORE_FILE = "highscore.txt"
9+
10+ DIFFICULTY_TIME_LIMITS = {
11+ "1" : 5 , # Easy - memorise time
12+ "2" : 4 , # Medium - memorise time
13+ "3" : 2 , # Hard - memorise time
14+ }
15+
16+ DIFFICULTY_INPUT_LIMITS = {
17+ "1" : 10 , # Easy - input time
18+ "2" : 8 , # Medium - input time
19+ "3" : 5 , # Hard - input time
20+ }
21+
22+
23+
24+ # Core, testable logic (no I/O, no global state)
25+
26+ def get_time_limit (choice ):
27+ """Return the memorise display time (seconds) for a difficulty choice."""
28+ return DIFFICULTY_TIME_LIMITS .get (choice , 4 )
29+
30+
31+ def get_input_limit (choice ):
32+ """Return the allowed input time (seconds) for a difficulty choice."""
33+ return DIFFICULTY_INPUT_LIMITS .get (choice , 8 )
34+
35+
36+ def generate_sequence (emojis , level ):
37+ """Generate a random emoji sequence for the given level.
38+
39+ Sequence length grows with level (level + 2), matching the
40+ original game's difficulty curve.
41+ """
42+ if level < 1 :
43+ raise ValueError ("level must be >= 1" )
44+ return random .choices (emojis , k = level + 2 )
45+
46+
47+ def parse_input (raw_input ):
48+ """Turn raw user text into a list of emoji tokens."""
49+ return [token .strip () for token in raw_input .split () if token .strip ()]
50+
51+
52+ def validate_input (user_sequence , correct_sequence ):
53+ """Return True if the user's sequence exactly matches the correct one."""
54+ return user_sequence == correct_sequence
55+
56+
57+ def is_timeout (elapsed_seconds , time_limit ):
58+ """Return True if elapsed_seconds exceeds the allowed time_limit."""
59+ return elapsed_seconds > time_limit
60+
61+
62+ def update_streak (is_correct , current_streak ):
63+ """Increment streak on a correct round, reset to 0 on an incorrect one."""
64+ return current_streak + 1 if is_correct else 0
65+
66+
67+ def format_countdown_message (remaining_seconds ):
68+ """Return the display string for a given number of seconds left."""
69+ if remaining_seconds <= 0 :
70+ return "⏱️ Time's up!"
71+ return f"⏱️ { remaining_seconds } s left..."
72+
73+
74+ def calculate_score (level , streak ):
75+ """Calculate score for a correctly-answered round.
976
10- # Load or initialize high score ONCE at the start of the program
77+ Base score scales with level; a streak bonus rewards consecutive
78+ correct answers.
79+ """
80+ base = level * 10
81+ streak_bonus = streak * 2
82+ return base + streak_bonus
83+
84+
85+
86+ # I/O helpers
87+
88+ def load_high_score ():
1189 if not os .path .exists (HIGH_SCORE_FILE ):
1290 try :
1391 with open (HIGH_SCORE_FILE , "w" , encoding = "utf-8" ) as file :
1492 file .write ("0" )
1593 except OSError as e :
1694 print (f"⚠️ Warning: Could not initialize high score file: { e } " )
1795
18- high_score = 0
1996 try :
2097 with open (HIGH_SCORE_FILE , "r" , encoding = "utf-8" ) as file :
21- high_score = int (file .read ().strip () or "0" )
98+ return int (file .read ().strip () or "0" )
2299 except (FileNotFoundError , ValueError , OSError ) as e :
23100 print (f"⚠️ Warning: Could not read high score file: { e } " )
24- high_score = 0
101+ return 0
102+
103+
104+ def save_high_score (score ):
105+ try :
106+ with open (HIGH_SCORE_FILE , "w" , encoding = "utf-8" ) as file :
107+ file .write (str (score ))
108+ except OSError as e :
109+ print (f"⚠️ Could not save high score: { e } " )
110+
111+
112+ def clear_screen ():
113+ os .system ('cls' if os .name == 'nt' else 'clear' )
114+
115+
116+ def run_countdown (duration , stop_event ):
117+ """Update a single countdown line once per second while waiting for input.
118+
119+ Runs in a background thread. Instead of printing a new line each
120+ tick, it:
121+ 1. Saves the cursor's current position (which is on the input
122+ line, wherever the user has typed so far).
123+ 2. Moves the cursor up 2 lines to the dedicated countdown line.
124+ 3. Clears that line and writes the new remaining-time message.
125+ 4. Restores the cursor back to the input line, so the user's
126+ typing is completely undisturbed.
127+
128+ stop_event is set as soon as the user submits their answer, so the
129+ countdown stops immediately rather than continuing to update.
130+ """
131+ remaining = duration
132+ while remaining > 0 :
133+ if stop_event .wait (timeout = 1 ):
134+ return # user already answered, stop counting
135+ remaining -= 1
136+ sys .stdout .write ("\033 7" ) # save cursor position
137+ sys .stdout .write ("\033 [2A" ) # move up 2 lines -> countdown line
138+ sys .stdout .write ("\r \033 [2K" ) # jump to col 0 and clear the line
139+ sys .stdout .write (format_countdown_message (remaining ))
140+ sys .stdout .write ("\033 8" ) # restore cursor -> back to input line
141+ sys .stdout .flush ()
142+
143+
144+ # Game loop
145+
146+ def main ():
147+ high_score = load_high_score ()
25148
26- # Main Game Loop
27149 while True :
28150 print ("\n 🎮 Welcome to Emoji Memory Game!" )
29151 print (f"🏅 High Score: { high_score } \n " )
@@ -35,52 +157,79 @@ def main():
35157
36158 while True :
37159 choice = input ("Enter choice (1/2/3): " ).strip ()
38- if choice in [ '1' , '2' , '3' ] :
160+ if choice in DIFFICULTY_TIME_LIMITS :
39161 break
40162 print ("⚠️ Invalid input. Enter 1, 2, or 3." )
41163
42- display_time = 4
43- if choice == "1" :
44- display_time = 5
45- elif choice == "3" :
46- display_time = 2
164+ display_time = get_time_limit (choice )
165+ input_time = get_input_limit (choice )
47166
48167 print ("\n ⏳ Get Ready!" )
49168 for i in range (3 , 0 , - 1 ):
50169 print (i )
51170 time .sleep (1 )
52- os . system ( 'cls' if os . name == 'nt' else 'clear' )
171+ clear_screen ( )
53172
54173 score = 0
55174 level = 1
175+ streak = 0
56176
57177 # Gameplay Loop
58178 while True :
59- sequence = random . choices ( emojis , k = level + 2 )
179+ sequence = generate_sequence ( EMOJIS , level )
60180
61181 print ("🧠 MEMORIZE THESE EMOJIS:" )
62182 print (" " .join (sequence ))
63183
64184 time .sleep (display_time )
65- os .system ('cls' if os .name == 'nt' else 'clear' )
185+ clear_screen ()
186+
187+ # Line 1: countdown line (updated in place by run_countdown)
188+ # Line 2: static prompt text
189+ # Line 3: "> " where the user actually types
190+ print (format_countdown_message (input_time ))
191+ print ("Type the emojis in order (separated by spaces):" )
192+ sys .stdout .write ("> " )
193+ sys .stdout .flush ()
66194
67- user_input_raw = input ("Type the emojis in order (separated by spaces):\n > " ).strip ()
68- user_input = [emoji .strip () for emoji in user_input_raw .split ()]
195+ stop_event = threading .Event ()
196+ timer_thread = threading .Thread (
197+ target = run_countdown , args = (input_time , stop_event ), daemon = True
198+ )
199+ timer_thread .start ()
200+
201+ start_time = time .time ()
202+ user_input_raw = input ().strip ()
203+ elapsed = time .time () - start_time
204+
205+ stop_event .set ()
206+ timer_thread .join (timeout = 0.2 )
207+
208+ user_input = parse_input (user_input_raw )
209+ timed_out = is_timeout (elapsed , input_time )
69210
70211 if not user_input :
71212 print ("⚠️ Empty input detected!" )
72213 print ("❌ Wrong!" )
73214 print ("Correct sequence was:" , " " .join (sequence ))
74215 break
75216
76- if user_input == sequence :
77- score += level * 10
217+ correct = validate_input (user_input , sequence ) and not timed_out
218+
219+ streak = update_streak (correct , streak )
220+
221+ if correct :
222+ score += calculate_score (level , streak )
78223 print ("✅ Correct!" )
79224 level += 1
80- print (f"🏆 Score: { score } \n " )
225+ print (f"🏆 Score: { score } " )
226+ print (f"🔥 Streak: { streak } \n " )
81227 else :
228+ if timed_out :
229+ print ("⏱️ Time's up!" )
82230 print ("❌ Wrong!" )
83231 print ("Correct sequence was:" , " " .join (sequence ))
232+ print (f"🔥 Streak: { streak } " )
84233 break
85234
86235 # Game Over Processing
@@ -89,16 +238,12 @@ def main():
89238
90239 if score > high_score :
91240 print ("🔥 NEW HIGH SCORE!" )
92- high_score = score # Update local memory variable
93- try :
94- with open (HIGH_SCORE_FILE , "w" , encoding = "utf-8" ) as file :
95- file .write (str (score ))
96- except OSError as e :
97- print (f"⚠️ Could not save high score: { e } " )
241+ high_score = score
242+ save_high_score (score )
98243 else :
99244 print (f"🏅 High Score remains: { high_score } " )
100245
101- # Replay Loop Fix
246+ # Replay Loop
102247 play_again = False
103248 while True :
104249 replay = input ("\n Play again? (y/n): " ).strip ().lower ()
@@ -112,7 +257,8 @@ def main():
112257
113258 if not play_again :
114259 print ("👋 Thanks for playing!" )
115- break # Safely breaks the main outer loop and exits the game
260+ break
261+
116262
117263if __name__ == '__main__' :
118- main ()
264+ main ()
0 commit comments