Home > front end >  Minimax tic tac toe isn't choosing actual best move
Minimax tic tac toe isn't choosing actual best move

Time:01-19

I wrote python code for a Minimax AI tic tac toe. However, the Minimax in action isn't actually picking the best move. It always seems to pick the upper right corner for the first move, and all other moves almost seem random. It is very easy to beat, which seems to be an error somewhere in my code. Thank you for any help.

GUI Code:

from tkinter import *
import customtkinter
import minimax

customtkinter.set_appearance_mode("Dark")
#creating CTk window for app
root = customtkinter.CTk()

#setting window width and height
root.geometry('500x300')


#Creating label
label = customtkinter.CTkLabel(master=root,
                               text="Tic Tac Toe",
                               width=120,
                               height=50,
                               font=("normal", 20),
                               corner_radius=8)
label.place(relx=0.25, rely=0.8, anchor=CENTER)

#Handling clicks
depth=9
def clickbutton(r, c):
    buttons[r][c]["text"]="X"
    board[r][c]="X"
    buttons[r][c]['state']=DISABLED

    label = customtkinter.CTkLabel(master=root,
                               text=checkwin(board),
                               width=120,
                               height=25,
                               corner_radius=8)
    label.place(relx=0.25, rely=0.9, anchor=CENTER)

    global depth
    depth-=1
    computerplay()
    depth-=1
    
    

#Button matrix
buttons = [
     [0,0,0],
     [0,0,0],
     [0,0,0]]
 
#Matrix identifying whether buttons are active or inactive
board=[[0,0,0],[0,0,0],[0,0,0]]
 
for i in range(3):
    for j in range(3):                                 
        buttons[i][j] = Button(height = 3, width = 6, font = ("Normal", 20),
                        command = lambda r = i, c = j : clickbutton(r,c))
        buttons[i][j].grid(row = i, column = j)



def computerplay():
    global depth
    bestmove=minimax.minimax(board, depth, 1)
    buttons[bestmove[0]][bestmove[1]]['text']="O"
    buttons[bestmove[0]][bestmove[1]]['state']=DISABLED
    board[bestmove[0]][bestmove[1]]="O"

def checkwin(b):
    score=minimax.evaluate(b)
    if score==10:
        return 'Computer won!'
    elif score==-10:
        return 'You won!'
    else:
        return 'Player vs. Computer'
    

root.mainloop() 

Minimax code:

def change_board(board):
    return [
        [(cell == "O") - (cell == "X") for cell in row]
        for row in board
    ]

def empty_cells(board):
    return [(i, j) for i in range(3) for j in range(3) if board[i][j] == 0]

def game_over(board):
    return (evaluate(board) == 10 or evaluate(board) ==-10 or not empty_cells(board))
    
lines = (
    (0, 0, 1, 1, 2, 2),
    (0, 2, 1, 1, 2, 0),
    (0, 0, 0, 1, 0, 2),
    (1, 0, 1, 1, 1, 2),
    (2, 0, 2, 1, 2, 2),
    (0, 0, 1, 0, 2, 0),
    (0, 1, 1, 1, 2, 1),
    (0, 2, 1, 2, 2, 2)
)

def evaluate(board):
    def iswin(line):
        arow, acol, brow, bcol, crow, ccol = line
        if board[arow][acol] == board[brow][bcol] == board[crow][ccol]:
            return board[arow][acol]*10

    return next((win for win in map(iswin, lines) if win), 0)
    

def minimax(board, depth, player):
    best = (-1, -1, -11*player)

    if depth <= 0 or game_over(board):
        return (-1, -1, evaluate(board))  # must return same structure

    for x, y in empty_cells(board):
        board[x][y] = player
        score = minimax(board, depth - 1, -player)[2]  # only get the score
        board[x][y] = 0
        if (player == 1) == (score > best[2]):
            best = (x, y, score)  # only inject x, y when move is best

    return best

CodePudding user response:

Your code is mixing up the values that it stores in board. On the one hand there is the minimax module which stores and expects values -1, 0 and 1 in board (as player is either 1 or -1), but on the other hand you have code on the GUI side that stores "O" or "X" in the board data structure. This mix up breaks the logic used in the evaluate function.

In your previous question on the subject, you had a function change_board, which performed a conversion from the "X" and "O" values to -1 and 1. But in this question this conversion is not performed.

So to fix this, change these lines in the GUI managing code:

board[r][c]="X"
...
board[bestmove[0]][bestmove[1]]="O"

...to:

board[r][c]=-1
...
board[bestmove[0]][bestmove[1]]=1
  • Related