Minimax Alpha Beta修剪算法花费太多时间来解决井字游戏(10x10电路板)

时间:2018-07-16 14:46:50

标签: javascript algorithm machine-learning minimax alpha-beta-pruning

我用Javascript制作了两种井字游戏。一个是3x3,另一个是10x10。

我正在将Minimax算法与Alpha Beta修剪一起使用来解决这两个游戏。在游戏树非常小的3x3中,该算法可以正常工作。

但是在10x10中,这会花费太多时间。该代码甚至无法在10分钟内一动不动。我运行了算法,等待了10分钟,它仍然在计算,然后才关闭浏览器选项卡。 (如果我让代码运行大声笑,可能甚至需要数小时,数天,数周的时间)

我读了几篇文章,说带有Alpha Beta修剪功能的Minimax可以轻松解决Tic Tac Toe 10x10或更大的问题。是错误的,还是我的代码不正确?

这是我的代码,但是我认为,很难理解它。但是我猜代码并不重要。我应用了Minimax + Alpha Beta修剪。我还可以做些什么?

function makeBotMove(newBoard, availMoves, XorO, firstCall) { // newBoard stores board state in an array. availMoves stores Available moves in an array (0-99). XorO store either "X" or "O" depending on whoes turn it is. firstCall is used to find out If the call is made inside the function or not. I need it for Alpha Beta Pruning. It helps in storing the length of the total available moves when the call was made for
    if (firstCall)
    {
        var originalAvailMovesLength = availMoves.length;
        if (originalAvailMovesLength == board.length)
            var maxPossibleResult = 0.5; // OriginalAvailMoves will be only 100, if it is the first move. And if it is first move, it is impossible to get reward of 1. The best the computer can do is, draw (0.5 reward). 
        else
            var maxPossibleResult = 1;
    }

    availMoves = getAvailableMoves(newBoard);

    var result = checkResult(newBoard, false); // It can return 4 values. 1 = Win, 0.5 = Draw, 0 = Game is on, -1 = Lose.
    if (result != 0) 
        return [result];

    var movesIndex = []; 
    var movesScore = []; 
    for (var i = 0; i < availMoves.length; i++)
    {

        var move = availMoves[i];  
        newBoard[move] = XorO; 
        availMoves.splice(availMoves.indexOf(Number(move)),1); 
        if (XorO == "O") // 1.) Yes 
            var reward = makeBotMove(newBoard, availMoves, "X", false); 
        else 
            var reward = makeBotMove(newBoard, availMoves, "O", false); 

        newBoard[move] = "-"; 

        availMoves.push(move);
        availMoves.sort();


        movesIndex.push(move); 
        movesScore.push(reward[0]); 
        var bestMove = [];
        if (originalAvailMovesLength == availMoves.length && Math.max(...movesScore) == maxPossibleResult)
        {
            bestMove[0] = Math.max(...movesScore);
            bestMove[1] = movesScore.indexOf(bestMove[0]);
            bestMove[1] = movesIndex[bestMove[1]];
            return bestMove;
        }
    }


    if (XorO == "O") 
        bestMove[0] = Math.max(...movesScore);
    else 
        bestMove[0] = Math.min(...movesScore);

    bestMove[1] = movesScore.indexOf(bestMove[0]);
    bestMove[1] = movesIndex[bestMove[1]];

    return bestMove;

}

如果使用minimax算法,则无法完成这项工作。你们推荐哪种算法?一定不是很复杂,到目前为止我还不是一个好的编码员。

编辑:在10x10中,玩家需要连续放置5步才能获胜,而不是3步。

1 个答案:

答案 0 :(得分:1)

您的代码显示,您继续进行递归调用,直到赢/输或棋盘已满。由于在专家之间进行的游戏中,单排5行并不是一件容易的事,因此这种搜索可能必须访问大多数绘图位置,我估计这大约相当于一个位置上的10 100 个位置10x10的木板,给定100!几乎是10 158 (但我们需要从所有获胜和亏损中减去)。无论如何,要搜索如此多的板子是不现实的,因为可见宇宙中的原子数要少于这个数目。因此,请勿等待代码完成。它不会在你的一生中。

有两种通用的方法可以减少花在计算好动作上的时间:

  1. 减少搜索树的深度
  2. 减少搜索树的宽度

对于第一个动作,您可以定义递归搜索的硬编码最大深度。如果您到达了该深度并且游戏还没有结束,则调用一个评估函数,该函数应在不打更多动作的情况下为当前棋盘提供得分。因此,它应该查看一些简单的模式,例如3排3,并让这些模式有助于最终得分。这是一种启发式方法,意味着(希望)有一个很好的猜测:该值应介于获胜和失败这两个极端之间。

对于第二个动作,您应该限制将要进一步调查的移动次数。候选动作不被访问是与已经玩过的方格相对较远的动作。

此外,您可以创建一个哈希表(在每个真正下棋的棋局之后新建),该表存储您已经评估过的棋盘,因此如果您通过交换一名棋手的棋局到达那里而不再进行该工作。您的搜索树。确保哈希表还捕获了镜像的或翻转的棋盘,这将减少游戏的前几步动作。

还有许多其他技术,例如在搜索过程中跟踪“杀手”的动作。如果在搜索树的一个分支中发现某举动可以带来获胜或避免损失,那么也应在替代分支中首先尝试此举。它可能会导致通过alpha-beta机制进行快速修剪。更笼统地说,以“质量”降序访问您的举动非常重要。当然,在分析动作之前,您不知道动作有多好,但同样,您可以注意到有关动作的一些静态信息。在板的一角移动肯定不如在中心的移动,等等。

某些搜索变体首先进行1深度搜索,然后使用该结果根据该评估结果对移动进行排序。然后进行2深度搜索,并再次根据该(更准确的)结果...等对移动进行排序,直到达到最终深度。这看起来可能需要做很多工作,但是当以最佳顺序订购移动时,alpha-beta修剪将带来最大的好处,这将是整体效率的更主要决定因素。