javaScript 中的 minimax 算法未按预期工作并且 returns 走错了
minimax algorithm in javaScript is not working as expected and returns the wrong move
我正在尝试使用 minimax 算法在 javaScript 中制作 Tic-Tac-Toe,但似乎我做错了什么,minimax 算法没有检测到最佳着法。这是代码:
const board = ["X", null, null, null, null, "X", "X", "O", "O"];
/*
X _ _
_ _ X
X O O
*/
// duplicate passed board and return the new board state
const makeAIMove = (currentBoard, square, player) => {
const nextBoard = [...currentBoard];
nextBoard[square] = player;
return nextBoard;
};
// find empty squares
const emptySquares = (sqBoard) =>
sqBoard
.map((sq, idx) => (sq === null ? idx : null))
.filter((sq) => sq !== null);
// check if no empty squares are available
const isFinished = (sqBoard) => (emptySquares(sqBoard).length ? false : true);
// check winner
const checkWinner = (sqBoard) => {
const winConditions = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (const winCondition of winConditions) {
[a, b, c] = winCondition;
if (sqBoard[a] && sqBoard[a] === sqBoard[b] && sqBoard[a] === sqBoard[c])
return sqBoard[a];
}
return false;
};
// minimax algorithm
const minimax = (sqBoard, depth, isMaximizer) => {
// terminal checker
const theWinner = checkWinner(sqBoard);
// we have a winner
if (theWinner) {
return theWinner === "X" ? -10 : 10;
}
// it's a tie
if (isFinished(sqBoard)) {
return 0;
}
let bestScore;
if (isMaximizer) {
bestScore = -1000;
emptySquares(sqBoard).forEach((square) => {
// make a sample move
let nextBoard = makeAIMove(sqBoard, square, "O");
// recursion
let score = minimax(nextBoard, depth + 1, false);
bestScore = Math.max(bestScore, score);
});
} else {
bestScore = 1000;
emptySquares(sqBoard).forEach((square) => {
let nextBoard = makeAIMove(sqBoard, square, "X");
let score = minimax(nextBoard, depth + 1, true);
bestScore = Math.min(bestScore, score);
});
}
return bestScore;
};
// find the best move
const nextBestMove = (sqBoard) => {
let nextMoveArray = [];
let remainedSquares = emptySquares(sqBoard);
remainedSquares.forEach((square) => {
let nextBoard = makeAIMove(sqBoard, square, "O");
let theScore = minimax(nextBoard, 0, true);
nextMoveArray.push({
sq: square,
sc: theScore,
});
});
nextMoveSorted = nextMoveArray.sort((a, b) => (a.sc < b.sc ? 1 : -1));
return nextMoveSorted[0].sq;
};
console.log(nextBestMove(board));
在上述情况下,最好的着法是通过在棋盘[3]中填满“O”来阻止 X 取胜,但它总是会检测到另一个得分更高的着法。
任何人都可以帮助我了解我的代码出了什么问题吗?
谢谢。
从你的代码中我了解到 X 是最小化玩家,O 是最大化玩家。但后来我看到这段代码:
let nextBoard = makeAIMove(sqBoard, square, "O");
let theScore = minimax(nextBoard, 0, true);
因此在 O 移动后,您调用 minimax
并将 isMaximizer
设置为 true。但这将使 minimax
下另一个 O 步,而 O 已经下了。你想得到 X 的最佳回复移动,所以你应该在这里传递 false
:
let theScore = minimax(nextBoard, 0, false);
现在,对于每个这样的调用(因此对于 O 的每一步),这将 return -10,因为游戏已经处于 O 的失败状态,无论它做什么,X 都会赢。如果O走3,那么X就打2双攻。
如果你想区分快赢和慢赢,那么你应该在每次回溯时调整分数。
例如,您可以将 return bestScore
语句替换为 return 一个更接近零的单位的值。因此,例如 -10 变成 -9,5 变成 4,0 仍然是 0:
return bestScore - Math.sign(bestScore);
有了这个变化,O 将在 3 下棋,因为它的分数是 -7(仍然输),而其他棋子的分数都是 -9(从 X 走一步就立即输)。
我正在尝试使用 minimax 算法在 javaScript 中制作 Tic-Tac-Toe,但似乎我做错了什么,minimax 算法没有检测到最佳着法。这是代码:
const board = ["X", null, null, null, null, "X", "X", "O", "O"];
/*
X _ _
_ _ X
X O O
*/
// duplicate passed board and return the new board state
const makeAIMove = (currentBoard, square, player) => {
const nextBoard = [...currentBoard];
nextBoard[square] = player;
return nextBoard;
};
// find empty squares
const emptySquares = (sqBoard) =>
sqBoard
.map((sq, idx) => (sq === null ? idx : null))
.filter((sq) => sq !== null);
// check if no empty squares are available
const isFinished = (sqBoard) => (emptySquares(sqBoard).length ? false : true);
// check winner
const checkWinner = (sqBoard) => {
const winConditions = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (const winCondition of winConditions) {
[a, b, c] = winCondition;
if (sqBoard[a] && sqBoard[a] === sqBoard[b] && sqBoard[a] === sqBoard[c])
return sqBoard[a];
}
return false;
};
// minimax algorithm
const minimax = (sqBoard, depth, isMaximizer) => {
// terminal checker
const theWinner = checkWinner(sqBoard);
// we have a winner
if (theWinner) {
return theWinner === "X" ? -10 : 10;
}
// it's a tie
if (isFinished(sqBoard)) {
return 0;
}
let bestScore;
if (isMaximizer) {
bestScore = -1000;
emptySquares(sqBoard).forEach((square) => {
// make a sample move
let nextBoard = makeAIMove(sqBoard, square, "O");
// recursion
let score = minimax(nextBoard, depth + 1, false);
bestScore = Math.max(bestScore, score);
});
} else {
bestScore = 1000;
emptySquares(sqBoard).forEach((square) => {
let nextBoard = makeAIMove(sqBoard, square, "X");
let score = minimax(nextBoard, depth + 1, true);
bestScore = Math.min(bestScore, score);
});
}
return bestScore;
};
// find the best move
const nextBestMove = (sqBoard) => {
let nextMoveArray = [];
let remainedSquares = emptySquares(sqBoard);
remainedSquares.forEach((square) => {
let nextBoard = makeAIMove(sqBoard, square, "O");
let theScore = minimax(nextBoard, 0, true);
nextMoveArray.push({
sq: square,
sc: theScore,
});
});
nextMoveSorted = nextMoveArray.sort((a, b) => (a.sc < b.sc ? 1 : -1));
return nextMoveSorted[0].sq;
};
console.log(nextBestMove(board));
在上述情况下,最好的着法是通过在棋盘[3]中填满“O”来阻止 X 取胜,但它总是会检测到另一个得分更高的着法。
任何人都可以帮助我了解我的代码出了什么问题吗?
谢谢。
从你的代码中我了解到 X 是最小化玩家,O 是最大化玩家。但后来我看到这段代码:
let nextBoard = makeAIMove(sqBoard, square, "O");
let theScore = minimax(nextBoard, 0, true);
因此在 O 移动后,您调用 minimax
并将 isMaximizer
设置为 true。但这将使 minimax
下另一个 O 步,而 O 已经下了。你想得到 X 的最佳回复移动,所以你应该在这里传递 false
:
let theScore = minimax(nextBoard, 0, false);
现在,对于每个这样的调用(因此对于 O 的每一步),这将 return -10,因为游戏已经处于 O 的失败状态,无论它做什么,X 都会赢。如果O走3,那么X就打2双攻。
如果你想区分快赢和慢赢,那么你应该在每次回溯时调整分数。
例如,您可以将 return bestScore
语句替换为 return 一个更接近零的单位的值。因此,例如 -10 变成 -9,5 变成 4,0 仍然是 0:
return bestScore - Math.sign(bestScore);
有了这个变化,O 将在 3 下棋,因为它的分数是 -7(仍然输),而其他棋子的分数都是 -9(从 X 走一步就立即输)。