作为一个练习项目,我在JSFiddle上制作了一个Tic-Tac-Toe游戏(因为它已经不够了,对吗?)并且我逐渐增加了一个无与伦比的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子句导致了这些问题,因为计算机确实计算了正确的值(在调试器中可观察到),但它们有时会被覆盖,但我不确定这是否真的是问题的根源。任何帮助或提示将不胜感激!
编辑:问题解决了!如果不是第一个,请在下面查找我的答案。答案 0 :(得分:1)
我不能肯定这是问题的根源,但是你的代码中肯定存在一个会产生奇怪结果的错误。这一行:
var returnValue = 0 //value to be returned later
不正确。除了缺少分号外,正确的代码应该是:
var returnValue = -1;
if(!player){
returnValue = 1;
}
你希望最大玩家的默认值为负值,这样他才能获得最佳移动,并且最大限度地减少玩家积极因素,这样他就会采取最差的行动。你这样做的方式,如果最大化玩家只面对值为-1的选项,因为-1小于0,并且returnValue初始化为0,虽然要返回的正确值为-1,但仍会返回0。
答案 1 :(得分:1)
returnValue默认值错误的想法肯定让我走上了正确的道路;它并没有让所有东西神奇地起作用(如果它真的那么太好了),但它确实给了我正确的推动力。由于我们不想在没有计算任何值的情况下返回任何值,因此我将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
}
现在默认值为null,因此不应该关闭计算。然而它甩掉的是第一块支票,所以我调整它只是为了在游戏结束时返回当前状态,而不是进行任何精心检查。但是 放弃了结果,因为我在两种方法中使用了反转的默认值,所以我也必须调整evaluateGameStatus。现在,如果人类获胜,则返回-1而不是1,如果计算机获胜,则返回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不再起作用,所以我改为循环,它只检查是否有空字段,如果有则返回2,如果有,则返回0没有(它在这里返回0,因为此时它已经完成了所有的检查:X没有赢,O没赢,而且没有剩下的开放位置,所以游戏必须是平局)。 / p>
现在可以在这里找到工作项目:
https://jsfiddle.net/h5zwzkm7/