1

I wrote a python program utilizing the minimax algorithm to play tic tac toe. The board is represented by a 1d array. The player is able to play as either side. The program recursively generates all of the legal boards and checks for a winner alternating between minimizing and maximizing every turn. For some reason the algorithm never generates a winning board.

At first I assumed it was related to the algorithm not minimizing and maximizing for the right side but I checked and I don't see any reason the code wouldn't work

import random

def getOtherPlayer(letter):
    if letter == "X":
        return "O"
    else:
        return "X"


def getWinner(board):
    for i in range(3):
        # Check rows
        if board[i*3] == board[i*3+1] == board[i*3+2] != "":
            return board[i*3]
        # Check columns
        if board[i] == board[i+3] == board[i+6] != "":
            return board[i]
    # Check diagonals
    if board[0] == board[4] == board[8] != "":
        return board[0]
    if board[2] == board[4] == board[6] != "":
        return board[2]
    # Check for tie
    if "" not in board:
        return "Tie"
    # No winner yet
    return None

def test():
    board = ['X', '', 'O', 'X', 'O', 'X', 'O', 'X', 'O']
    print(getScore(board, 'X', False))

def getAvailableMoves(board):
    return [i for i in range(len(board)) if board[i] == ""]


def getScore(board, letter, isMaximizing):
    winner = getWinner(board)
    if winner == letter and isMaximizing:
        print('win')
        return 1  # current player wins
    elif winner == letter and isMaximizing is False:
        print('lose')
        return -1
    else:
        print(board)
        print(letter)
        print(isMaximizing)
        print('tie')
        return 0  # tie (no winner)


def minimax(board, letter, player):
    if player == 0:
        if letter == "X":
            bestScore = float('inf')
            isMaximizing = False
        else:
            bestScore = float('-inf')
            isMaximizing = True
    if player == 1:
        if letter == "O":
            bestScore = float('inf')
            isMaximizing = False
        else:
            bestScore = float('-inf')
            isMaximizing = True
    winner = getWinner(board)
    if winner is not None:
        print("terminal")
        return getScore(board, letter, isMaximizing)
    availableMoves = getAvailableMoves(board)
    if isMaximizing:
        for move in availableMoves:
            board[move] = letter
            score = minimax(board, getOtherPlayer(letter), player)
            board[move] = ""
            bestScore = max(score, bestScore)
    else:
        for move in availableMoves:
            board[move] = letter
            print(board)
            score = minimax(board, getOtherPlayer(letter), player)
            board[move] = ""
            bestScore = min(score, bestScore)
    return bestScore




def findBestMove(board, startingLetter, player):
    availableMoves = getAvailableMoves(board)
    bestMove = None
    bestScore = -9999
    for move in availableMoves:
        board[move] = startingLetter
        score = minimax(board, getOtherPlayer(startingLetter), player)
        board[move] = ""
        if score > bestScore:
            bestScore = score
            bestMove = move
    return bestMove


def printBoard(board):
    print()
    for i in range(3):
        print(board[i * 3:i * 3 + 3])

# Main Code
test()
letter = 'X'
board = [""] * 9
player = random.choice([0, 1])
print("You are playing as", player)
while True:
    printBoard(board)
    winner = getWinner(board)
    if winner is not None:
        if winner == "Tie":
            print("Tie")
        else:
            print("Winner:", winner)
        break
    if player == 0:
        if letter == "O":
            move = findBestMove(board, letter, player)
            print("Computer played move", move)
            board[move] = letter
        else:
            move = int(input("Enter move (0-8): "))
            while board[move] != "":
                move = int(input("Invalid move. Enter move (0-8): "))
            board[move] = letter
    elif player == 1:
        if letter == "X":
            move = findBestMove(board, letter, player)
            print("Computer played move", move)
            board[move] = letter
        else:
            move = int(input("Enter move (0-8): "))
            while board[move] != "":
                move = int(input("Invalid move. Enter move (0-8): "))
            board[move] = letter
    letter = getOtherPlayer(letter)
  • I’m voting to close this question because I feel this is too specific to be of general use. It should probably be moved to https://codereview.stackexchange.com/ (but I cannot vote for moving it there). – fuenfundachtzig Feb 27 '23 at 11:41

1 Answers1

3

The problem is in your getScore function.

It only returns a non-zero value when the winner is letter, but not when the winner is the other player. Yet that is the situation you will have: if there is a win, it will have been played by the player that moved last, while in your calls to getScore, letter is actually the player who is next to play. As a consequence, getScore will return 0 even when the game has been won.

Here is a suggested correction of that function:

def getScore(board, letter, isMaximizing):
    winner = getWinner(board)
    # When the game is not over yet, or it is a tie, then return 0
    if not winner or winner == 'Tie':
        return 0
    # When we get here, we know the game has been won.
    # Return 1  when `letter` is the winner and is maximizing, or
    #           when `letter` is the loser  and is minimizing
    # Return -1 when `letter` is the loser  and is maximizing, or
    #           when `letter` is the winner and is minimizing
    # In short: if the expression `winner == letter` has the same boolean value as
    #           `isMaximizing` return 1, else return -1
    return 1 if (winner == letter) == isMaximizing else -1

A small remark: When analysing your code I found it quite confusing that you used a variable player which determines whether the human player plays with "X" and "O", but the variable is a number (0 or 1). You can avoid some code repetition by using "X" and "O" also for the player value.

Also, why not make "X" always the maximizing player, no matter if it is the human or non-human player? Sure, you'd need a bit more care in the findBestMove function, but all the rest would become simpler.

trincot
  • 317,000
  • 35
  • 244
  • 286