Python 中 Tictactoe 的 Minimax 算法
Minimax algorithm for Tictactoe in Python
我最近注册了 CS50 AI python 课程,其中一个项目是为 tictactoe 游戏实现 minimax 算法。我寻求帮助并搜索了 Whosebug,但没有找到可以帮助我的答案。它的图形部分已经实现了,你需要做的就是编写一个模板的给定函数,我相信我做对了,除了算法部分,函数如下:
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 board == initial_state():
return X
xcounter = 0
ocounter = 0
for row in board:
xcounter += row.count(X)
ocounter += row.count(O)
if xcounter == ocounter:
return X
else:
return O
def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
"""
possible_moves = []
for i in range(3):
for j in range(3):
if board[i][j] == EMPTY:
possible_moves.append([i, j])
return possible_moves
def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
"""
boardcopy = copy.deepcopy(board)
try:
if boardcopy[action[0]][action[1]] != EMPTY:
raise IndexError
else:
boardcopy[action[0]][action[1]] = player(boardcopy)
return boardcopy
except IndexError:
print('Spot already occupied')
def winner(board):
"""
Returns the winner of the game, if there is one.
"""
columns = []
# Checks rows
for row in board:
xcounter = row.count(X)
ocounter = row.count(O)
if xcounter == 3:
return X
if ocounter == 3:
return O
# Checks columns
for j in range(len(board)):
column = [row[j] for row in board]
columns.append(column)
for j in columns:
xcounter = j.count(X)
ocounter = j.count(O)
if xcounter == 3:
return X
if ocounter == 3:
return O
# Checks diagonals
if board[0][0] == O and board[1][1] == O and board[2][2] == O:
return O
if board[0][0] == X and board[1][1] == X and board[2][2] == X:
return X
if board[0][2] == O and board[1][1] == O and board[2][0] == O:
return O
if board[0][2] == X and board[1][1] == X and board[2][0] == X:
return X
# No winner/tie
return None
def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
# Checks if board is full or if there is a winner
empty_counter = 0
for row in board:
empty_counter += row.count(EMPTY)
if empty_counter == 0:
return True
elif winner(board) is not None:
return True
else:
return False
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):
current_player = player(board)
if current_player == X:
v = -math.inf
for action in actions(board):
k = min_value(result(board, action)) #FIXED
if k > v:
v = k
best_move = action
else:
v = math.inf
for action in actions(board):
k = max_value(result(board, action)) #FIXED
if k < v:
v = k
best_move = action
return best_move
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 #FIXED
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 #FIXED
最后一部分是 minimax(board) 函数所在的位置,它应该获取棋盘的当前状态并根据 AI 是玩家 'X' 还是 'O'(可以是两者中的任何一个),'X' 玩家尝试最大化分数,而 'O' 应该利用 returns a 1 的效用(棋盘)函数将其最小化X 获胜,-1 代表 'O' 获胜,0 代表平局。
到目前为止,AI 的动作并不是最佳的,我可以在不应该的时候轻松地战胜它,因为在最好的情况下,我应该得到的只是平局,因为 AI 应该计算那一点上的每一个可能的动作。但是不知道怎么回事...
首先谈谈调试:如果您要打印在递归调用中完成的计算,您可以跟踪问题的执行并快速找到答案。
但是,您的问题似乎排在首位。在您的 minimax 调用中,如果当前玩家是 X,您将在状态的每个 children 上调用 max_value,然后取该结果的最大值。但是,这会在树的顶部应用两次 max 函数。游戏中的下一位玩家是 O,因此您应该为下一位玩家调用 min_value 函数。
所以,在 minimax 调用中,如果 current_player 是 X,你应该调用 min_value,如果 current_player 是 O,你应该调用 max_value。
@harsh-kothari,cs50 project page 说
Importantly, the original board should be left unmodified: since Minimax will ultimately require considering many different board states during its computation. This means that simply updating a cell in board itself is not a correct implementation of the result function. You’ll likely want to make a deep copy of the board first before making any changes.
为了避免子列表被修改,我们使用deepcopy而不是copy
改变
对此的操作(板)代码
possibleActions = set()
for i in range(0, len(board)):
for j in range(0, len(board[0])):
if board[i][j] == EMPTY:
possibleActions.add((i, j))
return possibleActions
我最近注册了 CS50 AI python 课程,其中一个项目是为 tictactoe 游戏实现 minimax 算法。我寻求帮助并搜索了 Whosebug,但没有找到可以帮助我的答案。它的图形部分已经实现了,你需要做的就是编写一个模板的给定函数,我相信我做对了,除了算法部分,函数如下:
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 board == initial_state():
return X
xcounter = 0
ocounter = 0
for row in board:
xcounter += row.count(X)
ocounter += row.count(O)
if xcounter == ocounter:
return X
else:
return O
def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
"""
possible_moves = []
for i in range(3):
for j in range(3):
if board[i][j] == EMPTY:
possible_moves.append([i, j])
return possible_moves
def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
"""
boardcopy = copy.deepcopy(board)
try:
if boardcopy[action[0]][action[1]] != EMPTY:
raise IndexError
else:
boardcopy[action[0]][action[1]] = player(boardcopy)
return boardcopy
except IndexError:
print('Spot already occupied')
def winner(board):
"""
Returns the winner of the game, if there is one.
"""
columns = []
# Checks rows
for row in board:
xcounter = row.count(X)
ocounter = row.count(O)
if xcounter == 3:
return X
if ocounter == 3:
return O
# Checks columns
for j in range(len(board)):
column = [row[j] for row in board]
columns.append(column)
for j in columns:
xcounter = j.count(X)
ocounter = j.count(O)
if xcounter == 3:
return X
if ocounter == 3:
return O
# Checks diagonals
if board[0][0] == O and board[1][1] == O and board[2][2] == O:
return O
if board[0][0] == X and board[1][1] == X and board[2][2] == X:
return X
if board[0][2] == O and board[1][1] == O and board[2][0] == O:
return O
if board[0][2] == X and board[1][1] == X and board[2][0] == X:
return X
# No winner/tie
return None
def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
# Checks if board is full or if there is a winner
empty_counter = 0
for row in board:
empty_counter += row.count(EMPTY)
if empty_counter == 0:
return True
elif winner(board) is not None:
return True
else:
return False
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):
current_player = player(board)
if current_player == X:
v = -math.inf
for action in actions(board):
k = min_value(result(board, action)) #FIXED
if k > v:
v = k
best_move = action
else:
v = math.inf
for action in actions(board):
k = max_value(result(board, action)) #FIXED
if k < v:
v = k
best_move = action
return best_move
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 #FIXED
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 #FIXED
最后一部分是 minimax(board) 函数所在的位置,它应该获取棋盘的当前状态并根据 AI 是玩家 'X' 还是 'O'(可以是两者中的任何一个),'X' 玩家尝试最大化分数,而 'O' 应该利用 returns a 1 的效用(棋盘)函数将其最小化X 获胜,-1 代表 'O' 获胜,0 代表平局。 到目前为止,AI 的动作并不是最佳的,我可以在不应该的时候轻松地战胜它,因为在最好的情况下,我应该得到的只是平局,因为 AI 应该计算那一点上的每一个可能的动作。但是不知道怎么回事...
首先谈谈调试:如果您要打印在递归调用中完成的计算,您可以跟踪问题的执行并快速找到答案。
但是,您的问题似乎排在首位。在您的 minimax 调用中,如果当前玩家是 X,您将在状态的每个 children 上调用 max_value,然后取该结果的最大值。但是,这会在树的顶部应用两次 max 函数。游戏中的下一位玩家是 O,因此您应该为下一位玩家调用 min_value 函数。
所以,在 minimax 调用中,如果 current_player 是 X,你应该调用 min_value,如果 current_player 是 O,你应该调用 max_value。
@harsh-kothari,cs50 project page 说
Importantly, the original board should be left unmodified: since Minimax will ultimately require considering many different board states during its computation. This means that simply updating a cell in board itself is not a correct implementation of the result function. You’ll likely want to make a deep copy of the board first before making any changes.
为了避免子列表被修改,我们使用deepcopy而不是copy
改变 对此的操作(板)代码
possibleActions = set()
for i in range(0, len(board)):
for j in range(0, len(board[0])):
if board[i][j] == EMPTY:
possibleActions.add((i, j))
return possibleActions