【问题标题】:Minimax algorithm for Tictactoe in PythonPython中Tictactoe的Minimax算法
【发布时间】:2020-04-07 03:07:24
【问题描述】:

我最近参加了 CS50 AI python 课程,要做的项目之一是为 tictactoe 游戏实现 minimax 算法。我寻求帮助并搜索了stackoverflow,但没有找到可以帮助我的答案。它的图形部分已经实现,您需要做的就是编写模板的给定功能,我相信我做对了,除了算法部分,功能如下:

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'应该使用实用程序(board)函数将其最小化,该函数返回 1 表示 X 获胜,-1 表示“O”获胜或 0 表示联系。 到目前为止,人工智能的动作并不是最优的,当我不应该战胜它时,我可以很容易地战胜它,因为在最好的情况下,我应该得到的只是平局,因为人工智能应该在那个时候计算每一个可能的动作。但是不知道怎么回事……

【问题讨论】:

  • 为什么在得到答案后还要编辑代码?如果你只是用解决方案更新代码,它就违背了 Q 和 A 的目的。

标签: python algorithm artificial-intelligence minimax


【解决方案1】:

首先谈谈调试:如果您要打印在递归调用中完成的计算,您可以跟踪问题的执行并快速找到答案。

但是,您的问题似乎在树的顶部。在您的 minimax 调用中,如果当前玩家是 X,您将调用该状态的每个子​​级的 max_value,然后取该结果的最大值。但是,这在树的顶部应用了两次 max 函数。游戏中的下一位玩家是 O,因此您应该为下一位玩家调用 min_value 函数。

因此,在 minimax 调用中,如果 current_player 为 X,则应调用 min_value,如果 current_player 为 O,则应调用 max_value。

【讨论】:

  • 您的观察是正确的!就好像计算机假设玩家已经连续转了 2 圈……现在已经解决了。然后我遵循了您的调试建议,发现 Max/Min 函数没有循环遍历所有操作,因为它们在第一个循环/操作时到达了 return v 行,所以我遇到了一个缩进问题,因为 return v 行有在 for 循环之外以允许所有迭代。非常感谢
【解决方案2】:

@harsh-kothari,cs50 project page

重要的是,原始棋盘应保持不变:因为 Minimax 最终需要在计算过程中考虑许多不同的棋盘状态。这意味着简单地更新板上的单元格本身并不是结果函数的正确实现。在进行任何更改之前,您可能需要先制作一个板的深层副本。

为了避免子列表被修改,我们使用 deepcopy 代替 copy

Read more about copy() and deepcopy() here

【讨论】:

    【解决方案3】:

    改变 对此的操作(板)代码

    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
    

    【讨论】:

      猜你喜欢
      • 2021-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-26
      相关资源
      最近更新 更多