【问题标题】:Negamax implementation doesn't appear to work with tic-tac-toeNegamax 实现似乎不适用于井字游戏
【发布时间】:2012-09-14 17:00:56
【问题描述】:

我已经实现了 Negamax,因为它可以在 wikipedia 上找到,其中包括 alpha/beta 修剪。

但是,它似乎倾向于失败的举动,据我所知,这是一个无效的结果。

游戏是井字游戏,我已经抽象了大部分游戏玩法,因此应该很容易发现算法中的错误。

#include <list>
#include <climits>
#include <iostream>

//#define DEBUG 1

using namespace std;

struct Move {
    int row, col;

    Move(int row, int col) : row(row), col(col) { }
    Move(const Move& m) { row = m.row; col = m.col; }
};

struct Board {
    char player;
    char opponent;
    char board[3][3];

    Board() { }

    void read(istream& stream) {
        stream >> player;
        opponent = player == 'X' ? 'O' : 'X';

        for(int row = 0; row < 3; row++) {
            for(int col = 0; col < 3; col++) {
                char playa;

                stream >> playa;
                board[row][col] = playa == '_' ? 0 : playa == player ? 1 : -1;
            }
        }
    }

    void print(ostream& stream) {
        for(int row = 0; row < 3; row++) {
            for(int col = 0; col < 3; col++) {
                switch(board[row][col]) {
                    case -1:
                        stream << opponent;
                        break;

                    case 0:
                        stream << '_';
                        break;

                    case 1:
                        stream << player;
                        break;

                }
            }
            stream << endl;
        }
    }

    void do_move(const Move& move, int player) {
        board[move.row][move.col] = player;
    }

    void undo_move(const Move& move) {
        board[move.row][move.col] = 0;
    }

    bool isWon() {
        if (board[0][0] != 0) {
            if (board[0][0] == board[0][1] &&
                    board[0][1] == board[0][2])
                return true;

            if (board[0][0] == board[1][0] &&
                    board[1][0] == board[2][0])
                return true;
        }

        if (board[2][2] != 0) {
            if (board[2][0] == board[2][1] &&
                    board[2][1] == board[2][2])
                return true;

            if (board[0][2] == board[1][2] &&
                    board[1][2] == board[2][2])
                return true;
        }

        if (board[1][1] != 0) {
            if (board[0][1] == board[1][1] &&
                    board[1][1] == board[2][1])
                return true;

            if (board[1][0] == board[1][1] &&
                    board[1][1] == board[1][2])
                return true;

            if (board[0][0] == board[1][1] &&
                    board[1][1] == board[2][2])
                return true;

            if (board[0][2] == board [1][1] &&
                    board[1][1] == board[2][0])
                return true;
        }

        return false;
    }

    list<Move> getMoves() {
        list<Move> moveList;

        for(int row = 0; row < 3; row++)
            for(int col = 0; col < 3; col++)
                if (board[row][col] == 0)
                    moveList.push_back(Move(row, col));

        return moveList;
    }
};

ostream& operator<< (ostream& stream, Board& board) {
    board.print(stream);
    return stream;
}

istream& operator>> (istream& stream, Board& board) {
    board.read(stream);
    return stream;
}

int evaluate(Board& board) {
    int score = board.isWon() ? 100 : 0;

    for(int row = 0; row < 3; row++)
        for(int col = 0; col < 3; col++)
            if (board.board[row][col] == 0)
                score += 1;

    return score;
}

int negamax(Board& board, int depth, int player, int alpha, int beta) {
    if (board.isWon() || depth <= 0) {
#if DEBUG > 1
        cout << "Found winner board at depth " << depth << endl;
        cout << board << endl;
#endif
        return player * evaluate(board);
    }

    list<Move> allMoves = board.getMoves();

    if (allMoves.size() == 0)
        return player * evaluate(board);

    for(list<Move>::iterator it = allMoves.begin(); it != allMoves.end(); it++) {
        board.do_move(*it, -player);
        int val = -negamax(board, depth - 1, -player, -beta, -alpha);
        board.undo_move(*it);

        if (val >= beta)
            return val;

        if (val > alpha)
            alpha = val;
    }

    return alpha;
}

void nextMove(Board& board) {
    list<Move> allMoves = board.getMoves();
    Move* bestMove = NULL;
    int bestScore = INT_MIN;

    for(list<Move>::iterator it = allMoves.begin(); it != allMoves.end(); it++) {
        board.do_move(*it, 1);
        int score = -negamax(board, 100, 1, INT_MIN + 1, INT_MAX);
        board.undo_move(*it);

#if DEBUG
        cout << it->row << ' ' << it->col << " = " << score << endl;
#endif

        if (score > bestScore) {
            bestMove = &*it;
            bestScore = score;
        }
    }

    if (!bestMove)
        return;

    cout << bestMove->row << ' ' << bestMove->col << endl;

#if DEBUG
    board.do_move(*bestMove, 1);
    cout << board;
#endif

}

int main() {
    Board board;

    cin >> board;
#if DEBUG
    cout << "Starting board:" << endl;
    cout << board;
#endif

    nextMove(board);
    return 0;
}

给出这个输入:

O
X__
___
___

算法选择将棋子放在0、1,造成一定的损失,做这个陷阱(没有什么可以赢或以平局告终):

XO_
X__
___

我很确定游戏实现是正确的,但算法也应该是正确的。

编辑:更新了evaluatenextMove

EDIT2:修复了第一个问题,但似乎仍然存在错误

【问题讨论】:

  • 请注意,这是 X 的输家,所以 (2,0)比 (1,0) 更好。
  • @Beta:嗯...,不,不是。玩井字游戏很多吗? X 必须在 (2,0) 处阻止,然后 O 必须在 (1,0) 处阻止,然后 X 必须在 (1,2) 处阻止,然后没有人获胜。
  • @KeithRandall,[facepalm] 没关系。

标签: c++ algorithm tic-tac-toe minimax


【解决方案1】:

您的evaluate 函数会计算空格,并且无法识别获胜的棋盘。

编辑:
nextMove 中还有一个相对较小的问题。该行应该是

int score = -negamax(board, 0, -1, INT_MIN + 1, INT_MAX);

修复该问题(和evaluate),代码会选择正确的移动。

编辑:

这解决了它:

if (board.isWon() || depth <= 0) {
#if DEBUG > 1
  cout << "Found winner board at depth " << depth << endl;
  cout << board << endl;
#endif
  return -100;                                                      
}

几乎所有这些问题都源于不清楚score的含义。这是从player的角度来看的。如果negamax正在评估玩家1的位置,而玩家1无法获胜,则分数应该较低(例如-100);如果negamax 正在评估玩家-1 的位置,而玩家-1 无法获胜,则分数应该较低(例如-100)。如果evaluate()无法区分玩家,那么返回player * evaluate(board)的分数是错误的。

【讨论】:

  • 我已按照您的建议修复了代码,但是,我仍然得到相同的结果。我已更新代码以反映更改。
  • @GeorgeJiglau, if (board.isWon()) {score *= 100;} 0*100 是什么?
  • 我又修复了evaluate,不过,这只应该影响最后一步获胜的游戏,不会影响当前的测试用例。
  • 太棒了!我并没有真正想出评估应该从玩家的角度而不是从全局的角度来评估董事会。谢谢!
【解决方案2】:

isWon 对玩家的胜利或失败都返回 true。这没用。

【讨论】:

  • 没关系,检查是否赢了意味着当前的举动产生了游戏结束,因此无需检查进一步的举动。
  • 但是 negamax 返回的分数需要以某种方式不同,才能确定输赢。 isWonevaluate 都需要包含这些信息...
  • 我已经在评估函数中加入了这个语义。谢谢!
【解决方案3】:

播放器的使用似乎有些有趣。

您的顶层循环调用“board.do_move(*it, 1);”然后使用 player=1 调用 negamax。

然后 negamax 将调用“board.do_move(*it, player);”,所以看起来第一个玩家有效地得到了 2 个动作。

【讨论】:

  • 哦,对了!谢谢!另外,在顶级循环中,我是否应该将分数分配为 negamax 的否定?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 2015-01-08
相关资源
最近更新 更多