我最近报名参加了CS50 AI Python课程,其中一个项目是为井字游戏实现Minimax算法。我寻求帮助并在StackOverflow上搜索,但没有找到能帮到我的答案。图形部分已经实现,你只需要编写模板中给定的函数,我相信我已经正确实现了这些函数,除了算法部分。以下是这些函数:
import mathimport copyX = "X"O = "O"EMPTY = Nonedef initial_state(): """ 返回棋盘的初始状态。 """ return [[EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY]]def player(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 Odef actions(board): """ 返回棋盘上所有可能的动作(i, j)。 """ possible_moves = [] for i in range(3): for j in range(3): if board[i][j] == EMPTY: possible_moves.append([i, j]) return possible_movesdef result(board, action): """ 返回在棋盘上执行动作(i, j)后的棋盘状态。 """ 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): """ 如果有赢家,返回游戏的赢家。 """ columns = [] # 检查行 for row in board: xcounter = row.count(X) ocounter = row.count(O) if xcounter == 3: return X if ocounter == 3: return O # 检查列 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 # 检查对角线 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 # 无赢家/平局 return Nonedef terminal(board): """ 如果游戏结束,返回True,否则返回False。 """ # 检查棋盘是否已满或是否有赢家 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 Falsedef utility(board): """ 如果X赢了游戏,返回1;如果O赢了,返回-1;否则返回0。 """ if winner(board) == X: return 1 elif winner(board) == O: return -1 else: return 0def 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_movedef 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 #FIXEDdef 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’应该利用utility(board)函数最小化得分,该函数在X获胜时返回1,O获胜时返回-1,平局时返回0。到目前为止,AI的移动并非最优,我可以轻松战胜它,而我本不应该能做到这一点,因为在最佳情况下,我应该只能得到平局,因为AI应该能够计算出那一点的所有可能移动。但我不知道哪里出了问题…
回答:
首先谈谈调试:如果你打印出递归调用中进行的计算,你可以追踪问题的执行情况,并迅速找到答案。
但是,你的问题似乎出现在树的顶部。在你的minimax调用中,如果当前玩家是X,你会对状态的每个子节点调用max_value,然后取这些结果的最大值。然而,这在树的顶部对max函数应用了两次。游戏中的下一个玩家是O,所以你应该为下一个玩家调用min_value函数。
因此,在minimax调用中,如果current_player是X,你应该调用min_value;如果是O,则调用max_value。