井字棋Bitboard改造中二进制转换出现异常位问题求助
井字棋Bitboard改造异常问题
我正在将井字棋代码改造为使用Bitboard(位棋盘)实现AI对手。测试阶段计划通过按位与运算验证落子有效性,后续还将用于查看可用空位,但日志输出显示每局游戏的第三步落子转换存在异常,导致按位与运算结果出错。
HTML中cellName对应的id标签为0-8(注:井字棋共9个格子,推测原描述笔误,应为0-8),异常表现为第三步落子后位运算结果不符合预期。
原代码
window.onload = init(); var playerNought = true; // 胜利条件的位棋盘十进制表示 const wins = [7, 56, 448, 73, 146, 292, 273, 84]; var boardState = 0; const noughtMoves = []; const crossMoves = []; function init(){ playMove(); var newGame = document.getElementById("newGame"); newGame.onclick = function(){location.reload();}; } function playMove(){ var cells = document.getElementsByTagName("td"); for (var i = 0; i < cells.length; i++) { cells[i].onclick = function(eventObj){ var cellName = eventObj.target.id; var cell = document.getElementById(cellName); // 检查当前格子未被占用 if(!cell.getAttribute("class")){ if(playerNought){ cell.setAttribute("class", "nought"); noughtMoves.push(Number(cellName)); checkMove(noughtMoves, "Nought"); } else { cell.setAttribute("class", "cross"); crossMoves.push(Number(cellName)); checkMove(crossMoves, "Crosses"); } } playerNought = !playerNought; return; } } } function checkMove(moves, player) { // 将玩家所有落子转换为位棋盘 const pattern = moves.reduce((acc, i) => acc | (1 << i), 0); // 验证落子有效性:按位与非零表示位置已被占用 var validMove = boardState & pattern; console.log("旧棋盘状态(二进制): " + boardState.toString(2)); console.log("当前玩家落子位模式(二进制): " + pattern.toString(2)); console.log("按位与结果(二进制): " + validMove.toString(2)); // 更新棋盘状态 boardState = boardState | pattern; console.log("新棋盘状态(二进制): " + boardState.toString(2)); // 检查平局(所有9位都被占用) if(boardState == 511){ setTimeout(() => { if(confirm("平局!")){location.reload();} },500); } // 检查是否获胜:任意胜利模式与当前玩家位模式完全匹配 const winLine = wins.some(win => (win & pattern) === win); if(winLine){ var table = document.getElementById("table"); const redLines = ["hLineTop", "hLineMid", "hLineBottom", "vLineLeft", "vLineMid", "vLineRight", "dLineLtoR", "dLineRtoL"]; table.setAttribute("class", redLines[wins.findIndex((win) => win == pattern)]); setTimeout(() => { if(confirm("恭喜 " + player + " 获胜!")){location.reload();} },500); } }
问题根源
核心错误在于每次调用checkMove时,传入的是玩家的所有历史落子数组,而非当前仅这一步的落子:
- 前两步落子后,
boardState已经记录了这两个位置的位信息 - 第三步落子时,
pattern会计算该玩家所有已落子的位(包括前一步),此时boardState & pattern会得到该玩家前一步的位,结果非零,被误判为无效落子 - 同时,
boardState = boardState | pattern会重复叠加该玩家的历史位,导致棋盘状态异常
修复方案
修改逻辑,仅传递当前落子的位置给checkMove,而非整个历史数组:
修复后的代码
window.onload = init(); var playerNought = true; // 胜利条件的位棋盘十进制表示 const wins = [7, 56, 448, 73, 146, 292, 273, 84]; var boardState = 0; const noughtMoves = []; const crossMoves = []; function init(){ playMove(); var newGame = document.getElementById("newGame"); newGame.onclick = function(){location.reload();}; } function playMove(){ var cells = document.getElementsByTagName("td"); for (var i = 0; i < cells.length; i++) { cells[i].onclick = function(eventObj){ var cellName = eventObj.target.id; var cellId = Number(cellName); var cell = document.getElementById(cellName); // 先通过位运算验证落子有效性,避免DOM属性判断的冗余 const currentMoveBit = 1 << cellId; if((boardState & currentMoveBit) === 0){ if(playerNought){ cell.setAttribute("class", "nought"); noughtMoves.push(cellId); checkMove(currentMoveBit, noughtMoves, "Nought"); } else { cell.setAttribute("class", "cross"); crossMoves.push(cellId); checkMove(currentMoveBit, crossMoves, "Crosses"); } playerNought = !playerNought; } return; } } } function checkMove(currentMoveBit, moves, player) { // 更新棋盘状态 boardState |= currentMoveBit; console.log("旧棋盘状态(二进制): " + (boardState ^ currentMoveBit).toString(2)); console.log("当前落子位(二进制): " + currentMoveBit.toString(2)); console.log("新棋盘状态(二进制): " + boardState.toString(2)); // 检查平局(所有9位都被占用) if(boardState == 511){ setTimeout(() => { if(confirm("平局!")){location.reload();} },500); } // 计算玩家当前所有落子的位模式,用于判断胜利 const playerPattern = moves.reduce((acc, i) => acc | (1 << i), 0); const winLine = wins.some(win => (win & playerPattern) === win); if(winLine){ var table = document.getElementById("table"); const redLines = ["hLineTop", "hLineMid", "hLineBottom", "vLineLeft", "vLineMid", "vLineRight", "dLineLtoR", "dLineRtoL"]; table.setAttribute("class", redLines[wins.findIndex((win) => (win & playerPattern) === win)]); setTimeout(() => { if(confirm("恭喜 " + player + " 获胜!")){location.reload();} },500); } }
关键修改点
- 在
playMove中直接用位运算验证落子有效性,比DOM属性判断更高效且准确 checkMove接收当前落子的位currentMoveBit,而非历史数组,避免重复计算- 仅在判断胜利时,才计算玩家所有落子的位模式
playerPattern - 修正胜利判断时的索引查找逻辑,避免原代码中
win == pattern的错误匹配
内容的提问来源于stack exchange,提问作者Dave022
相关产品推荐
相关产品推荐

