PAYATHON 2026

JavaScript 井字棋如何比较多个数组索引并正确判断获胜?

数据小陈

读取 gameBoard.board,把八种获胜组合存成索引数组,再检查是否有一组的三个位置都等于当前玩家的 marker。gameBoard[0, 3, 6] 实际上只会读取 gameBoard[6],因此原代码可能在只落一子时就判定获胜。

为什么多个索引的写法会误判?

方括号中的 0, 3, 6 是逗号表达式。JavaScript 会依次求值,表达式的结果是最后一项的值:

gameBoard[0, 3, 6] // 等价于 gameBoard[6]
gameBoard[0, 1, 2] // 等价于 gameBoard[2]
gameBoard[3, 4, 5] // 等价于 gameBoard[5]

这几种写法都只检查一个位置。原代码还执行了 gameBoard[index] = player.marker,因此写入相应属性后,就可能满足某个条件,通过 || 进入获胜分支。

还需要区分对象上的属性和对象中的数组:

gameBoard[6]       // gameBoard 对象上的属性 "6"
gameBoard.board[6] // board 数组中索引为 6 的元素

这两个位置彼此独立。可以把棋盘状态统一保存在 gameBoard.board 中,落子时直接赋值:

gameBoard.board[index] = currentPlayer.marker;

原来的 for 循环也可以删除。当前的 gameBoard 是普通对象,没有 length 属性;循环条件中的 gameBoard.length 为 undefined,循环体不会执行。

如何检查八种获胜组合?

按照原 HTML 中从左到右、从上到下的顺序,棋盘索引为:

0 1 2
3 4 5
6 7 8

将三行、三列和两条对角线列成数组。用 some() 检查是否存在获胜组合,再用 every() 检查组合中的三个位置是否都是同一个玩家的标记:

const winningLines = [
  [0, 1, 2],
  [3, 4, 5],
  [6, 7, 8],
  [0, 3, 6],
  [1, 4, 7],
  [2, 5, 8],
  [0, 4, 8],
  [2, 4, 6]
];

function hasWon(board, marker) {
  return marker !== '' && winningLines.some((line) =>
    line.every((index) => board[index] === marker)
  );
}

例如,检查玩家 Me 是否获胜:

hasWon(gameBoard.board, player.marker);

这里要求玩家使用非空的 marker,以免把尚未落子的空行算作获胜组合。

如何接入现有游戏流程?

下面的代码可以替换原 JavaScript,与已有 HTML 配合使用。它保留了玩家工厂函数;点击回调通过闭包访问当前玩家和游戏结束状态,board 则通过对象解构取得。

const gameBoard = {
  board: Array(9).fill('')
};

function gamePlayers(player, marker) {
  return { player, marker };
}

const player = gamePlayers('Me', 'X');
const playerTwo = gamePlayers('You', 'O');

const winningLines = [
  [0, 1, 2],
  [3, 4, 5],
  [6, 7, 8],
  [0, 3, 6],
  [1, 4, 7],
  [2, 5, 8],
  [0, 4, 8],
  [2, 4, 6]
];

function hasWon(board, marker) {
  return marker !== '' && winningLines.some((line) =>
    line.every((index) => board[index] === marker)
  );
}

function gameController(firstPlayer, secondPlayer) {
  const { board } = gameBoard;
  let currentPlayer = firstPlayer;
  let gameOver = false;

  const squares = document.querySelectorAll('.squares');

  squares.forEach((square) => {
    const index = Number(square.dataset.index);

    square.addEventListener('click', () => {
      if (gameOver || board[index] !== '') {
        return;
      }

      board[index] = currentPlayer.marker;
      square.textContent = currentPlayer.marker;

      if (hasWon(board, currentPlayer.marker)) {
        gameOver = true;
        console.log(`${currentPlayer.player} wins`);
        return;
      }

      if (board.every((cell) => cell !== '')) {
        gameOver = true;
        console.log('Draw');
        return;
      }

      currentPlayer = currentPlayer === firstPlayer
        ? secondPlayer
        : firstPlayer;
    });
  });

  return {
    gameBoard,
    firstPlayer,
    secondPlayer
  };
}

// 在棋盘 HTML 加载完成后执行,且只初始化一次。
const game = gameController(player, playerTwo);

每次有效落子后,代码先检查刚落子的玩家是否获胜,再判断棋盘是否已满,最后切换玩家。已有标记的格子会忽略后续点击,游戏结束后也不能继续落子。

使用时需要注意什么?

  • 两名玩家应使用不同且非空的标记,例如 X 和 O。
  • 示例读取已有的 data-index 属性;九个格子的值应分别为 0 到 8,并且互不重复。
  • 脚本应放在棋盘 HTML 后执行,或通过 defer 等方式确保元素已加载。
  • const { board } = gameBoard 得到的是同一个数组的引用,修改 board[index] 会更新 gameBoard.board。如果之后将 gameBoard.board 替换为新数组,闭包中的 board 仍指向旧数组。
  • 示例按单局游戏设计。重新开始时,需要同时重置数组、格子文本、当前玩家和 gameOver;反复调用 gameController() 会重复绑定监听器。

备注:内容仅供参考。