提问者:小点点

使Minimax返回最佳移动,而不是最佳移动产生的分数


从我所看到的所有示例中,minimax算法将返回一个int值,该值表示最佳移动产生的最佳得分或板状态。如何返回与此分数相关的最佳移动?非常感谢。

private Integer minimax(Board board, Integer depth, Color current, Boolean maximizingPlayer, Integer maxPlayerBestVal, Integer minPlayerBestVal) {
    Integer bestValue;
    if (0 == depth)
        return ((current == selfColor) ? 1 : -1) * this.evaluateBoard(board, current);

    Integer val;
    if (maximizingPlayer) {
        bestValue = -INF;
        for (Move m : board.getPossibleMoves(current)) {
            board.apply(m);
            val = minimax(board, depth - 1, Boolean.FALSE, 
                      minPlayerBestVal, maxPlayerBestVal); // swap here 
            bestValue = Math.max(bestValue, val);
            board.revert(m);
            if (bestValue >= minPlayerBestVal) // too good for the minPlayer
                return bestValue;              // so cut here (pruning)
            }
        return bestValue;
    } else {
        [...] min player
    }
}

the evaluate function

private Integer evaluateBoard(Board board, Color player) {
    return board.pawns(player) - board.pawns(player.other());
}

共2个答案

匿名用户

一种策略是使用类范围的实例变量存储最佳移动(另一种方法可以是返回一对值、值和关联的移动)。每当您发现自己处于顶层递归调用深度时,使用新的更好的移动(在初始深度,我们将检查每一个可能的移动,并选择最终导致具有最佳评估的节点的移动),请设置此最佳移动变量。

因为我们只希望我们的最佳移动是可以从原始状态到达的,所以我们可以跟踪深度,并且只在第一次递归调用时设置最佳移动,或者,当我们在子对象中找到最佳移动时设置它(当新的最佳移动返回给调用方时,它将被覆盖,因此我们最终将从源对象获得一个可用移动)。

请注意,如果从起始板开始的路径在此之前仅遇到终端状态,则可能永远不会达到深度0。例如,探索深度可能为8,但必须捕获所有棋子,并且游戏在接下来的2步内结束,因此调用棋盘。getPossibleMoves()返回一个空数组。这将使最好的行动悬而未决。添加类似于isTerminal(board)的检查可以处理这种情况。

我注意到minPlayerBestValmaxPlayerBestVal(alpha-beta修剪边界)在提供的实现中似乎没有更新。递归调用也缺少Color current参数。

不需要使用原始数据类型的装箱版本;使用intboolean

最后,在不知道你正在编程的游戏的情况下(我想象一些像棋子一样的东西),您为评估提供的启发式可能不完整,并且可能需要考虑在下一步深度移动中没有捕获的位置(如果游戏足够琐碎,如hexapawn,可以进行完全搜索,则完全跳过深度限制)。

这里有一个以上几点的例子。很可能,你需要调整一下,因为我没有你的支持课程:

private Move bestMove;

public Move getBestMove(Board board) {
    minimax(board, 42, selfColor, true, -INF, INF);
    return bestMove;
}

private int minimax(Board board, int depth, Color current, 
                    boolean maximizing, int alpha, int beta) {
    if (depth == 0/* || isTerminal(board)*/) {
        return ((current == selfColor) ? 1 : -1) * 
                   this.evaluateBoard(board, current);
    }
    else if (maximizing) {
        int best = -INF;

        for (Move m : board.getPossibleMoves(current)) {
            board.apply(m);
            int childVal = minimax(board, depth - 1, current, 
                                   false, alpha, beta);
            board.revert(m);

            if (childVal > best) {
                best = childVal;
                alpha = Math.max(alpha, best);
                this.bestMove = m;

                if (alpha >= beta) {
                    break;
                }
            }
        }

        return best;
    }

    int best = INF;

    for (Move m : board.getPossibleMoves(current)) {
        board.apply(m);
        best = Math.min(best, minimax(board, depth - 1, current, 
                                      true, alpha, beta));
        board.revert(m);
        beta = Math.min(beta, best);

        if (alpha >= beta) {
            break;
        }
    }

    return best;
}

匿名用户

一种解决方案是返回一个同时存储最佳移动和最佳得分的对象。另一种方法是只搜索调用另一个搜索函数但返回最佳移动的根。如果移动是整数形式,您可以检查是否在根上,然后返回移动而不是分数。例如,在国际象棋编程中,如果移动是一个捕获,则移动通常是整数,存储移动包含的信息。。。。这是通过按位操作完成的。最后一种解决方案并非适用于所有问题。