关于java:如何实现高效的Alpha-Beta剪枝游戏搜索树?

How to implement efficient Alpha-Beta pruning Game Search Tree?

我正在尝试学习人工智能以及如何在程序中实现它。最容易开始的地方可能是简单的游戏(在本例中为井字游戏)和游戏搜索树(递归调用;不是实际的数据结构)。我在有关该主题的讲座中发现了这个非常有用的视频。

我遇到的问题是对算法的第一次调用需要很长时间(大约 15 秒)才能执行。我已经在整个代码中放置了调试日志输出,看起来它调用了算法的某些部分的次数过多。

以下是为计算机选择最佳移动的方法:

    public Best chooseMove(boolean side, int prevScore, int alpha, int beta){
    Best myBest = new Best();
    Best reply;

    if (prevScore == COMPUTER_WIN || prevScore == HUMAN_WIN || prevScore == DRAW){
        myBest.score = prevScore;
        return myBest;
    }

    if (side == COMPUTER){
        myBest.score = alpha;
    }else{
        myBest.score = beta;
    }
    Log.d(TAG,"Alpha:" + alpha +" Beta:" + beta +" prevScore:" + prevScore);
    Move[] moveList = myBest.move.getAllLegalMoves(board);
    for (Move m : moveList){
        String choice;
        if (side == HUMAN){
            choice = playerChoice;
        }else if (side == COMPUTER && playerChoice.equals("X")){
            choice ="O";
        }else{
            choice ="X";
        }
        Log.d(TAG,"Current Move: column-" + m.getColumn() +" row-" + m.getRow());
        int p = makeMove(m, choice, side);
        reply = chooseMove(!side, p, alpha, beta);
        undoMove(m);
        if ((side == COMPUTER) && (reply.score > myBest.score)){
            myBest.move = m;
            myBest.score = reply.score;
            alpha = reply.score;
        }else if((side == HUMAN) && (reply.score < myBest.score)){
            myBest.move = m;
            myBest.score = reply.score;
            beta = reply.score;
        }//end of if-else statement
        if (alpha >= beta) return myBest;
    }//end of for loop
    return myBest;
}

如果位置为空,则 makeMove 方法进行移动并返回一个值(-1 - 人类获胜,0 - 平局,1 - 计算机获胜,-2 或 2 - 否则)。虽然我相信错误可能出在 getAllLegalMoves 方法中:

    public Move[] getAllLegalMoves(String[][] grid){
    //I'm unsure whether this method really belongs in this class or in the grid class, though, either way it shouldn't matter.
    items = 0;
    moveList = null;
    Move move = new Move();

    for (int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            Log.d(TAG,"At Column:" + i +" At Row:" + j);
            if(grid[i][j] == null || grid[i][j].equals("")){
                Log.d(TAG,"Is Empty");
                items++;
                if(moveList == null || moveList.length < items){
                    resize();
                }//end of second if statement
                move.setRow(j);
                move.setColumn(i);
                moveList[items - 1] = move;
            }//end of first if statement
        }//end of second loop
    }//end of first loop
    for (int k = 0; k < moveList.length; k++){
        Log.d(TAG,"Count:" + k +" Column:" + moveList[k].getColumn() +" Row:" + moveList[k].getRow());
    }
    return moveList;
}

private void resize(){
    Move[] b = new Move[items];
    for (int i = 0; i < items - 1; i++){
        b[i] = moveList[i];
    }
    moveList = b;
}

总而言之:是什么导致我的决定,选择最佳行动,需要这么长时间?我错过了什么?有没有更简单的方法来实现这个算法?任何帮助或建议将不胜感激,谢谢!

相关讨论

  • 您是否尝试过在代码上使用分析器?
  • 不,我认为这个问题更多地与它被调用太多次的事实有关。我的意思是它递归调用 chooseBest 方法的次数比可能要分析的不同组合的次数多。所以每次递归调用所花费的时间并不是问题。
  • 我没有看到 chooseBest 方法。
  • 抱歉,我的意思是选择移动

一个带有 alpha beta 剪枝的极小极大树应该被可视化为一棵树,树的每个节点都是一个可能的移动,很多轮到未来,它的子节点是所有可以从中获取的移动。

为了尽可能快并保证您只需要与您正在向前看的移动数量呈线性关系的空间,您可以进行深度优先搜索并从一侧"扫描"到另一侧。例如,如果您想象正在构建整个树,那么您的程序实际上一次只会构建一个从引导到根的单链,并丢弃它完成的任何部分。

此时我将复制维基百科的伪代码,因为它真的非常简洁明了:

