You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

井字棋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时,传入的是玩家的所有历史落子数组,而非当前仅这一步的落子:

  1. 前两步落子后,boardState已经记录了这两个位置的位信息
  2. 第三步落子时,pattern会计算该玩家所有已落子的位(包括前一步),此时boardState & pattern会得到该玩家前一步的位,结果非零,被误判为无效落子
  3. 同时,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);
  }
}

关键修改点

  1. 在playMove中直接用位运算验证落子有效性,比DOM属性判断更高效且准确
  2. checkMove接收当前落子的位currentMoveBit,而非历史数组,避免重复计算
  3. 仅在判断胜利时,才计算玩家所有落子的位模式playerPattern
  4. 修正胜利判断时的索引查找逻辑,避免原代码中win == pattern的错误匹配

内容的提问来源于stack exchange,提问作者Dave022

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 10:35:37