Javascript 中的 Minimax 无法正常工作

Minimax in Javascript not working properly

作为练习项目,我在 JSFiddle 上制作了一个井字棋游戏(因为还不够,对吧?),然后我逐渐添加了无与伦比的 AI。在大多数情况下它是有效的,但有一些组合(例如将 X 设置到字段 5、9、3 或设置到字段 3、7、9)会导致计算机无法正确计算最佳移动。

JSFiddle 上的项目:https://jsfiddle.net/jd8x0vjz/

以及从第 63 行开始的相关函数:

function evaluateMove(move, player, depth) {
var gameStatus = evaluateGameStatus(move); //get status of current board
if (gameStatus < 2 && player)
    return -1; //if human won, return -1
if (gameStatus < 2 && !player)
    return 1; //if human lost, return 1

var returnValue = 0 //value to be returned later

for (var z = 0; z < 3; z++) { //loop for row
    for (var s = 0; s < 3; s++) { //loop for column
        if (move[z][s]) //if current slot has an x or o,
            continue; //skip it     
        var nextMove = cloneGameStatus(move); //create temporary array with base of current grid
        nextMove[z][s] = !player ? "x" : "o"; //assign first free field  the appropriate symbol
        var value = evaluateMove(nextMove, !player, depth+1);   //recursion but with switched player, to add the correct icon afterwards
        if ((value > returnValue) && player)
            returnValue = value;            
        if ((value < returnValue) && !player)
            returnValue = value;                
    }
}
return returnValue; //return value of current simulation
}

我认为最后两个 if 子句导致了这些问题,因为计算机确实计算了正确的值(在调试器中可以观察到),但它们有时会被覆盖,但我不确定这是否真的是问题的根源。如有任何帮助或提示,我们将不胜感激!

编辑:问题已解决!在下面寻找我的答案,以防它不是第一个。

我不能肯定这就是问题的根源,但您的代码中肯定存在会产生奇怪结果的错误。该行:

var returnValue = 0 //value to be returned later

不正确。除了缺少分号之外,正确的代码应该是:

var returnValue = -1;
if(!player){
  returnValue = 1;
}

您希望最大玩家的默认值为负,以便他采取最佳着法,而对于最小化玩家,默认值为正值,以便他采取最差的着法。您这样做的方式是,如果最大化玩家只面临值为 -1 的选项,因为 -1 小于 0,并且 returnValue 被初始化为 0,尽管要返回的正确值是 -1,但将返回 0。

returnValue 的默认值错误的想法确实让我走上了正确的道路;它并没有让所有事情都神奇地起作用(如果它起作用的话就太好了),但它确实给了我正确的推动。由于我们不想 return 任何值,如果没有计算,我调整了 evaluateMove 函数如下:

function evaluateMove(move, player, depth) {
var gameStatus = evaluateGameStatus(move); //get status of current board
if (gameStatus != 2)
    return gameStatus; //if the game is not running anymore, return result

var returnValue; //value to be returned later

for (var z = 0; z < 3; z++) { //loop for row
    for (var s = 0; s < 3; s++) { //loop for column
        if (move[z][s]) //if current slot has an x or o,
            continue; //skip it     
        var nextMove = cloneGameStatus(move); //create temporary array with base of current grid
        nextMove[z][s] = !player ? "x" : "o"; //assign first free field  the appropriate symbol
        var value = evaluateMove(nextMove, !player, depth+1);   //recursion but with switched player, to add the correct icon afterwards
        if ((value > returnValue || returnValue == null) && player)
            returnValue = value;            
        if ((value < returnValue || returnValue == null) && !player)
            returnValue = value;                
    }
}
return returnValue; //return value of current simulation
}

现在默认值为空,因此不应中断计算。然而,它确实抛出了第一块检查,所以我将它调整为简单的 return 游戏结束时的当前状态,而不是进行任何复杂的检查。然而,that 抛出了结果,因为我在这两种方法中使用了相反的默认值,所以我也不得不调整 evaluateGameStatus。现在如果人类赢了它 returns -1 而不是 1,如果计算机赢了它 returns 1 而不是 -1:

function evaluateGameStatus(gameStatus) { //a clusterfuck of winning combinations
if(
X Checks
)
return -1; //there's a successful combination of x's

else if(
O Checks
)
return 1; //there's a successful combination of o's

else {
for (var z = 0; z < 3; z++) {
    for (var s = 0; s < 3; s++) {
        if (!gameStatus[z][s])
            return 2; //if there is an empty field neither has won, continue playing
        }
    }

return 0; //there's no successful combination and max moves have been reached. it's a draw
}
}

显然,我必须对 checkGameEnd 函数进行相同的调整。
您会注意到我也更改了平局支票。那是因为,出于某种原因,旧的 count == maxMoves 检查不再起作用,所以我改为一个循环,它只检查是否有任何空字段,如果有则 return 2 , 如果没有则为 0(这里 returns 0 因为此时它通过所有检查 运行 :X 没有赢,O 没有赢,并且没有打开剩余名额,所以比赛必须是平局)。

现在可以在这里找到工作项目:
https://jsfiddle.net/h5zwzkm7/