function alphabeta(node, depth, α, β, Player)        
    if  depth = 0 or node is a terminal node
        return score
    if  Player = MaxPlayer
        for each child of node
            α := max(α, alphabeta(child, depth-1, α, β, not(Player) ))    
            if β ≤ α
                break                             (* Beta cut-off *)
        return α
    else
        for each child of node
            β := min(β, alphabeta(child, depth-1, α, β, not(Player) ))    
            if β ≤ α
                break                             (* Alpha cut-off *)
        return β

注意事项:

-\\'for each child of node\\' - 与其编辑当前棋盘的状态,不如创建一个全新棋盘,它是应用移动的结果。通过使用不可变对象,您的代码将不太容易出现错误,并且总体上可以更快地进行推理。

-要使用此方法,请在当前状态下为您可以进行的所有可能移动调用它,将其深度 -1,-Infinity 用于 alpha,Infinity 用于 beta,并且应该从不动玩家开始\\轮到这些调用中的每一个 - 返回最高值的调用是最好的调用。

它在概念上非常非常简单。如果你编码正确,那么你永远不会一次实例化超过(深度)板,你永远不会考虑无意义的分支等等。

相关讨论

  • 我明白它是如何工作的。我想知道为什么我的实现不能正常工作。我似乎找不到问题所在。正如您所看到的,在我提供的代码中,我正在构建一个递归树,然后在需要时返回适当的值。
  • @Android Student 我不能说,那么 - 你必须自己分析/调试你的代码,因为没有什么是明显的错误。 🙂

我不会为你分析你的代码,但是因为这是一个很好的编码 kata,所以我为井字游戏写了一个小 ai:

import java.math.BigDecimal;

public class Board {

    /**
     * -1: opponent
     * 0: empty
     * 1: player
     */

    int[][] cells = new int[3][3];

    /**
     * the best move calculated by eval(), or -1 if no more moves are possible
     */

    int bestX, bestY;

    int winner() {
        // row
        for (int y = 0; y < 3; y++) {
            if (cells[0][y] == cells[1][y] && cells[1][y] == cells[2][y]) {
                if (cells[0][y] != 0) {
                    return cells[0][y];
                }
            }
        }

        // column
        for (int x = 0; x < 3; x++) {
            if (cells[x][0] == cells[x][1] && cells[x][1] == cells[x][2]) {
                if (cells[x][0] != 0) {
                    return cells[x][0];
                }
            }
        }

        // 1st diagonal
        if (cells[0][0] == cells[1][1] && cells[1][1] == cells[2][2]) {
            if (cells[0][0] != 0) {
                return cells[0][0];
            }
        }

        // 2nd diagonal
        if (cells[2][0] == cells[1][1] && cells[1][1] == cells[0][2]) {
            if (cells[2][0] != 0) {
                return cells[2][0];
            }
        }

        return 0; // nobody has won
    }

    /**
     * @return 1 if side wins, 0 for a draw, -1 if opponent wins
     */

    int eval(int side) {
        int winner = winner();
        if (winner != 0) {
            return side * winner;
        } else {
            int bestX = -1;
            int bestY = -1;
            int bestValue = Integer.MIN_VALUE;
        loop:
            for (int y = 0; y < 3; y++) {
                for (int x = 0; x < 3; x++) {
                    if (cells[x][y] == 0) {
                        cells[x][y] = side;
                        int value = -eval(-side);
                        cells[x][y] = 0;

                        if (value > bestValue) {
                            bestValue = value;
                            bestX = x;
                            bestY = y;
                            if (bestValue == 1) {
                                // it won't get any better, we might as well stop thinking
                                break loop;
                            }
                        }
                    }
                }
            }
            this.bestX = bestX;
            this.bestY = bestY;
            if (bestValue == Integer.MIN_VALUE) {
                // there were no moves left, it must be a draw!
                return 0;
            } else {
                return bestValue;
            }
        }
    }

    void move(int side) {
        eval(side);
        if (bestX == -1) {
            return;
        }
        cells[bestX][bestY] = side;
        System.out.println(this);

        int w = winner();
        if (w != 0) {
            System.out.println("Game over!");
        } else {
            move(-side);
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        char[] c = {'O', ' ', 'X'};
        for (int y = 0; y < 3; y++) {
            for (int x = 0; x < 3; x++) {
                sb.append(c[cells[x][y] + 1]);
            }
            sb.append('\
'
);
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        long start = System.nanoTime();
        Board b = new Board();
        b.move(1);
        long end = System.nanoTime();
        System.out.println(new BigDecimal(end - start).movePointLeft(9));
    }
}

精明的读者会注意到我没有使用 alpha/beta 截止值。尽管如此,在我有点过时的笔记本上,这在 0.015 秒内完成了游戏......

没有分析您的代码,我无法确定问题出在哪里。但是,您在搜索树的每个节点上记录每个可能的移动可能与它有关。


以上是关于java:如何实现高效的Alpha-Beta剪枝游戏搜索树?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>