@@ -5,27 +5,123 @@ def __init__(self):
55 """Initializes the game state without blocking execution."""
66 self .user_score = 0
77 self .computer_score = 0
8- self .rounds_played = 0
8+ self .rounds_played = 0
99 # Perfectly mirrors the JS choices array ['rock', 'paper', 'scissors']
1010 self .choices = ["rock" , "paper" , "scissors" ]
11+ # Counter look-up: what beats each move
12+ self .beaten_by = {"rock" : "paper" , "paper" : "scissors" , "scissors" : "rock" }
13+ # Player history for adaptive AI (capped at recent 20 moves)
14+ self .player_history = []
15+ self .HISTORY_CAP = 20
16+ self .MIN_ADAPTIVE = 3 # rounds before adaptive mode activates
17+ self .ADAPT_RATE = 0.70 # probability of playing counter vs random
1118
19+ # ── Adaptive AI logic ────────────────────────────────────────────────
20+ def _get_move_frequencies (self ):
21+ """Returns overall frequency dict for player history."""
22+ freq = {"rock" : 0 , "paper" : 0 , "scissors" : 0 }
23+ for move in self .player_history :
24+ freq [move ] += 1
25+ return freq
26+
27+ def _get_markov_transitions (self , last_move ):
28+ """Returns transition counts from last_move to the next move in history."""
29+ transitions = {"rock" : 0 , "paper" : 0 , "scissors" : 0 }
30+ for i in range (len (self .player_history ) - 1 ):
31+ if self .player_history [i ] == last_move :
32+ transitions [self .player_history [i + 1 ]] += 1
33+ return transitions
34+
35+ def _predict_player_move (self ):
36+ """
37+ Uses a blended Markov-chain + frequency model to predict the player's
38+ next move. Returns (predicted_move, confidence_pct) or (None, None)
39+ if there is not enough data.
40+ """
41+ n = len (self .player_history )
42+ if n < self .MIN_ADAPTIVE :
43+ return None , None
44+
45+ freq = self ._get_move_frequencies ()
46+ last = self .player_history [- 1 ]
47+ trans = self ._get_markov_transitions (last )
48+ total = sum (trans .values ())
49+
50+ if total > 0 :
51+ # Blend: 60 % Markov, 40 % frequency
52+ blended = {}
53+ for c in self .choices :
54+ blended [c ] = (0.6 * trans [c ] / total ) + (0.4 * freq [c ] / n )
55+ predicted = max (blended , key = blended .get )
56+ confidence = round (blended [predicted ] * 100 )
57+ else :
58+ # Fallback to pure frequency
59+ predicted = max (freq , key = freq .get )
60+ confidence = round (freq [predicted ] / n * 100 )
61+
62+ return predicted , confidence
63+
64+ def get_adaptive_computer_choice (self ):
65+ """
66+ Returns (computer_choice, predicted_player_move, confidence_pct, mode).
67+ Plays the counter-move 70 % of the time; random otherwise for balance.
68+ """
69+ predicted , confidence = self ._predict_player_move ()
70+
71+ if predicted is None :
72+ return random .choice (self .choices ), None , None , "learning"
73+
74+ if random .random () < self .ADAPT_RATE :
75+ computer_choice = self .beaten_by [predicted ]
76+ mode = "adaptive"
77+ else :
78+ computer_choice = random .choice (self .choices )
79+ mode = "random"
80+
81+ return computer_choice , predicted , confidence , mode
82+
83+ # ── Stats helpers ────────────────────────────────────────────────────
84+ def _most_frequent_choice (self ):
85+ if not self .player_history :
86+ return None
87+ freq = self ._get_move_frequencies ()
88+ return max (freq , key = freq .get )
89+
90+ # ── Gameplay ─────────────────────────────────────────────────────────
1291 def users_play (self ):
13- """Handles a single round of interaction."""
92+ """Handles a single round of interaction with adaptive computer logic ."""
1493 user_choice = ""
1594 while user_choice not in self .choices :
1695 user_choice = input ("Enter your choice (rock, paper, or scissors): " ).lower ()
1796 if user_choice not in self .choices :
1897 print ("Invalid choice. Please choose rock, paper, or scissors." )
1998
20- computer_choice = random . choice ( self . choices )
21- print ( f"Computer chose: { computer_choice } " )
99+ # AI decides BEFORE we record the player's move (prediction is based on prior history )
100+ computer_choice , predicted , confidence , mode = self . get_adaptive_computer_choice ( )
22101
102+ # Print AI brain info after first MIN_ADAPTIVE rounds
103+ if len (self .player_history ) >= self .MIN_ADAPTIVE and predicted is not None :
104+ fav = self ._most_frequent_choice ()
105+ print (f"\n 🧠 Computer Brain [{ mode .upper ()} ]" )
106+ print (f" Your favourite move : { fav } " )
107+ print (f" Predicted your move : { predicted } ({ confidence } % confidence)" )
108+ print (f" Computer chose : { computer_choice } " )
109+ else :
110+ remaining = self .MIN_ADAPTIVE - len (self .player_history )
111+ if remaining > 0 :
112+ print (f"\n 🧠 Computer Brain [LEARNING] — observing for { remaining } more move(s)..." )
113+ print (f" Computer chose: { computer_choice } " )
114+
115+ # Record player move after AI has decided
116+ self .player_history .append (user_choice )
117+ if len (self .player_history ) > self .HISTORY_CAP :
118+ self .player_history .pop (0 )
119+
120+ # Determine round winner
23121 if user_choice == computer_choice :
24122 print ("It's a Tie! 🤝" )
25123 return "tie"
26- elif (user_choice == "rock" and computer_choice == "scissors" ) or \
27- (user_choice == "paper" and computer_choice == "rock" ) or \
28- (user_choice == "scissors" and computer_choice == "paper" ):
124+ elif self .beaten_by [computer_choice ] == user_choice :
29125 print ("You Win this round! 🎉" )
30126 return "user"
31127 else :
@@ -35,30 +131,39 @@ def users_play(self):
35131 def statistics (self ):
36132 """Displays performance statistics matching the web dashboard metrics."""
37133 print ("\n --- Game Statistics ---" )
38- print (f"Rounds Played: { self .rounds_played } " )
39- print (f"Your Score: { self .user_score } " )
40- print (f"Computer Score: { self .computer_score } " )
134+ print (f"Rounds Played : { self .rounds_played } " )
135+ print (f"Your Score : { self .user_score } " )
136+ print (f"Computer Score : { self .computer_score } " )
137+ fav = self ._most_frequent_choice ()
138+ if fav :
139+ freq = self ._get_move_frequencies ()
140+ pct = round (freq [fav ] / len (self .player_history ) * 100 )
141+ print (f"Your Favourite : { fav } ({ pct } % of plays)" )
41142
42143 def save_game (self ):
43144 """Appends the final game results to a local tracking log."""
44145 name = input ("Enter your name to save the results (optional): " )
45146 if not name :
46147 name = "Anonymous"
47- result_string = f"Player: { name } , Final Score: { self .user_score } - { self .computer_score } (User-Computer), Rounds: { self .rounds_played } \n "
148+ result_string = (
149+ f"Player: { name } , Final Score: { self .user_score } - { self .computer_score } "
150+ f"(User-Computer), Rounds: { self .rounds_played } \n "
151+ )
48152 try :
49153 with open ("game_results.txt" , "a" ) as f :
50154 f .write (result_string )
51155 print ("Game results saved successfully." )
52156 except IOError :
53157 print ("Error: Could not save game results to file." )
54158
55- def play_game (self ):
159+ def play_game (self ):
56160 """Launches the primary interactive gameplay loop."""
57161 print ("Welcome to Rock, Paper, Scissors!" )
162+ print ("The computer will learn your patterns and adapt — good luck! 🧠" )
58163 while True :
59164 self .rounds_played += 1
60165 print (f"\n --- Round { self .rounds_played } ---" )
61-
166+
62167 round_winner = self .users_play ()
63168
64169 if round_winner == "user" :
@@ -74,6 +179,7 @@ def play_game(self):
74179 self .save_game ()
75180 break
76181
182+
77183# Standard execution block ensuring clean instantiation
78184if __name__ == "__main__" :
79185 game = Rock_Paper_Scissors ()
0 commit comments