【问题标题】:How to call a minimax method(with alpha beta pruning) properly如何正确调用 minimax 方法(使用 alpha beta 剪枝)
【发布时间】:2015-08-22 09:32:50
【问题描述】:

这是我实现 alpha beta 剪枝和记忆的 minimax 方法:

public int[] newminimax499(int a, int b){
    int bestPos=-1;
    int alpha= a;
    int beta= b;
    int currentScore;
    //boardShow();
    String stateString = "";                                                
    for (int i=0; i<state.length; i++) 
        stateString += state[i];                        
    int[] oldAnswer = oldAnswers.get(stateString);                          
    if (oldAnswer != null) 
        return oldAnswer;
    if(isGameOver2()!='N'){
        int[] answer = {score(), bestPos};                                    
        oldAnswers.put (stateString, answer);                                   
        return answer;
    }
    else{
        for(int x:getAvailableMoves()){
            if(turn=='O'){  //O is maximizer
                setO(x);
                //System.out.println(stateID++);
                currentScore = newminimax499(alpha, beta)[0];
                //revert(x);
                if(currentScore>alpha){
                    alpha=currentScore;
                    bestPos=x;
                }
                /*if(alpha>=beta){
                    break;
                }*/
            }
            else {  //X is minimizer
                setX(x);
                //System.out.println(stateID++);
                currentScore = newminimax499(alpha, beta)[0];
                //revert(x);
                if(currentScore<beta){
                    beta=currentScore;
                    bestPos=x;
                }
                /*if(alpha>=beta)
                    break;*/
            }
            revert(x);
            if(alpha>=beta)
                break;
        }
    }
    if(turn=='O'){ 
        int[] answer = {alpha, bestPos};                                    
        oldAnswers.put (stateString, answer);                                   
        return answer;
    }
    else {
        int[] answer = {beta, bestPos};                                    
        oldAnswers.put (stateString, answer);                                   
        return answer;
    }
}

作为一个测试游戏,在我的主要方法中,我在某处放置了一个 X(X 是玩家),然后调用 newminimax499 来查看我应该放置 O(计算机)的位置:

 public static void main(String[] args) {
    State3 s=new State3(3);
    int [] result=new int[2];
    s.setX(4);
    result=s.newminimax499(Integer.MIN_VALUE, Integer.MAX_VALUE);
    System.out.println("Score: "+result[0]+" Position: "+ result[1]);
    System.out.println("Run time: " + (endTime-startTime));
    s.boardShow();
}

}

该方法返回计算机应该播放的位置是O(在这种情况下是6),所以我按照指示放置O,为自己播放一个X,调用newminimax499并再次运行代码以查看O要播放的位置以此类推。

public static void main(String[] args) {
    State3 s=new State3(3);
    int [] result=new int[2];
    s.setX(4);
    s.setO(6);//Position returned from previous code run
    s.setX(2);
    s.setO(8);//Position returned from previous code run
    s.setX(3);
    result=s.newminimax499(Integer.MIN_VALUE, Integer.MAX_VALUE);
    System.out.println("Score: "+result[0]+" Position: "+ result[1]);
    System.out.println("Run time: " + (endTime-startTime));
    s.boardShow();
}

在这个特定的运行之后,我得到了结果

Score: 10 Position: 7

哪个好。但是,在我的 GUI 中,这不是调用 newminimax 的方式。每次放置新的 X 或 O 时,板都不会重置。如果我把它放在前面例子中的 main 方法中,它看起来像这样(请记住,它是完全相同的输入序列):

public static void main(String[] args) {
    State3 s=new State3(3);
    int [] result=new int[2];
    s.setX(4); //Player makes his move
    result=s.newminimax499(Integer.MIN_VALUE, Integer.MAX_VALUE);//Where should pc play?
    s.setO(result[1]);//PC makes his move
    s.setX(2);//Player makes his move
    result=s.newminimax499(Integer.MIN_VALUE, Integer.MAX_VALUE);//Where should PC make his move?
    s.setO(result[1]);//PC makes his move
    s.setX(3);//Player makes his move
    result=s.newminimax499(Integer.MIN_VALUE, Integer.MAX_VALUE);
    System.out.println("Score: "+result[0]+" Position: "+ result[1]);
    System.out.println("Run time: " + (endTime-startTime));
    s.boardShow();
}

