2

When working on a python Tic Tac Toe algorithm to power a discord bot, I encountered an error. By the way, the algorithm is built to find the best move for X, and the top-left (first) square is 0. When using the board configuration:

[1, 0, 0, 
 0, 0, 0, 
-1, 0, -1]

where 1 is X and -1 is O (0 is blank), the algorithm returns 2. This move does not block the inpending win for O on the bottom-middle square, which should be caught by the program. As you'll see below, I have implemented a failsafe-of-sorts that should be catching these types of scenarios, but it seems that it's not working.

Main minimax function:

def minimax(board, player):
    #if someone has already won the game
    winner = getWinner(board)
    if winner != Player.Blank:
        return winner * player, -1
    move = -1
    score = -2

    for i in range(9):
        if board[i] == Player.Blank:
            copy_board = board.copy()
            copy_board[i] = player
            opponent = getOpponentFor(player)

            #play the move if it wins
            if getWinner(copy_board) == Player.Computer:
                return Player.Computer, i

            #this is the part I believe is not working
            elif getWinner(copy_board) == opponent:
                copy_score = 2

            #else just run the algorithm    
            else:
                copy_score = -minimax(copy_board, opponent)[0]
            
            #make this move the best move
            if copy_score > score:
                score = copy_score
                print(score)
                move = i

    #if board is full
    if move == -1:
        return 0, -1

    return score, move

Other engine code (might be useful for debugging):

def getWinner(board):
    #rows
    for i in range(0, 9, 3):
        if board[i] == board[i + 1] == board[i + 2]:
            return board[i]

    #columns     
    for i in range(3):
        if board[i] == board[i + 3] == board[i + 6]:
            return board[i]

    #diagonals
    if board[0] == board[4] == board[8]:
        return board[0]
    if board[2] == board[4] == board[6]:
        return board[2]
        
    #no winner
    return Player.Blank

def getOpponentFor(player):
    return Player.Human if player == Player.Computer else Player.Computer

class Player:
    Human = -1
    Blank = 0
    Computer = 1 


def Engine(board):
    return minimax(board, Player.Computer)[1]

As always, anything is appreciated. Thanks in advance!

hyperrr
  • 97
  • 1
  • 9

1 Answers1

0

Modifying minimax()'s loop like this should solve the issue:

for i in range(9):
        if board[i] == Player.Blank:
            copy_board = board.copy()
            copy_board[i] = player
            opponent = get_opponent_for(player)

            # play the move if it wins
            if get_winner(copy_board) == player:
                return 2, i

            # else just run the algorithm
            else:
                copy_score = -minimax(copy_board, opponent)[0]

            # make this move the best move
            if copy_score > score:
                score = copy_score
                move = i

Code I used for testing:

from typing import List, Tuple


def get_winner(board: List[int]) -> int:
    # rows
    for i in range(0, 9, 3):
        if board[i] == board[i + 1] == board[i + 2]:
            return board[i]

    # columns
    for i in range(3):
        if board[i] == board[i + 3] == board[i + 6]:
            return board[i]

    # diagonals
    if board[0] == board[4] == board[8]:
        return board[0]
    if board[2] == board[4] == board[6]:
        return board[2]

    # draw / no winner
    return -2 if Player.Blank not in board else Player.Blank


def get_opponent_for(player: int) -> int:
    return Player.Human if player == Player.Computer else Player.Computer


class Player:
    Human = -1
    Blank = 0
    Computer = 1


def engine(board: List[int]):
    return minimax(board, Player.Computer)[1]


def minimax(board: List[int], player: int) -> Tuple[int, int]:
    # if someone has already won the game
    winner = get_winner(board)
    if winner == -2:  # draw
        return 0, -1
    if winner != Player.Blank:
        return winner * player, -1

    move = -1
    score = -2

    for i in range(9):
        if board[i] == Player.Blank:
            copy_board = board.copy()
            copy_board[i] = player
            opponent = get_opponent_for(player)

            # play the move if it wins
            if get_winner(copy_board) == player:
                return 2, i

            # else just run the algorithm
            else:
                copy_score = -minimax(copy_board, opponent)[0]

            # make this move the best move
            if copy_score > score:
                score = copy_score
                move = i

    # if board is full
    return (0, -1) if move == -1 else (score, move)


def print_board(board: List[int]) -> None:
    print("╔═══╦═══╦═══╗")
    print(f"║{space_if_pos(board[0])} ║", end='')
    print(f"{space_if_pos(board[1])} ║", end='')
    print(f"{space_if_pos(board[2])} ║")
    print("╠═══╬═══╬═══╣")
    print(f"║{space_if_pos(board[3])} ║", end='')
    print(f"{space_if_pos(board[4])} ║", end='')
    print(f"{space_if_pos(board[5])} ║")
    print("╠═══╬═══╬═══╣")
    print(f"║{space_if_pos(board[6])} ║", end='')
    print(f"{space_if_pos(board[7])} ║", end='')
    print(f"{space_if_pos(board[8])} ║")
    print("╚═══╩═══╩═══╝")


def space_if_pos(num: int) -> str:
    return f" {num}" if num >= 0 else str(num)


def main():
    board = [0, 0, 0,
             0, 0, 0,
             0, 0, 0]
    while get_winner(board) == Player.Blank:
        print_board(board)
        move = int(input("Your move: "))
        board[move] = Player.Human
        if get_winner(board) == Player.Blank:
            print_board(board)
            print("Computer's move")
            move = engine(board)
            board[move] = Player.Computer
    print_board(board)
    if get_winner(board) == Player.Human:
        print("Player wins!")
    elif get_winner(board) == Player.Computer:
        print("Computer wins!")
    else:
        print("Draw!")


if __name__ == "__main__":
    main()
chubercik
  • 534
  • 1
  • 5
  • 13