[Python] Infinite Recursion on Minimax Algorithm Tic Tac Toe [closed]

Discussão em 'Python' iniciado por Stack, Dezembro 19, 2024.

  1. Stack

    Stack Membro Participativo

    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]

Compartilhe esta Página