Ошибка в алгоритме Negamax, связанная с оценочной функцией? Работает несколько раз, а не другие
Я пытаюсь разработать игру Tic-Tac-Toe с непревзойденным ИИ, и я нахожусь в точке, где моя функция negamax большую часть времени возвращает правильный результат. Однако иногда при определенных предсказуемых условиях компьютер выбирает движение доски, которое не имеет смысла и не может блокировать победу пользователя.
Основываясь на моих исследованиях, алгоритм negamax выглядит хорошо. Меня больше беспокоит, что в моей функции оценки может быть что-то не так; другими словами, в способе вычисления эвристического значения. Большая часть моей энергии была направлена на борьбу с Негамаксом, поэтому другие функции были в основном запоздалыми.
Вот только один пример того, что я описываю:
// user: X, computer: O, turn: computer
// this scenario should return 1 to block user from [0, 1, 2]
// returns 5 when negamax called with compNum
// output is correct when negamax called with userNum (simulating user's turn)
var scenario01 = [
userNum, 0, userNum,
userNum, compNum, 0,
compNum, 0, 0
];
Столкнувшись с этим сценарием, вместо выбора квадрата 1 (верхняя середина: массив с нулевым индексом), чтобы заблокировать пользователя от достижения выигрыша, функция negamax выдает 5, квадрат, который не имеет непосредственного использования ни одному из игроков. Тем не менее, при тестировании, если я вызываю negamax по тому же сценарию с userNum в качестве аргумента, negamax выполняет свою работу и находит выигрышный квадрат 1 для пользователя.
Я знаю, что упускаю что-то обманчиво простое, и у меня есть ощущение, что это связано с тем, как я настроил userNum и compNum, и с тем, как они жестко запрограммированы. Возможно, я взял свои идеи Minimax и безуспешно перевел их на negamax, а это значит, что я не могу максимизировать ценность как для пользователя, так и для компьютера. Или я могу максимизировать с неправильной точки зрения игрока. Если бы кто-то любезно предоставил толчок в правильном направлении, я был бы благодарен.
Полная рабочая версия вместе с тестами доступна по адресу: https://repl.it/CnGD/4
Я не хотел размещать здесь огромную сумму, но я могу создать фрагмент на этом сайте, если потребуется.
Функция Негамакс
var negamax = (board, player, depth, lvl, α, β) => {
if ((board.getWinner(userNum, compNum) !== null) || depth >= lvl) {
return board.getWinner(userNum, compNum);
// also tried: return board.score(depth) * player;
}
var highScore = -Infinity;
board.available().forEach(function (move) {
board.mark(move, player);
var score = -negamax(board, -player, depth + 1, lvl, -β, -α);
board.undo(move);
if (score > highScore) { // if better cell is found
highScore = score; // note best score so far
bestMove = move; // note best move so far
α = Math.max(α, score);
if (α >= β) { // alpha-beta pruning
return bestMove;
}
}
});
return bestMove; };
Функция оценки
// these functions are part of a Board prototype
// I haven't included it all here, but will provide a link
getWinner: function (userNum, compNum) {
return (
this.validateWin(this.occupied(userNum)) ? userNum :
this.validateWin(this.occupied(compNum)) ? compNum :
this.matchAll(true) ? 0 :
null
);
},
score: function (depth) {
if (board.getWinner(userNum, compNum) === userNum) {
return depth - 10;
} else if (board.getWinner(userNum, compNum) === compNum) {
return 10 - depth;
} else {
return 0;
}
}