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