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()会重复绑定监听器。
备注:内容仅供参考。
