I'm programming Tic-Tac-Toe using minimax algorithm with alpha-beta pruning after taking Harvard's CS-50.
My logic is working perfectly, except for the return time of minimax (especially on the first call, when only one player played). I was thinking of replacing the extra recursive call of minimax to max/min value. I want to emphasize that minimax is taking relatively slow for my purposes (around 0.5 seconds).
import math
import copy
import time
X = "X"
O = "O"
EMPTY = None
def initial_state():
"""
Returns starting state of the board.
"""
return [[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]]
def player(board):
"""
Returns player who has the next turn on a board.
"""
if board == initial_state():
return X
X_counter = 0
O_counter = 0
for row in board:
for cell in row:
if cell == X:
X_counter += 1
elif cell == O:
O_counter += 1
return O if X_counter > O_counter else X
def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
"""
possible_moves = set()
for i in range(3):
for j in range(3):
if board[i][j] is None:
possible_moves.add((i, j))
return possible_moves
def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
"""
newboard = copy.deepcopy(board)
newboard[action[0]][action[1]] = player(board)
return newboard
def winner(board):
"""
Returns the winner of the game, if there is one.
"""
if board[0][0] == board[0][1] == board[0][2] != None: # 1, 2, 3
return board[0][0]
if board[1][0] == board[1][1] == board[1][2] != None: # 4, 5, 6
return board[1][0]
if board[2][0] == board[2][1] == board[2][2] != None: # 7, 8, 9
return board[2][0]
if board[0][0] == board[1][0] == board[2][0] != None: # 1, 4, 7
return board[0][0]
if board[0][1] == board[1][1] == board[2][1] != None: # 2, 5, 8
return board[0][1]
if board[0][2] == board[1][2] == board[2][2] != None: # 3, 6, 9
return board[0][2]
if board[0][0] == board[1][1] == board[2][2] != None: # 1, 4, 7
return board[0][0]
if board[0][2] == board[1][1] == board[2][0] != None: # 3, 5, 7
return board[0][2]
return None
def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
def check_draw(board):
for row in board:
for cell in row:
if cell is None:
return False
return True
if winner(board) is not None or check_draw(board): # or someone won , or there is a draw
return True
return False
def utility(board):
"""
Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
temp = winner(board)
if temp == X:
return 1
elif temp == O:
return -1
return 0
def minimax(board):
"""
Returns the optimal action for the current player on the board.
"""
options = actions(board)
if player(board) == X:
vT = -math.inf
move = set()
for action in options:
v, count = minvalue(result(board,action), -math.inf, math.inf, 0)
if v > vT:
vT = v
move = action
else:
vT = math.inf
move = set()
for action in options:
v, count = maxvalue(result(board,action), -math.inf, math.inf, 0)
if v < vT:
vT = v
move = action
return move
def maxvalue(board,alpha,beta,count):
"""
Calculates the max value of a given board recursively together with minvalue
"""
if terminal(board): return utility(board), count+1
v = -math.inf
posactions = actions(board)
for action in posactions:
vret, count = minvalue(result(board, action),alpha,beta,count)
v = max(v, vret)
alpha = max(alpha, v)
if alpha > beta:
break
return v, count+1
def minvalue(board,alpha,beta,count):
"""
Calculates the min value of a given board recursively together with maxvalue
"""
if terminal(board): return utility(board), count+1
v = math.inf
posactions = actions(board)
for action in posactions:
vret, count = maxvalue(result(board, action),alpha,beta,count)
v = min(v, vret)
beta = min(v, beta)
if alpha > beta:
break
return v, count + 1
Rather than:
def player(board):
"""
Returns player who has the next turn on a board.
"""
if board == initial_state():
return X
X_counter = 0
O_counter = 0
for row in board:
for cell in row:
if cell == X:
X_counter += 1
elif cell == O:
O_counter += 1
return O if X_counter > O_counter else X
What kind of performance do you get with:
def player(board):
"""
Returns player who has the next turn on a board.
"""
counts = collections.Counter(cell for row in board for cell in row if cell)
return O if counts.get(X, 0) > counts.get(O, 0) else X
A question might arise as to why improving the performance of player might make a big difference.
To calculate the first move, I counted the number of times each method was called:
[('winner', 53673), ('player', 30442), ('result', 30441), ('terminal', 30441), ('utility', 23232), ('maxvalue', 16248), ('actions', 14419), ('minvalue', 14193), ('minimax', 1)]
We can see that player() is called very often. The next candidate to look at is clearly winner() as it is called even more frequently than player(). We can observe that winner() is only ever called by terminal() and utility(). Further, we can observe that terminal() and utility() are only ever called together like:
if terminal(board): return utility(board), count+1
So let's see if we can do something about combining them.
How about:
def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
is_winner = winner(board)
if is_winner == X:
return 1
if is_winner == O:
return -1
if not actions(board):
return 0
return None
and we can use it like:
who_wins = terminal(board)
if who_wins is not None: return who_wins, count+1
This takes the heat off of winner():
[('player', 30442), ('result', 30441), ('terminal', 30441), ('winner', 30441), ('maxvalue', 16248), ('actions', 14419), ('minvalue', 14193), ('minimax', 1)]
This takes an addition 25% off the already reduced time.
On my system, the original code takes (on average) 2.5 seconds for a first move. With these simple updates, the same recommended move is generated in 0.001 seconds on average.
Related
I am trying to create a tic tac toe game with an adjustable game size and a computer that uses the minimax algorithm. The game sizes can only be odd numbers, to make sure that diagonal wins are always possible. The game runs with no errors, but I know that the minimax algorithm isn't working 100% correct because I can still beat the computer. I've looked extensively over my code and cannot find where the algorithm is going wrong. Here is my code:
Main.py
import TicTacToe
import Minimax
if (__name__ == "__main__"):
t = TicTacToe.ttt(3)
m = Minimax.Minimax(3, t)
while (t.winner == None):
if (t.turn == 1):
playerInputI = int(input("Input row: "))
playerInputJ = int(input("Input column: "))
bestIndex = (playerInputI, playerInputJ)
else:
winner, bestIndex = m.minimax(t.grid, (-1, -1), 15, -1)
t.winner = None
t.findWinner(bestIndex, t.grid)
t.updateGameGrid(bestIndex)
print(t.grid)
print(t.grid)
Minimax.py
class Minimax:
def __init__(self, gs, t):
self.gridSize = gs
self.ttt = t
def minimax(self, state, currIndex, depth, turn):
if (currIndex[0] != -1 and currIndex[1] != -1):
winner = self.ttt.findWinner(currIndex, state)
if (winner == -1):
return winner - depth, currIndex
elif (winner == -1):
return winner + depth, currIndex
elif (winner == 0):
return 0, currIndex
if (depth==0 and winner==None):
return 0, currIndex
evalLimit = -turn * 1000
bestIndex = None
for i in range(self.gridSize):
for j in range(self.gridSize):
if (state[i][j] == 0):
state[i][j] = turn
eval, newIndex = self.minimax(state, (i, j), depth-1, -turn)
state[i][j] = 0
if (turn > 0 and eval > evalLimit):
bestIndex = newIndex
evalLimit = eval
elif (turn < 0 and eval < evalLimit):
bestIndex = newIndex
evalLimit = eval
return evalLimit, bestIndex
Tictactoe.py
from random import randint
class ttt:
def __init__(self, size):
self.gridSize = size
self.grid = self.createGrid()
# If using minimax algorithm, user is maximizer(1) and computer is minimizer(-1)
# If single player, then user is 1, computer is -1
# If multiplayer, user1 is 1, user2 = -1
self.turn = 1
self.winner = None
def createGrid(self):
grid = []
for i in range(self.gridSize):
grid.append([])
for j in range(self.gridSize):
grid[i].append(0)
# grid = [[-1, 1, 0], [0, -1, 0], [0, 0, 0]]
return grid
def updateGameGrid(self, index):
if (self.grid[index[0]][index[1]] != 0):
return
self.grid[index[0]][index[1]] = self.turn
winner = self.findWinner(index, self.grid)
self.turn = -self.turn
def randomIndex(self):
x = randint(0, self.gridSize-1)
y = randint(0, self.gridSize-1)
while (self.grid[x][y] != 0):
x = randint(0, self.gridSize-1)
y = randint(0, self.gridSize-1)
return (x, y)
def findWinner(self, index, grid):
# Row
found = True
for j in range(self.gridSize-1):
if (grid[index[0]][j] != grid[index[0]][j+1] or grid[index[0]][j] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Column
found = True
for i in range(self.gridSize-1):
if (grid[i][index[1]] != grid[i+1][index[1]] or grid[i][index[1]] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Top Left to Bottom Right Diagonal
if (index[0] == index[1]):
found = True
for i in range(self.gridSize-1):
if (grid[i][i] != grid[i+1][i+1] or grid[i][i] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Top Right to Bottom Left Diagonal
if (index[0] + index[1] == self.gridSize-1):
found = True
for i in range(self.gridSize-1):
if (grid[self.gridSize-i-1][i] != grid[self.gridSize-i-2][i+1] or grid[self.gridSize-i-1][i] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
tie = True
for i in range(self.gridSize):
for j in range(self.gridSize):
if (grid[i][j] == 0):
tie = False
if (tie):
self.winner = 0
return 0
return None
The grid is represented as a 2d array, with each element being either a -1 for O, 1 for X and 0 for nothing. The player is 1 and the computer is -1. Who's turn it is is represented as a -1 or 1, corresponding to O or X. If anyone is able to find where the error in my code is that would be a great.
I see two troubles :
a) the two following conditions are the same in minimax
if (winner == -1):
return winner - depth, currIndex
elif (winner == -1):
return winner + depth, currIndex
b) When you return bestIndex, you actually return the winning move, the one that completes a line or a row or a diagonal, at one of the leaves of the game tree. What you really want is the next move to play. Write instead bestIndex = (i, j) in the condition if eval > evalLimit
I didn't check for everything but that is a good start. Run your code at depth=1 or 2 with many printf inside the minimax function, and look at the different moves, with the corresponding score, if they look correct or not.
I'm doing the cs50 AI tic-tac-toe project. I have completed the entire code base but it seems to me that the agent only does moves in the first row of the grid. Also, if the first row of the grid is filled up, the game is shown as 'tied'. This is my code:
"""
Tic Tac Toe Player
"""
import math
import copy
X = "X"
O = "O"
EMPTY = None
def initial_state():
"""
Returns starting state of the board.
"""
return [[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]]
def player(board):
"""
Returns player who has the next turn on a board.
"""
#Initialize number of X's and O's in the board
sum_X = 0
sum_O = 0
#Count the number of variables each time
for i in board:
sum_X+=i.count(X)
sum_O+=i.count(O)
# If X>O, then the player has to be O, but if X<O(which won't really happen ever) or X=O, naturally the player has to be X
if sum_X>sum_O:
return O
else:
return X
def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
"""
#Initialize a dictionary to track all the empty spots on the board
i=0
j=0
possible_actions=set()
while i<3:
while j<3:
if board[i][j] == EMPTY:
possible_actions.add((i,j))
j+=1
i+=1
return possible_actions
def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
"""
#Generate deep copy
board_deepcopy = copy.deepcopy(board)
try:
if board_deepcopy[action[0]][action[1]]:
raise IndexError
else:
board_deepcopy[action[0]][action[1]] = player(board_deepcopy)
return board_deepcopy
except IndexError:
print('Spot occupied already')
def winner(board):
"""
Returns the winner of the game, if there is one.
"""
#Horizontal check
for i in board:
if i.count(X)==3:
return X
elif i.count(O)==3:
return O
#Vertical check
j=0
while j<3:
i=0
vert_check=[]
while i<3:
vert_check.append(board[i][j])
i+=1
if vert_check.count(X)==3:
return X
elif vert_check.count(O)==3:
return O
j+=1
#Diagonal check
i=0
diag_check_1=[]
diag_check_2=[]
while i<3:
diag_check_1.append(board[i][i])#top left to bottom right
diag_check_2.append(board[i][2-i])#top right to bottom left
i+=1
if diag_check_1.count(X)==3 or diag_check_2.count(X)==3:
return X
elif diag_check_1.count(O)==3 or diag_check_2.count(O)==3:
return O
return None
def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
#Game either ends if a winner is declared or if there is a tie i.e. there are no more actions left
if winner(board):
return True
elif not actions(board):
return True
else:
return False
def utility(board):
"""
Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
if terminal(board):
if winner(board) == X:
return 1
elif winner(board) == O:
return -1
else:
return 0
def minimax(board):
"""
Returns the optimal action for the current player on the board.
"""
current_player = player(board)
if current_player == X:
v = -math.inf
for action in actions(board):
k = minimize(result(board, action))
if k > v:
v = k
best_move = action
else:
v = math.inf
for action in actions(board):
k = maximize(result(board, action))
if k < v:
v = k
best_move = action
return best_move
def maximize(board):
if terminal(board):
return utility(board)
v = -math.inf
for action in actions(board):
v=max(v,minimize(result(board, action)))
return v
def minimize(board):
if terminal(board):
return utility(board)
v = math.inf
for action in actions(board):
v=min(v,maximize(result(board, action)))
return v
The moves are also not optimal since it moves only in the first row. The possible_moves set contains all the possible moves in all the rows so that isn't really an issue.
I'm trying to create an AI program that plays tic-tac-toe against the user using PyGame. I get two different errors depending on if I click X or O. When I click X, I see the game window pop up but I cannot actually click on any of the squares. When I click O, the game window reads "Computer is thinking..." and then crashes with the following error message:
File "C:\Users\schwa\Code\cs50\pset1\tictactoe\tictactoe\tictactoe.py", line 169, in min_value
v = min(v, max_value(result(board, action)))
File "C:\Users\schwa\Code\cs50\pset1\tictactoe\tictactoe\tictactoe.py", line 158, in max_value
v = max(v, min_value(result(board, action)))
File "C:\Users\schwa\Code\cs50\pset1\tictactoe\tictactoe\tictactoe.py", line 169, in min_value
v = min(v, max_value(result(board, action)))
File "C:\Users\schwa\Code\cs50\pset1\tictactoe\tictactoe\tictactoe.py", line 158, in max_value
v = max(v, min_value(result(board, action)))
File "C:\Users\schwa\Code\cs50\pset1\tictactoe\tictactoe\tictactoe.py", line 53, in result
new_board = copy.deepcopy(board)
File "C:\Users\schwa\lib\copy.py", line 146, in deepcopy
y = copier(x, memo)
File "C:\Users\schwa\lib\copy.py", line 205, in _deepcopy_list
append(deepcopy(a, memo))
File "C:\Users\schwa\lib\copy.py", line 146, in deepcopy
y = copier(x, memo)
File "C:\Users\schwa\lib\copy.py", line 205, in _deepcopy_list
append(deepcopy(a, memo))
File "C:\Users\schwa\lib\copy.py", line 137, in deepcopy
d = id(x)
RecursionError: maximum recursion depth exceeded while calling a Python object
I scrolled and there seems to be an endless cycle alternating between min_value and max_value being called.
Here is my code:
"""
Tic Tac Toe Player
"""
import copy
import math
X = "X"
O = "O"
EMPTY = None
def initial_state():
"""
Returns starting state of the board.
"""
return [[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]]
def player(board):
x_count = 0
o_count = 0
for rows in board:
for columns in rows:
if columns == X:
x_count += 1
elif columns == O:
o_count += 1
if x_count <= o_count:
return X
else:
return O
def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
"""
possible_actions = set()
for i in range(3):
for j in range(3):
if board[i][j] == EMPTY:
possible_actions.add((i, j))
return possible_actions
def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
"""
new_board = copy.deepcopy(board)
if new_board[action[0]][action[1]] != EMPTY:
return Exception
else:
new_board[action[0]][action[1]] == player(board)
return new_board
# if action in actions(board):
# (i, j) = action
# current_player = player(board)
# new_board = copy.deepcopy(board)
# new_board[i][j] = current_player
# return new_board
# else:
# raise Exception
def winner(board):
"""
Returns the winner of the game, if there is one.
"""
for i in range(3):
if board[i][0] == board[i][1] == board[i][2] == X:
return X
elif board[i][0] == board[i][1] == board[i][2] == O:
return O
for i in range(3):
if board[0][i] == board[1][i] == board[2][i] == X:
return X
elif board[0][i] == board[1][i] == board[2][i] == O:
return O
if board[0][0] == board[1][1] == board[2][2] == X:
return X
if board[0][0] == board[1][1] == board[2][2] == O:
return O
if board[0][2] == board[1][1] == board[2][0] == X:
return X
if board[0][2] == board[1][1] == board[2][0] == O:
return O
return None
def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
if winner(board) == X or winner(board) == O:
return True
else:
for i in range(3):
for j in range(3):
if board[i][j] == EMPTY:
return False
return True
def utility(board):
"""
Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
if winner(board) == X:
return 1
elif winner(board) == O:
return -1
else:
return 0
def minimax(board):
"""
Returns the optimal action for the current player on the board.
"""
if terminal(board):
return None
if player(board) == X:
score = -math.inf
best = None
for action in actions(board):
current = min_value(result(board, action))
if current > score:
score = current
best = action
return best
elif player(board) == O:
score = math.inf
best = None
for action in actions(board):
current = max_value(result(board, action))
if current < score:
score = current
best = action
return best
def max_value(board):
if terminal(board):
return utility(board)
v = -math.inf
for action in actions(board):
v = max(v, min_value(result(board, action)))
return v
def min_value(board):
if terminal(board):
return utility(board)
v = math.inf
for action in actions(board):
v = min(v, max_value(result(board, action)))
return v
I'm working on the tic tac toe problem set. The whole game is working correctly, however I'm having some troubles kicking off the AI. I've been running the game with the computer doing random moves, and it works fine. I've tried implementing the minimax algorithm, and it seems like it works for a while, until I run into the error TypeError: '>' not supported between instances of 'NoneType' and 'float'Since I'm an absolute beginner in AI, (and python), I'm not really sure how to solve this. Regarding the error, I suppose the math.inf and -math.inf are floats, and the state (board) is is the one being NoneType?
Why am I getting an error? What value am I suppose to get? Why is this happening?
Thanks in advance for looking into this.
I've pasted all the code with comments
import math
import random
import copy
X = "X"
O = "O"
EMPTY = None
def initial_state():
"""
Returns starting state of the board.
"""
return [[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]]
def player(board):
"""
Returns player who has the next turn on a board.
- takes a state, tells whos turn it is
- assuming x makes a first move on an empty game board, player is x
- if x has made a move, o is the player
- takes a game board, and returns whos turn it is
"""
empty_board = True
count_x = 0
count_o = 0
f_list = []
"""
for cell in board:
empty_board = all(c is None for c in cell)
print(all(c is None for c in cell))
"""
# Check every item in board.
# Flatten list, and put all elements in new list, for later use
# Check if any of the cells contain X or O. If so, the board is not empty,
# game has began, so we switch empty_board to False
for cell in board:
for i in cell:
f_list.append(i)
if i == X or i == O:
empty_board = False
# If board is empty, return X as the first player
# else, figure out who's turn is next, by counting how many X and O's on the game board.
if empty_board == True:
return X
else:
count_x = f_list.count("X")
count_o = f_list.count("O")
if count_x > count_o:
# print("Computer turn")
return O
else:
# print("Player turn")
return X
def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
- Figures out which possible moves to do next
"""
available_moves = set()
for i in range(3):
for j in range(3):
if board[i][j] == None:
available_moves.add((i, j))
return available_moves
def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
If having a state, and taking an action, we need a result
- takes the board, and the move as input, and returns the new board
with the new action
"""
nboard = copy.deepcopy(board)
if nboard[action[0]][action[1]] == None:
nboard[action[0]][action[1]] = player(board)
else:
raise ValueError("Already taken")
return nboard
def winner(board):
"""
Returns the winner of the game, if there is one.
"""
for i in range(3):
if board[i][0] == board[i][1] == board[i][2]:
return board[i][0]
elif board[0][i] == board[1][i] == board[2][i]:
return board[0][i]
elif board[0][0] == board[1][1] == board[2][2]:
return board[0][0]
elif board[0][2] == board[1][1] == board[2][0]:
return board[0][2]
def terminal(board):
"""
Returns True if game is over, False otherwise.
- takes the game board as input. If the game is over, returns true
"""
# Determine if anyone has 3 in a row
for i in range(3):
if board[i][0] == board[i][1] == board[i][2] != None:
return True
elif board[0][i] == board[1][i] == board[2][i] != None:
return True
elif board[0][0] == board[1][1] == board[2][2] != None:
return True
elif board[0][2] == board[1][1] == board[2][0] != None:
return True
l_flatten = []
# Flattens the board, and check if None is in any of the cells.
# If None is in any cells, the game is not finished, return False.
# If None is NOT in any cells, the game board is filled out, and the game is over
for cell in board:
for i in cell:
l_flatten.append(i)
if None not in l_flatten:
return True
return False
def utility(board):
"""
Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
for i in range(3):
if board[i][0] == board[i][1] == board[i][2]:
if board[i][0] == X:
return 1
elif board[i][0] == O:
return -1
else:
return 0
elif board[0][i] == board[1][i] == board[2][i]:
if board[0][i] == X:
return 1
elif board[i][0] == O:
return -1
else:
return 0
elif board[0][0] == board[1][1] == board[2][2]:
if board[0][0] == X:
return 1
elif board[0][0] == O:
return -1
else:
return 0
elif board[0][2] == board[1][1] == board[2][0]:
if board[0][2] == X:
return 1
elif board[0][2] == O:
return -1
else:
return 0
def minimax(board):
"""
Returns the optimal action for the current player on the board.
should call action to get every move possible for the current player
"""
moves = actions(board)
playing = player(board)
if playing == X:
return max_value(board)
else:
return min_value(board)
return random.choice(tuple(moves))
def max_value(board):
if terminal(board):
print("TERMINAL MAX")
return utility(board)
v = -math.inf
for action in actions(board):
v = max(v, min_value(result(board, action)))
return v
def min_value(board):
if terminal(board):
print("TERMINAL MIN")
return utility(board)
v = math.inf
for action in actions(board):
v = min(v, max_value(result(board, action)))
return v
Edit: Added traceback
Traceback (most recent call last):
File "runner.py", line 115, in <module>
move = ttt.minimax(board)
File "tictactoe.py", line 198, in minimax
return min_value(board)
File "tictactoe.py", line 218, in min_value
v = min(v, max_value(result(board, action)))
File "tictactoe.py", line 209, in max_value
v = max(v, min_value(result(board, action)))
File "tictactoe.py", line 218, in min_value
v = min(v, max_value(result(board, action)))
File "tictactoe.py", line 209, in max_value
v = max(v, min_value(result(board, action)))
File "tictactoe.py", line 218, in min_value
v = min(v, max_value(result(board, action)))
File "tictactoe.py", line 209, in max_value
v = max(v, min_value(result(board, action)))
File "tictactoe.py", line 218, in min_value
v = min(v, max_value(result(board, action)))
File "tictactoe.py", line 209, in max_value
v = max(v, min_value(result(board, action)))
TypeError: '>' not supported between instances of 'NoneType' and 'float'
I am trying to write a minimax algorithm for tic tac toe in cs50ai, but I just can't seem to get my head around it. I can't seem to understand how to get a value back from my recursion between maxval and minval.
How can my script find a value for the game state and compare it to the infinite value and how can I relate that value to the initial move that the for loop is checking out? Currently my code always raises the exception of an invalid action. This makes me feel like it is running until the board is empty and then trying to perform another action.
I have done so much reading and looking through different codes but I can't figure out how to make this work. Any help and hints are appreciated.
"""
Tic Tac Toe Player
"""
import math
import copy
X = "X"
O = "O"
EMPTY = None
def initial_state():
"""
Returns starting state of the board.
"""
return [[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]]
def player(board):
"""
Returns player who has the next turn on a board.
"""
if terminal(board):
return None
if board == initial_state():
return X
if checkturn(board) == X:
return X
elif checkturn(board) == O:
return O
#return turn
#raise NotImplementedError
def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
"""
action_set = set()
coord_x = 0
coord_y = 0
board_size = 3
for row in board:
for item in row:
if item is None:
action_set.add((coord_x, coord_y))
coord_y += 1
if coord_y == board_size:
coord_y = 0
coord_x += 1
return action_set
#raise NotImplementedError
def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
"""
# If the action is not in the action set raise an exception that the move is invalid
#
# DISCUSS THIS WITH HAMISH
action_set = actions(board)
# Make a copy of the previous boards to reference
parentboard = copy.deepcopy(board)
if parentboard[action[0]][action[1]] is not None:
raise Exception("Invalid action")
#Make a new updated board to return so we don't overwrite the old ones
if action in action_set:
newboard = board
newboard[action[0]][action[1]] = player(board)
return newboard
def winner(board):
"""
Returns the winner of the game, if there is one.
"""
row = [[board[0][0], board[0][1], board[0][2]],
[board[1][0], board[1][1], board[1][2]],
[board[2][0], board[2][1], board[2][2]]]
col = [[board[0][0], board[1][0], board[2][0]],
[board[0][1], board[1][1], board[2][1]],
[board[0][2], board[1][2], board[2][2]]]
diag = [[board[0][0], board[1][1], board[2][2]],
[board[2][0], board[1][1], board[0][2]]]
for item in row:
win = check_xo(item)
if win is not None:
return win
for item in col:
check_xo(item)
if win is not None:
return win
for item in diag:
check_xo(item)
if win is not None:
return win
#raise NotImplementedError
def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
action_set = actions(board)
# Check if the actionset is empty. IF so game is over(True)
if len(action_set) == 0:
return True
# Check if someone has won the game. If so game is over (True).
if winner(board) == X or winner(board) == O:
return True
else:
return False
#raise NotImplementedError
def utility(board):
"""
Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
if winner(board) == X:
return 1
elif winner(board) == O:
return -1
elif winner(board) is None:
return 0
#raise NotImplementedError
def minimax(board):
"""
Returns the optimal action for the current player on the board.
"""
#if terminal(board):
# return None
if player(board) == X:
v = -math.inf
for action in actions(board):
v = minval(result(board, action))
if v > value:
return action
if player(board) == O:
v = math.inf
for action in actions(board):
v = maxval(result(board, action))
if v < value:
return action
# My added functions start #
def maxval(board):
if terminal(board):
return utility(board)
value = -math.inf
for action in actions(board):
max(value, minval(result(board, action)))
return value
# Find the move that produces the highest Value
def minval(board):
if terminal(board):
return utility(board)
value = -math.inf
for action in actions(board):
min(value, maxval(result(board, action)))
return value
def checkturn(board):
xCount = 0
oCount = 0
# Count how many Os and Xs are on the board
for i in board:
for j in i:
if j == X:
xCount += 1
if j == O:
oCount += 1
#If O is less than x, then it's O's turn, otherwise it's Xs turn
if oCount < xCount:
return O
else:
return X
def check_xo(item):
if item == ['X', 'X', 'X']:
return X
if item == ['O', 'O', 'O']:
return O
else:
return None
I am tryna do same thing but whenever I run my code I choose my player and put one move then next turn getting error