五子棋Minimax算法AI未拦截玩家必胜招问题排查
五子棋Minimax算法防御失效问题
目前AI已能主动寻找最优棋步、争取胜利,但仍存在核心问题:不会防御玩家即将获胜的局面。AI会主动走成叉形或四子连线争胜,但当HUMAN玩家形成3(或4)子连线时,AI不会落子阻止其获胜。更反常的是,设置较低搜索深度时AI能正常防御,但提高深度后反而失效。
以下是当前代码及测试棋盘示例:此时AI应落子64以阻止玩家下一回合获胜,但实际输出为20。
const ROWS = 9; const COLS = 9; const LEN = 5; const EMPTY = 0; const HUMAN = 1; const COMP = 2; const WINNING_MOVE = 100000; function checkDirection(grid, who, currChain, sRow, sCol, incRow, incCol) { let newChain = 0; while (currChain + newChain < LEN) { const row = sRow + (incRow * (newChain + 1)); const col = sCol + (incCol * (newChain + 1)); if (grid[row * COLS + col] !== who) { break; } newChain++; } return newChain; } function lineCheck(grid, who, sRow, sCol, mRow, mCol) { let chain = 1; chain += checkDirection(grid, who, 0, sRow, sCol, mRow, mCol); chain += checkDirection(grid, who, chain, sRow, sCol, -mRow, -mCol); return chain >= LEN; } function isWinningMove(grid, who, row, col) { return lineCheck(grid, who, row, col, 1, 0) || lineCheck(grid, who, row, col, 0, 1) || lineCheck(grid, who, row, col, 1, 1) || lineCheck(grid, who, row, col, -1, 1); } function getTile(grid, row, col) { if (row < 0 || col < 0 || row >= ROWS || col >= COLS) { return -1; } return grid[row * COLS + col]; } function hasNeighbor(board, row, col) { if (getTile(board, row - 1, col - 1) > 0) { return true; } if (getTile(board, row - 1, col + 1) > 0) { return true; } if (getTile(board, row + 1, col - 1) > 0) { return true; } if (getTile(board, row + 1, col + 1) > 0) { return true; } if (getTile(board, row - 1, col) > 0) { return true; } if (getTile(board, row + 1, col) > 0) { return true; } if (getTile(board, row, col - 1) > 0) { return true; } if (getTile(board, row, col + 1) > 0) { return true; } return false; } function minimax(board, depth, alpha, beta, player, latestRow, latestCol) { if (depth === 0) { const val = evaluateBoard(board, latestRow, latestCol); return [ val, latestRow * COLS + latestCol ]; // returns a pair (value, move) } const opponent = player === COMP ? HUMAN : COMP; // player argument should be opponent, and return statement should be different per player if (isWinningMove(board, opponent, latestRow, latestCol)) { const multiplier = player === COMP ? 1 : -1; return [ WINNING_MOVE * multiplier, latestRow * COLS + latestCol ]; } let bestMove = -1; if (player === COMP) { let maxEval = Number.MIN_SAFE_INTEGER; for (let row = 0; row < ROWS; row++) { for (let col = 0; col < COLS; col++) { const idx = row * COLS + col; const tileValue = board[idx]; if (tileValue > 0 || !hasNeighbor(board, row, col)) { continue; } board[idx] = player; const evaluation = minimax(board, depth - 1, alpha, beta, HUMAN, row, col)[0]; board[idx] = tileValue; if (evaluation > maxEval) { maxEval = evaluation; bestMove = idx; } alpha = Math.max(alpha, evaluation); if (beta <= alpha) { return [ maxEval, bestMove ]; } } } return [ maxEval, bestMove ]; } else { let minEval = Number.MAX_SAFE_INTEGER; for (let row = 0; row < ROWS; row++) { for (let col = 0; col < COLS; col++) { const idx = row * COLS + col; const tileValue = board[idx]; if (tileValue > 0 || !hasNeighbor(board, row, col)) { continue; } board[idx] = player; const evaluation = minimax(board, depth - 1, alpha, beta, COMP, row, col)[0]; board[idx] = tileValue; if (evaluation < minEval) { minEval = evaluation; bestMove = idx; // Also track best move for HUMAN. } beta = Math.min(beta, evaluation); if (beta <= alpha) { return [ minEval, bestMove ]; } } } return [ minEval, bestMove ]; } } function evaluatePlayerBoard(grid, who, latestRow, latestCol) { let idx = 0; let score = 0; if (isWinningMove(grid, who, latestRow, latestCol)) { return WINNING_MOVE; } for (let row = 0; row < ROWS; row++) { for (let col = 0; col < COLS; col++) { if (grid[idx] !== who) { idx++; continue; } if (getTile(grid, row - 1, col - 1) === who) { score++; } if (getTile(grid, row - 1, col + 1) === who) { score++; } if (getTile(grid, row + 1, col - 1) === who) { score++; } if (getTile(grid, row + 1, col + 1) === who) { score++; } if (getTile(grid, row - 1, col) === who) { score++; } if (getTile(grid, row + 1, col) === who) { score++; } if (getTile(grid, row, col - 1) === who) { score++; } if (getTile(grid, row, col + 1) === who) { score++; } // if (getTile(grid, row, col) === who) { score++; } idx++; } } return score; } function evaluateBoard(grid, latestRow, latestCol) { return evaluatePlayerBoard(grid, COMP, latestRow, latestCol) // COMP is maximizing - evaluatePlayerBoard(grid, HUMAN, latestRow, latestCol); // HUMAN is minimizing } function getBestMove(board, maxDepth) { for (let depth = 1; depth <= maxDepth; depth++) { const [ evaluation, move ] = minimax(board, depth, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, COMP, -1, -1); // if we found a winning move already, return early // otherwise, keep iterating until we reach max depth if (evaluation > 10000 || depth === maxDepth) { return move; } } return 0; // should never run } const exampleBoard = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-8 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9-17 0, 0, 0, 0, 0, 0, 2, 0, 0, // 18-26 0, 0, 2, 2, 0, 1, 0, 0, 0, // 27-35 0, 0, 0, 2, 1, 0, 0, 0, 0, // 36-44 0, 0, 0, 1, 0, 0, 0, 0, 0, // 45-53 0, 0, 1, 0, 0, 0, 0, 0, 0, // 54-62 0, 0, 0, 0, 0, 0, 0, 0, 0, // 63-71 0, 0, 0, 0, 0, 0, 0, 0, 0, // 72-80 ]; console.log(getBestMove(exampleBoard, 3));
内容的提问来源于stack exchange,提问作者John Smith
相关产品推荐
相关产品推荐

