web-dev-qa-db-ja.com

PythonでのTictactoeのミニマックスアルゴリズム

私は最近CS50 AIに登録しましたpythonコースです。プロジェクトの1つは、tictactoeゲームのミニマックスアルゴリズムを実装することです。ヘルプを探してスタックオーバーフローを検索しましたが、その答えは見つかりませんでしたそれのグラフィック部分はすでに実装されており、あなたがする必要があるのはテンプレートの与えられた関数をプログラムすることだけです、そして私はアルゴリズムの部分を除いてそれらを正しく理解していると思います、関数は次の通りです: :

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」であるかどうかに応じて、可能な最高の動きを計算します(それは2)、「X」プレーヤーはスコアを最大化しようとし、「O」は、Xの勝ちに1、「O」の勝ちに-1、または引き分けに0を返すユーティリティ(ボード)関数を使用してスコアを最小化する必要があります。これまでのところ、AIの動きは最適ではなく、AIがその時点で可能なすべての動きを計算することになっているので、最良のシナリオでは得られるのはネクタイだけなので、私はすべきでないときに簡単に勝つことができます。しかし、何が悪いのかわかりません...

1
Albert

まず、デバッグに関する一言:再帰呼び出しで行われた計算を出力する場合、問題の実行を追跡し、答えをすばやく見つけることができます。

しかし、あなたの問題はツリーの最上位にあるようです。 minimax呼び出しで、現在のプレーヤーがXの場合、状態の子のそれぞれでmax_valueを呼び出し、その結果の最大値を取得します。ただし、これにより、ツリーの最上部でmax関数が2回適用されます。ゲームの次のプレーヤーはOなので、次のプレーヤーのmin_value関数を呼び出す必要があります。

したがって、minimax呼び出しでは、current_playerがXの場合はmin_valueを呼び出し、current_playerがOの場合はmax_valueを呼び出す必要があります。

0
Nathan S.

@ harsh-kothari、 cs50プロジェクトページ は言う

重要なことに、元のボードは変更しないでおく必要があります。Minimaxは最終的に、計算中にさまざまなボードの状態を考慮する必要があるためです。つまり、ボード自体のセルを更新するだけでは、結果関数を正しく実装できません。変更を加える前に、まずボードの詳細なコピーを作成することをお勧めします。

サブリストが変更されないようにするために、コピーではなくディープコピーを使用します

copy()およびdeepcopy()の詳細はこちら

0
chaha0s