Bot.py import random class Bot: def __init__(self): self.o = "o" self.x = "x" def easy_bot(self, display_grid, board): choice = random.randrange(9) quadrant = int(choice) - 1 while board[quadrant] != " ": choice = random.randrange(9) quadrant = int(choice) - 1 board[quadrant] = self.o display_grid() def medium_bot(self, display_grid,board, j): if j == 1: choice = random.randrange(9) quadrant = int(choice) - 1 while board[quadrant] != " ": choice = random.randrange(9) quadrant = int(choice) - 1 board[quadrant] = self.o display_grid() else: # Winning Moves for i in range(0, 9, 3): # Check rows if board == board[i + 1] == self.o and board[i + 2] == " ": board[i + 2] = self.o display_grid() return elif board[i + 1] == board[i + 2] == self.o and board == " ": board = self.o display_grid() return elif board == board[i + 2] == self.o and board[i + 1] == " ": board[i + 1] = self.o display_grid() return for i in range(3): # Check columns if board == board[i + 3] == self.o and board[i + 6] == " ": board[i + 6] = self.o display_grid() return elif board[i + 3] == board[i + 6] == self.o and board == " ": board = self.o display_grid() return elif board == board[i + 6] == self.o and board[i + 3] == " ": board[i + 3] = self.o display_grid() return # Check diagonals diagonals = [(0, 4, 8), (2, 4, 6)] for d in diagonals: if board[d[0]] == board[d[1]] == self.o and board[d[2]] == " ": board[d[2]] = self.o display_grid() return elif board[d[1]] == board[d[2]] == self.o and board[d[0]] == " ": board[d[0]] = self.o display_grid() return elif board[d[0]] == board[d[2]] == self.o and board[d[1]] == " ": board[d[1]] = self.o display_grid() return # Blocking Moves for i in range(0, 9, 3): # Check rows if board == board[i + 1] == self.x and board[i + 2] == " ": board[i + 2] = self.o display_grid() return elif board[i + 1] == board[i + 2] == self.x and board == " ": board = self.o display_grid() return elif board == board[i + 2] == self.x and board[i + 1] == " ": board[i + 1] = self.o display_grid() return for i in range(3): # Check columns if board == board[i + 3] == self.x and board[i + 6] == " ": board[i + 6] = self.o display_grid() return elif board[i + 3] == board[i + 6] == self.x and board == " ": board = self.o display_grid() return elif board == board[i + 6] == self.x and board[i + 3] == " ": board[i + 3] = self.o display_grid() return # Check diagonals for d in diagonals: if board[d[0]] == board[d[1]] == self.x and board[d[2]] == " ": board[d[2]] = self.o display_grid() return elif board[d[1]] == board[d[2]] == self.x and board[d[0]] == " ": board[d[0]] = self.o display_grid() return elif board[d[0]] == board[d[2]] == self.x and board[d[1]] == " ": board[d[1]] = self.o display_grid() return # Strategic Moves if board[4] == " ": # Center board[4] = self.o display_grid() return for corner in [0, 2, 6, 8]: # Corners if board[corner] == " ": board[corner] = self.o display_grid() return for edge in [1, 3, 5, 7]: # Edges if board[edge] == " ": board[edge] = self.o display_grid() return def hard_bot(self, display_grid, board): for i in range(0, 9, 3): # Check rows if board == board[i + 1] == self.o and board[i + 2] == " ": board[i + 2] = self.o display_grid() return elif board[i + 1] == board[i + 2] == self.o and board == " ": board = self.o display_grid() return elif board == board[i + 2] == self.o and board[i + 1] == " ": board[i + 1] = self.o display_grid() return for i in range(3): # Check columns if board[i] == board[i + 3] == self.o and board[i + 6] == " ": board[i + 6] = self.o display_grid() return elif board[i + 3] == board[i + 6] == self.o and board[i] == " ": board[i] = self.o display_grid() return elif board[i] == board[i + 6] == self.o and board[i + 3] == " ": board[i + 3] = self.o display_grid() return # Check diagonals diagonals = [(0, 4, 8), (2, 4, 6)] for d in diagonals: if board[d[0]] == board[d[1]] == self.o and board[d[2]] == " ": board[d[2]] = self.o display_grid() return elif board[d[1]] == board[d[2]] == self.o and board[d[0]] == " ": board[d[0]] = self.o display_grid() return elif board[d[0]] == board[d[2]] == self.o and board[d[1]] == " ": board[d[1]] = self.o display_grid() return # Blocking Moves for i in range(0, 9, 3): # Check rows if board[i] == board[i + 1] == self.x and board[i + 2] == " ": board[i + 2] = self.o display_grid() return elif board[i + 1] == board[i + 2] == self.x and board[i] == " ": board[i] = self.o display_grid() return elif board[i] == board[i + 2] == self.x and board[i + 1] == " ": board[i + 1] = self.o display_grid() return for i in range(3): # Check columns if board[i] == board[i + 3] == self.x and board[i + 6] == " ": board[i + 6] = self.o display_grid() return elif board[i + 3] == board[i + 6] == self.x and board[i] == " ": board[i] = self.o display_grid() return elif board[i] == board[i + 6] == self.x and board[i + 3] == " ": board[i + 3] = self.o display_grid() return # Check diagonals for d in diagonals: if board[d[0]] == board[d[1]] == self.x and board[d[2]] == " ": board[d[2]] = self.o display_grid() return elif board[d[1]] == board[d[2]] == self.x and board[d[0]] == " ": board[d[0]] = self.o display_grid() return elif board[d[0]] == board[d[2]] == self.x and board[d[1]] == " ": board[d[1]] = self.o display_grid() return # Strategic Moves if board[4] == " ": # Center board[4] = self.o display_grid() return for corner in [0, 2, 6, 8]: # Corners if board[corner] == " ": board[corner] = self.o display_grid() return for edge in [1, 3, 5, 7]: # Edges if board[edge] == " ": board[edge] = self.o display_grid() return def hardcore_bot(self, board, check_win, display_grid): best_score = -float('inf') best_move = None for quadrant in range(len(board)): if board[quadrant] == " ": board[quadrant] = self.o # Try the move score = self.minimax(board, False, check_win) # Call minimax for opponent board[quadrant] = " " # Undo the move if score > best_score: best_score = score best_move = quadrant if best_move is not None: board[best_move] = self.o # Make the best move display_grid() def minimax(self, board, is_maximizing, check_win): # Check for terminal states if check_win(self.o): # Bot wins return 1 elif check_win(self.x): # Opponent wins return -1 elif " " not in board: # Draw return 0 if is_maximizing: best_score = -float('inf') for quadrant in range(len(board)): if board[quadrant] == " ": board[quadrant] = self.o score = self.minimax(board, False, check_win) board[quadrant] = " " best_score = max(score, best_score) return best_score else: best_score = float('inf') for quadrant in range(len(board)): if board[quadrant] == " ": board[quadrant] = self.x score = self.minimax(board, True, check_win) board[quadrant] = " " best_score = min(score, best_score) return best_score tic_tac_toe.py from bot import Bot import time import threading import sys class TicTacToe: def __init__(self): self.board = [" "] * 9 self.x = "x" self.o = "o" def display_grid(self): print(f""" 1 |2 |3 {self.board[0]} | {self.board[1]} | {self.board[2]} _____|_____|_____ 4 |5 |6 {self.board[3]} | {self.board[4]} | {self.board[5]} _____|_____|_____ 7 |8 |9 {self.board[6]} | {self.board[7]} | {self.board[8]} | | """) def highlight_win(self, a, b, c): if b == a + 1: self.board[a], self.board[b], self.board[c] = "-", "-", "-" elif b == a + 3: self.board[a], self.board[b], self.board[c] = "|", "|", "|" elif a == 0 and c == 8: self.board[a], self.board[b], self.board[c] = "\\", "\\", "\\" elif a == 2 and c == 6: self.board[a], self.board[b], self.board[c] = "/", "/", "/" def check_win_row(self, player): for i in range(0, 9, 3): if self.board[i] == self.board[i+1] == self.board[i+2] == player: self.highlight_win(i, i+1, i+2) return True return False def check_win_column(self, player): columns = [[0, 3, 6], [1, 4, 7], [2, 5, 8]] for col in columns: if self.board[col[0]] == self.board[col[1]] == self.board[col[2]] == player: self.highlight_win(col[0], col[1], col[2]) return True return False def check_win_diagonals(self, player): diagonals = [[0, 4, 8], [2, 4, 6]] for dialgonal in diagonals: if self.board[dialgonal[0]] == self.board[dialgonal[1]] == self.board[dialgonal[2]] == player: self.highlight_win(dialgonal[0], dialgonal[1], dialgonal[2]) return True return False def check_win(self, player): if self.check_win_row(player) or self.check_win_column(player) or self.check_win_diagonals(player): print(f"{player} wins!!!") return True return False # def check_draw(self, board): # for quadrant in board: # if board[quadrant] == " ": # return False # return True class GameController: def __init__(self): self.game = TicTacToe() self.adversary = Bot() self.timer_active = True def get_play(self, player): while True: try: play = input(f"{player}'s turn: ") if play.isdigit() and 1 <= int(play) <= 9: return play else: raise ValueError("Type a number between 1 to 9") except ValueError as e: print(e) def countdown_timer(self, duration): """Countdown timer that updates on a separate line.""" sys.stdout.write("\033[s") # Save the current cursor position while duration > 0 and self.timer_active: sys.stdout.write("\033[u") # Restore the saved cursor position sys.stdout.write(f"\033[KTime remaining: {duration} seconds\n") # Clear line and print timer sys.stdout.flush() time.sleep(1) duration -= 1 if duration == 0: self.timer_active = False sys.stdout.write("\033[u\033[KTime's up! Game over.\n") # Notify when time is up sys.stdout.flush() def game_loop(self, game_style, difficulty): for j in range(9): if game_style == "bot": if j % 2 == 0: player = self.game.x move = self.get_play(player) quadrant = int(move) - 1 if self.game.board[quadrant] == " ": self.game.board[quadrant] = player self.game.display_grid() if self.game.check_win(player): self.game.display_grid() break else: print("Invalid Quadrant") if j==8: print("Draw!!!") else: print("o's turn...") match difficulty: case "1": self.adversary.easy_bot(self.game.display_grid, self.game.board) case "2": self.adversary.medium_bot(self.game.display_grid, self.game.board, j) case "3": self.adversary.hard_bot(self.game.display_grid, self.game.board) case "4": self.adversary.hardcore_bot(self.game.board, self.game.check_win,self.game.display_grid) player = self.game.o if self.game.check_win(player): self.game.display_grid() break if j==8: print("Draw!!!") elif game_style == "player2": player = self.game.x if j % 2 == 0 else self.game.o move = self.get_play(player) quadrant = int(move) - 1 if self.game.board[quadrant] == " ": self.game.board[quadrant] = player self.game.display_grid() if self.game.check_win(player): self.game.display_grid() break else: print("Invalid Quadrant") if j==8: print("Draw!!!") else: raise ValueError("Invalid command, type bot or player2") def game_timed_loop(self, game_style): timer_thread = threading.Thread(target= self.countdown_timer, args=(20,)) timer_thread.start() for j in range(9): if game_style == "bot": if j % 2 == 0: player = self.game.x move = self.get_play(player) quadrant = int(move) - 1 if self.game.board[quadrant] == " ": self.game.board[quadrant] = player self.game.display_grid() if self.game.check_win(player): self.game.display_grid() break else: print("Invalid Quadrant") if j == 8: print("Draw!!!") else: print("o's turn...") match self.game.difficulty: case "1": self.adversary.easy_bot(self.game.display_grid, self.game.board) case "2": self.adversary.medium_bot(self.game.display_grid, self.game.board, j) case "3": self.adversary.hard_bot(self.game.display_grid, self.game.board) case "4": self.adversary.hardcore_bot(self.game.board, self.game.check_win) player = self.game.o if self.game.check_win(player): self.game.display_grid() break if j == 8: print("Draw!!!") elif game_style == "player2": player = self.game.x if j % 2 == 0 else self.game.o move = self.get_play(player) quadrant = int(move) - 1 if self.game.board[quadrant] == " ": self.game.board[quadrant] = player self.game.display_grid() if self.game.check_win(player): self.game.display_grid() break else: print("Invalid Quadrant") if j == 8: print("Draw!!!") else: raise ValueError("Invalid command, type bot or player2") else: print("Time is over") self.timer_active = False # Ensure timer stops after the game ends timer_thread.join() def main(): controller = GameController() game_mode = input("""Choose game mode: 1. With countdown timer 2. Without countdown timer\n""") while game_mode != "1" and game_mode != "2": game_mode = input("""Choose game mode: 1. With countdown timer 2. Without countdown timer\n""") if game_mode == "2": game_style = input("Choose game style (bot or player2): ") while game_style != "bot" and game_style != "player2": game_style = input("Choose game style (bot or player2): ") if game_style == "bot": valid_difficulties = ["1", "2", "3", "4"] difficulty = None while difficulty not in valid_difficulties: difficulty = input("""Choose difficulty: 1. Easy 2. Medium 3. Hard 4. Hardcore\n""") controller.game.display_grid() controller.game_loop(game_style, difficulty) else: game_style = input("Choose game style (bot or player2): ") while game_style != "bot" and game_style != "player2": game_style = input("Choose game style (bot or player2): ") if game_style == "bot": valid_difficulties = ["1", "2", "3", "4"] difficulty = None while difficulty not in valid_difficulties: difficulty = input("""Choose difficulty: 1. Easy 2. Medium 3. Hard 4. Hardcore\n""") controller.game.display_grid() controller.game_timed_loop(game_style) main() The ouput: o wins!!! x wins!!! x wins!!! o wins!!! x wins!!! x wins!!! x wins!!! x wins!!! o wins!!! x wins!!! x wins!!! x wins!!! o wins!!! x wins!!! x wins!!! x wins!!! x wins!!! x wins!!! x wins!!! o wins!!! x wins!!! x wins!!! x wins!!! The expected output is that the bot play once after the user made its move and goes like this until the user or the bot wins. The outup is very strange since the moves are not interleaving and every single one is considering as a win. [url="https://stackoverflow.com/questions/79294985/infinite-recursion-on-minimax-algorithm-tic-tac-toe"]Continue reading...[/url][/i][/b][/b][/b][/b][/i][/i][/i][/i][/i][/i][/i][/i][/i][/i][/i][/i]