基于Minimax算法的JavaScript井字棋功能故障排查
井字棋Minimax算法故障修复
问题描述
现有一个基于Minimax算法的JavaScript井字棋项目,核心功能包括:
generateTable函数生成可点击棋盘,落子信息存入board变量- 初始化
board后,生成索引矩阵a(值为[[0,1,2],[3,4,5],[6,7,8]]) getlines函数枚举所有行、列、对角线的获胜组合
当前存在两个核心故障:
- Minimax算法计算的每次移动得分均为
-Infinity - AI首次落子的'O'可正常显示,后续AI落子无法在棋盘上渲染
故障原因分析及修复方案
1. AI后续落子无法显示
computermovedifficult函数中,设置AI落子到board后,未调用printBoard()函数更新DOM,导致棋盘界面没有同步更新。修复时添加printBoard()调用即可。
2. Minimax得分异常(始终为-Infinity)
- 全局变量污染:
result、lines变量未声明,成为全局变量,导致递归逻辑混乱;需在函数内部声明为局部变量 - 获胜组合数组累积:
getlines函数使用全局lines1变量,每次调用会保留之前的结果,导致获胜组合重复;需在函数内部初始化lines1 - 空位判断逻辑错误:Minimax中判断空位用
board[i] === "",但board初始值为null,应改为board[i] === null;同时平局判断逻辑错误,需先检查是否有获胜者,再判断是否平局 - 变量命名冲突:
checkWinner3和minimax中解构获胜组合时使用const [a, b, c] = lines[i],覆盖了全局的矩阵a,导致后续逻辑错误,需重命名变量
修复后完整代码
HTML部分
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>井字棋</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.4.2/d3.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> </head> <body> <h1>井字棋</h1> <br> <table id="board"></table> <div id="result" class="result"></div> </body>
CSS部分
body, html { height: 100%; margin: 0; display: flex; justify-content: center; align-items: center; flex-direction: column; } table { border-collapse: collapse; } td { border: 1px solid black; width: 50px; height: 50px; text-align: center; font-size: 20px; } .result { margin-top: 50px; font-size: 1.5em; }
JavaScript部分
let winner = null; let wins = 0; let turn = "X"; let board = initializeBoard(3); const winMatrix = generateMatrixWin(3); function generateTable() { const table = document.getElementById("board"); table.innerHTML = ""; for (let i = 0; i < 3; i++) { const row = document.createElement("tr"); for (let j = 0; j < 3; j++) { const cell = document.createElement("td"); cell.setAttribute("onclick", `cellClick(${i * 3 + j})`); row.appendChild(cell); } table.appendChild(row); } } generateTable(); function initializeBoard() { let board = []; for (let i = 0; i < 9; i++) { board.push(null); } return board; } function getlines(matrix) { let lines = []; const size = matrix.length; // 行 for (let i = 0; i < size; i++) { lines.push([matrix[i][0], matrix[i][1], matrix[i][2]]); } // 列 for (let j = 0; j < size; j++) { lines.push([matrix[0][j], matrix[1][j], matrix[2][j]]); } // 对角线 lines.push([matrix[0][0], matrix[1][1], matrix[2][2]]); lines.push([matrix[0][2], matrix[1][1], matrix[2][0]]); return lines; } function isValidIndex(array, row, col) { return row >= 0 && row < array.length && col >= 0 && col < array[row].length; } function generateMatrixWin(matrixSize) { let matrix = []; let count = 0; for (let i = 0; i < matrixSize; i++) { let row = []; for (let j = 0; j < matrixSize; j++) { row.push(count); count++; } matrix.push(row); } return matrix; } function checkWinner3() { const lines = getlines(winMatrix); // 检查获胜组合 for (let i = 0; i < lines.length; i++) { const [x, y, z] = lines[i]; if (board[x] && board[x] === board[y] && board[x] === board[z]) { return board[x]; } } // 检查是否平局 for (let i = 0; i < 9; i++) { if (board[i] === null) { return null; } } return "draw"; } function printBoard() { const table = document.getElementById("board"); for (let i = 0; i < 9; i++) { const row = Math.floor(i / 3); const col = i % 3; table.rows[row].cells[col].textContent = board[i] || ""; } } function cellClick(index) { if (!winner && board[index] === null) { board[index] = turn; printBoard(); winner = checkWinner3(); if (winner) { const resultEl = document.getElementById("result"); if (winner === "draw") { resultEl.textContent = "平局!感谢游玩。"; } else { resultEl.textContent = `恭喜!${winner}获胜!感谢游玩。`; } } else { turn = turn === "X" ? computermovedifficult() : "X"; } } } function computermoveeasy() { const table = document.getElementById("board"); let index; do { index = Math.floor(Math.random() * board.length); } while (board[index] !== null); board[index] = "O"; printBoard(); turn = "X"; const winner = checkWinner3(); if (winner) { const resultEl = document.getElementById("result"); if (winner === "draw") { resultEl.textContent = "平局!感谢游玩。"; } else { resultEl.textContent = `恭喜!${winner}获胜!感谢游玩。`; } } return turn; } function computermovedifficult() { let bestScore = -Infinity; let bestMove = null; for (let index = 0; index < 9; index++) { if (board[index] === null) { board[index] = "O"; const score = minimax(board, 0, false); board[index] = null; // 撤销移动 if (score > bestScore) { bestScore = score; bestMove = index; } } } board[bestMove] = "O"; printBoard(); // 更新棋盘显示 winner = checkWinner3(); if (winner) { const resultEl = document.getElementById("result"); if (winner === "draw") { resultEl.textContent = "平局!感谢游玩。"; } else { resultEl.textContent = `恭喜!${winner}获胜!感谢游玩。`; } } turn = "X"; return turn; } function minimax(board, depth, isMaximizing) { let result = null; const lines = getlines(winMatrix); // 检查是否有获胜者 for (let i = 0; i < lines.length; i++) { const [x, y, z] = lines[i]; if (board[x] && board[x] === board[y] && board[x] === board[z]) { result = board[x]; break; } } // 检查是否平局 if (result === null) { let isDraw = true; for (let i = 0; i < 9; i++) { if (board[i] === null) { isDraw = false; break; } } if (isDraw) { result = "draw"; } } // 终止条件 if (result !== null) { if (result === "draw") { return 0; } else if (result === "O") { return 10 - depth; } else if (result === "X") { return depth - 10; } } if (depth >= 9) { // 最大深度为9(棋盘填满) return 0; } if (isMaximizing) { let bestScore = -Infinity; for (let i = 0; i < 9; i++) { if (board[i] === null) { board[i] = "O"; const score = minimax(board, depth + 1, false); board[i] = null; bestScore = Math.max(score, bestScore); } } return bestScore; } else { let bestScore = Infinity; for (let i = 0; i < 9; i++) { if (board[i] === null) { board[i] = "X"; const score = minimax(board, depth + 1, true); board[i] = null; bestScore = Math.min(score, bestScore); } } return bestScore; } }
修复说明
- 解决AI落子显示问题:在
computermovedifficult函数中添加printBoard()调用,同步DOM与board数据 - 修复Minimax得分异常:
- 将
lines1改为getlines内部的局部变量,避免获胜组合累积 - 修复
minimax中的result变量为局部变量,避免全局污染 - 修正空位判断条件为
board[i] === null,与初始化逻辑一致 - 重命名解构变量避免覆盖全局矩阵
a - 优化平局判断逻辑,先检查获胜者再判断是否无空位
- 调整Minimax最大深度为9,覆盖所有可能的落子情况
- 将
内容的提问来源于stack exchange,提问作者Tayba
相关产品推荐
相关产品推荐