现在,当以这种方式调用该方法时(这是它在 GUI 中的调用方式),它会返回:

Score: 0 Position: 5

这意味着它没有采取获胜的行动,而是阻止了对手。以这种方式玩了几场比赛后,很明显PC实际上输了。那么为什么这两种调用 newminimax499 的方式会返回不同的结果呢?

这是它在 GUI 上的外观:

注意:运行程序所需的所有方法都可以在此post 中找到。

【问题讨论】:

    标签: java algorithm artificial-intelligence tic-tac-toe minimax


    【解决方案1】:

    您在此处遇到的问题与在国际象棋中使用换位表和 alpha beta 时遇到的问题相同。我不得不反驳你,他们是不相容的!

    正如我之前多次建议的那样,请在尝试实现某些东西之前阅读相应的国际象棋编程维基文章!

    为了使 memo 和 AB 一起工作,您必须为 memo 表中的每个位置保存一个标志,以区分 alpha-cut-nodes、beta-cut-nodes 和精确节点。

    相信我,我从经验中知道他们一起工作;)

    【讨论】:

    • 我浏览了国际象棋编程维基,但找不到与我的井字游戏问题真正直接相关的任何内容。但这可能只是我,因为我没有彻底搜索。无论如何,感谢您的提醒。所以只是我实现它的方式使它们不兼容。
    • @Omar:这是我的意思的链接:chessprogramming.wikispaces.com/Transposition+Table 这就是你的记忆的国际象棋等价物。在“表格条目类型”部分你会发现我的意思。
    • 所以我终于浏览了国际象棋维基 :) 很多信息。但是,如果我可以缩小所有我没有涉及到 2 个问题的事情:1)如果我的程序在表中找到匹配节点时应该如何反应,如果节点是 a)alpha-cut,b)贝塔削减? 2)我应该如何在转置表中实现代表不同类型切割的标志的想法?
    • @Omar 对于 Q1:您不能将 TT 值视为“精确”,而是将其视为最小值和最大值。因此,一个最小值条目可能有助于为玩家 Min 创建一个截止点。
    • @Omar Q2:向每个条目添加布尔值,或者添加一些其他属性,而不是信息。
    【解决方案2】:

    在玩了一堆想法之后,我终于找到了答案,所以不妨发布它。这里讨论的方法 newminimax499 正在尝试实现记忆化和 alpha beta 修剪。由于某种原因,这两个实用程序似乎不兼容(或者至少我对这两个实用程序的实现使它们不兼容)。删除与记忆相关的部分后,该方法变为纯 alpha beta pruning minimax 算法,工作正常,如下所示:

    public int[] newminimax499(int alpha, int beta){
        int bestPos=-1;
        int currentScore;
        if(isGameOver2()!='N'){
            int[] answer = {score(), bestPos};                                    
            return answer;
        }
        else{
            for(int x:getAvailableMoves()){
                if(turn=='O'){  //O is maximizer
                    setO(x);
                    //System.out.println(stateID++);
                    currentScore = newminimax499(alpha, beta)[0];
                    if(currentScore>alpha){
                        alpha=currentScore;
                        bestPos=x;
                    }
                }
                else {  //X is minimizer
                    setX(x);
                    //System.out.println(stateID++);
                    currentScore = newminimax499(alpha, beta)[0];
                    if(currentScore<beta){
                        beta=currentScore;
                        bestPos=x;
                    }
                }
                revert(x);
                if(alpha>=beta)
                    break;
            }
            if(turn=='O'){ 
                int[] answer = {alpha, bestPos};                                    
                return answer;
            }
            else {
                int[] answer = {beta, bestPos};                                    
                return answer;
            }
        }
    }
    

    这个方法现在不仅可以工作(但是你在 main 方法中调用),而且它也比带有记忆的 minimax 快得多。此方法仅用 7 秒计算 4x4 游戏中的第二步。而实现 memoization 的 minimax 大约需要 23 秒。

    【讨论】: