【问题标题】:Is there a problem with my Negamax algorithm with alpha-beta pruning?我的带有 alpha-beta 修剪的 Negamax 算法有问题吗?
【发布时间】:2019-08-14 09:03:17
【问题描述】:

我正在尝试构建一个国际象棋 AI。我使用 alpha-beta 剪枝 (ABP) 的 negamax 函数比使用 ABP 的单独的 min 和 max 函数运行得慢得多(大约 8 倍),尽管返回的移动是相等的。

我的棋盘评估函数总是返回一个关于红色玩家的值,即红色越高越好。仅适用于 Negamax,黑色玩家返回深度 0 时,此值乘以 -1。

我的 Negamax 函数:

int alphaBeta(Board board, int depth, int alpha, int beta) {
    if (depth <= 0 || board.isGameOver()) { // game over == checkmate/stalemate
        int color = board.getCurrPlayer().getAlliance().isRed() ? 1 : -1;
        return BoardEvaluator.evaluate(board, depth) * color;
    }

    int bestVal = Integer.MIN_VALUE + 1;
    for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
        MoveTransition transition = board.getCurrPlayer().makeMove(move);
        if (transition.getMoveStatus().isAllowed()) { // allowed == legal && non-suicidal
            int val = -alphaBeta(transition.getNextBoard(), depth - 1, -beta, -alpha);
            if (val >= beta) {
                return val; // fail-soft
            }
            if (val > bestVal) {
                bestVal = val;
                alpha = Math.max(alpha, val);
            }
        }
    }        

    return bestVal;
}

根调用:

-alphaBeta(transition.getNextBoard(), searchDepth - 1,
                        Integer.MIN_VALUE + 1, Integer.MAX_VALUE); // +1 to avoid overflow when negating

我的最小值和最大值函数:

int min(Board board, int depth, int alpha, int beta) {
    if (depth <= 0 || board.isGameOver()) {
        return BoardEvaluator.evaluate(board, depth);
    }

    int minValue = Integer.MAX_VALUE;
    for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
        MoveTransition transition = board.getCurrPlayer().makeMove(move);
        if (transition.getMoveStatus().isAllowed()) {
            minValue = Math.min(minValue, max(transition.getNextBoard(), depth - 1, alpha, beta));
            beta = Math.min(beta, minValue);
            if (alpha >= beta) break; // cutoff
        }
    }

    return minValue; 
}

int max(Board board, int depth, int alpha, int beta) {
    if (depth <= 0 || board.isGameOver()) {
        return BoardEvaluator.evaluate(board, depth);   
    }

    int maxValue = Integer.MIN_VALUE;
    for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
        MoveTransition transition = board.getCurrPlayer().makeMove(move);
        if (transition.getMoveStatus().isAllowed()) {
            maxValue = Math.max(maxValue, min(transition.getNextBoard(), depth - 1, alpha, beta));
            alpha = Math.max(alpha, maxValue);
            if (alpha >= beta) break; // cutoff
        }
    }

    return maxValue;
}

root分别呼唤红黑玩家:

min(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
max(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);

我猜测 Negamax 函数中的截止存在错误,尽管我遵循了来自 here 的伪代码。任何帮助表示赞赏,谢谢!

编辑:alphaBeta() 的调用次数大约是 min()max() 加起来的 6 倍,而 beta 截止值的数量只有大约 2 倍。

【问题讨论】:

  • 很确定你不应该在深度零返回上“偏向”颜色。除非您的 BoardEvaluator 也这样做? (不应该。)所有评估都应该与红色相关。递归调用的否定处理颜色切换。
  • 看看这个wikipedia page。如果从红色玩家的角度计算节点的启发式值,则需要颜色参数。
  • 也许你是对的。我仍然认为放缓是从那里来的。深度为零的情况是所有叶子,因此将执行最多。您是否考虑过将颜色作为参数传递,而不是像在每片叶子上那样从板上计算?
  • 试过了,结果一样。这是有道理的,因为颜色的计算只涉及一个 getter 方法。
  • 嗯,3 个方法调用,?: 操作和赋值,但没关系。抱歉,我无法提供更多帮助。

标签: java artificial-intelligence chess minimax alpha-beta-pruning


【解决方案1】:

解决了。我也应该发布我的 root 调用的完整代码——没有意识到我没有传递 beta 的新值。 Alpha/beta 实际上是在 root 方法中为单独的 min-max 更新的。

更新了 Negamax 的根方法:

Move bestMove = null;
int bestVal = Integer.MIN_VALUE + 1;

for (Move move : MoveSorter.simpleSort(currBoard.getCurrPlayer().getLegalMoves())) {
    MoveTransition transition = currBoard.getCurrPlayer().makeMove(move);
    if (transition.getMoveStatus().isAllowed()) {
        int val = -alphaBeta(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE + 1, -bestVal);
        if (val > bestVal) {
            bestVal = val;
            bestMove = move;
        }
    }
}

return bestMove;

对于我的问题中提供的信息不足深表歉意——我没想到会出现错误。

【讨论】:

  • 是的,理想情况下,您不应该仅仅为了“根循环”而复制所有代码。但是你的 main 函数必须在任何地方都返回一个 bestMove 和一个 bestVal,从而减慢它的速度。权衡。
  • 即根调用“应该”类似于best = alphaBeta(currBoard, searchDepth, Integer.MIN_VALUE + 1, Integer.MAX_VALUE); return best.Move;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多