【问题标题】:Minmax algorithm doesn't return direct child of root (returns illegal move)Minmax 算法不返回根的直接子节点(返回非法移动)
【发布时间】:2019-05-23 17:44:34
【问题描述】:

我正在尝试为九人莫里斯创建“AI”,但我在 minMax 算法上遇到了困难。总而言之,我试图找到这个问题超过 10 小时,但没有成功。 (调试这个递归很讨厌,或者我做得不好或两者兼而有之)

由于我开始怀疑我写的所有内容,我决定发布我的问题,以便有人可以在我的 minMax 版本中发现任何错误。我意识到如果没有整个应用程序,这真的是一项艰巨的任务,所以任何我应该对我的代码进行三次检查的建议也非常受欢迎。

这里是视频的链接,解释了 minMax,我的实现基于该链接:https://www.youtube.com/watch?v=l-hh51ncgDI(搜索 minmax 后弹出的第一个视频 - 以防万一您想观看视频而不想要点击链接)

没有 alpha-beta 修剪的我的 minMax:

    //turn - tells which player is going to move
//gameStage - what action can be done in this move, where possible actions are: put pawn, move pawn, take opponent's pawn
//depth - tells how far down the game tree should minMax go
//spots - game board
private int minMax(int depth, Turn turn, GameStage gameStage, Spot[] spots){
    if(depth==0){
        return evaluateBoard(spots);
    }

    //in my scenario I am playing as WHITE and "AI" is playing as BLACK
    //since heuristic (evaluateBoard) returns number equal to black pawns - white pawns
    //I have decided that in my minMax algorithm every white turn will try to minimize and black turn will try to maximize
    //I dont know if this is correct approach but It seems logical to me so let me know if this is wrong
    boolean isMaximizing = turn.equals(Turn.BLACK);

    //get all possible (legal) actions based on circumstances
    ArrayList<Action> children = gameManager.getAllPossibleActions(spots,turn,gameStage);

    //this object will hold information about game circumstances after applying child move
    //and this information will be passed in recursive call
    ActionResult result;

    //placeholder for value returned by minMax()
    int eval;

    //scenario for maximizing player
    if(isMaximizing){
        int maxEval = NEGATIVE_INF;
        for (Action child : children){
            //aplying possible action (child) and passing its result to recursive call
            result = gameManager.applyMove(child,turn,spots);

            //evaluate child move
            eval = minMax(depth-1,result.getTurn(),result.getGameStage(),result.getSpots());

            //resets board (which is array of Spots) so that board is not changed after minMax algorithm
            //because I am working on the original board to avoid time consuming copies
            gameManager.unapplyMove(child,turn,spots,result);

            if(maxEval<eval){
                maxEval = eval;

                //assign child with the biggest value to global static reference
                Instances.theBestAction = child;
            }
        }
        return maxEval;
    }
    //scenario for minimizing player - the same logic as for maximizing player but for minimizing
    else{
        int minEval = POSITIVE_INF;
        for (Action child : children){
            result = engine.getGameManager().applyMove(child,turn,spots);
            eval = minMax(depth-1,result.getTurn(),result.getGameStage(),result.getSpots());
            engine.getGameManager().unapplyMove(child,turn,spots,result);
            if(minEval>eval){
                minEval=eval;
                Instances.theBestAction = child;
            }
        }
        return minEval;
    }
}

简单的启发式评估:

//calculates the difference between black pawns on board
//and white pawns on board
public int evaluateBoard(Spot[] spots) {
    int value = 0;
    for (Spot spot : spots) {
        if (spot.getTurn().equals(Turn.BLACK)) {
            value++;
        }else if(spot.getTurn().equals(Turn.WHITE)){
            value--;
        }
    }
    return value;
}

我的问题:

    //the same parameters as in minMax() function
public void checkMove(GameStage gameStage, Turn turn, Spot[] spots) {

    //one of these must be returned by minMax() function
    //because these are the only legal actions that can be done in this turn
    ArrayList<Action> possibleActions = gameManager.getAllPossibleActions(spots,turn,gameStage);

    //I ignore int returned by minMax() because,
    //after execution of this function, action choosed by minMax()  is assigned
    //to global static reference
    minMax(1,turn,gameStage,spots);

    //getting action choosed by minMax() from global
    //static reference
    Action aiAction = Instances.theBestAction;

    //flag to check if aiAction is in possibleActions
    boolean wasFound = false;

    //find the same action returned by minMax() in possibleActions
    //change the flag upon finding one
    for(Action possibleAction : possibleActions){
        if(possibleAction.getStartSpotId() == aiAction.getStartSpotId() &&
                possibleAction.getEndSpotId() == aiAction.getEndSpotId() &&
                possibleAction.getActionType().equals(aiAction.getActionType())){
            wasFound = true;
            break;
        }
    }
    //when depth is equal to 1 it always is true
    //because there is no other choice, but
    //when depth>1 it really soon is false
    //so direct child of root is not chosen
    System.out.println("wasFound?: "+wasFound);
}

我实现 minMax 算法背后的想法是否正确?

【问题讨论】:

    标签: java recursion minmax game-theory


    【解决方案1】:

    我认为错误可能存在于您正在更新Instances.theBestAction,即使在评估子移动时也是如此。

    例如,假设“移动 4”是最终将返回的真正最佳移动,但在评估“移动 5”时,theBestAction 设置为“移动 5”的最佳子动作。从此时起,您不会将原来的 theBestAction 更新回“移动 4”。

    也许只是一个简单的条件,只在depth == originalDepth 时设置theBestAction

    除了使用全局变量之外,您还可以考虑返回一个结构/对象,其中包含最佳得分和获得得分的动作。

    【讨论】:

    • 感谢您的意见。我将尝试返回 Pair of value and action 并通过更新进行编辑,无论它是否有帮助。但是我不知道条件depth == originalDepth 的解决方案是否可行,至少我无法想象当递归返回到第一次调用时如何编写 if 语句检查。但是你是对的,除了原始深度上的子移动之外,我不应该让子移动设置为theBestAction,但我认为递归应该一直回到顶部并重写之前设置的每个theBestAction .也许这就是我犯错的地方。
    • 您是对的,您对返回对象的建议解决了我的问题,ai 正在正常工作。非常感谢,你救了我,因为我还有几个小时的最后期限:)
    • 太棒了!很高兴我及时赶到。如果你有兴趣更多地探索这个算法,你应该阅读 NegaMax、Alpha-Beta 剪枝和转置表。所有这些都是非常了不起的改进。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多