JavaScript井字棋:胜负判定函数调用位置异常问题求助
井字棋游戏获胜检测函数优化问题
我开发的JavaScript井字棋游戏,大部分功能正常,但获胜组合检测及胜负/平局显示的函数存在问题:
- 把
determineWinner()放在方块点击事件的addEventListener内部时能正常运行,但放在forEach循环内、监听器外部时完全失效。 - 需要将该函数移到监听器外部,确保每次落子后立即检测并更新页面状态,不需要再次点击才能显示胜负或平局。我试过在多个位置调用该函数,要么无法运行,要么达不到需求。
原代码
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tic-Tac-Toe</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <div class="header"> <h1>Tic Tac Toe</h1> <div class="display"> <form action="nameInput" class="nameInput"> <label for="player1Name">Player 1 Name</label> <input type="text" id="player1Name" name="player1Name" required> <label for="player2Name">Player 2 Name</label> <input type="text" id="player2Name" name="player2Name" required> <input type="submit" id="submit" name="submit" value="Submit / Start"> </form> </div> </div> <div class="board"> <div class="columnA"> <div class="square" id="a1"></div> <div class="square" id="a2"></div> <div class="square" id="a3"></div> </div> <div class="columnB"> <div class="square" id="b1"></div> <div class="square" id="b2"></div> <div class="square" id="b3"></div> </div> <div class="columnC"> <div class="square" id="c1"></div> <div class="square" id="c2"></div> <div class="square" id="c3"></div> </div> </div> </div> <script src="script.js" defer></script> </body> </html>
CSS代码
.container { display: flex; flex-direction: column; justify-content: center; align-items: center; } .header { display: flex; justify-content: center; align-items: center; flex-direction: column; } form { display: flex; flex-direction: column; } .header { margin-bottom: 100px; } .board { display: none; } .square { display: flex; height: 175px; width: 175px; border: 1px solid black; justify-content: center; align-items: center; font-size: 4em; } #a1, #b1, #c1 { border-top: none; } #a3, #b3, #c3 { border-bottom: none; } #a1, #a2, #a3 { border-left: none; } #c1, #c2, #c3 { border-right: none; } #player1Name { margin-bottom: 10%; } #player2Name { margin-bottom: 10%; } label { font-size: 1.2em; }
JavaScript代码
const addPlayer = (name, letter) => { return { name, letter }; } //Form creates players and if names are left blank, will make names "Player 1" and "Player 2" by default. //Will start the game upon clicking submit and hide the form in place of a game display document.querySelector('#submit').addEventListener('click', (e) => { e.preventDefault(); player1 = addPlayer(document.querySelector('#player1Name').value, 'X'); if (player1.name == '') { player1.name = 'Player 1' } player2 = addPlayer(document.querySelector('#player2Name').value, 'O'); if (player2.name == '') { player2.name = 'Player 2' } console.log(player1.name) console.log(player1.letter) console.log(player2.name) console.log(player2.letter) document.querySelector('.board').style = ('display: flex;') document.querySelector('.nameInput').style = ('display: none;') document.querySelector('.display').textContent = `${player1.name}'s turn`; document.querySelector('.display').style = ('font-size: 2em;'); playGame() }) const gameGrid = ['', '', '', '', '', '', '', '', '']; let currentPlayer = ''; let playerTurn = 1; let gameOver = false; //Function to take turns switching from player 1 to player 2, starting with player 1. Display div displays whose turn it is once the current player has just taken a turn. const takeTurns = () => { if (playerTurn === 1) { currentPlayer = player1.letter; document.querySelector('.display').textContent = `${player2.name}'s turn`; playerTurn = 0; } else if (playerTurn === 0) { currentPlayer = player2.letter; document.querySelector('.display').textContent = `${player1.name}'s turn`; playerTurn = 1; } else { return; } } const player1WinDisplay = () => { console.log(`${player1.name} wins!`) console.log('game over') gameOver = true; document.querySelector('.display').textContent = `${player1.name} wins!`; return null } const player2WinDisplay = () => { console.log(`${player2.name} wins!`) console.log('game over') gameOver = true; document.querySelector('.display').textContent = `${player2.name} wins!`; return null } const determineWinner = () => { if (gameGrid[0] != '' && gameGrid[0] == gameGrid[1] && gameGrid[2] == gameGrid[1]) { if (gameGrid[0] == 'X' && gameGrid[1] == 'X' && gameGrid[2] == 'X') { player1WinDisplay() } else { player2WinDisplay() } } else if (gameGrid[3] != '' && gameGrid[3] == gameGrid[4] && gameGrid[5] == gameGrid[4]) { if (gameGrid[3] == 'X' && gameGrid[4] == 'X' && gameGrid[5] == 'X') { player1WinDisplay() } else { player2WinDisplay() } } else if (gameGrid[6] != '' && gameGrid[6] == gameGrid[7] && gameGrid[8] == gameGrid[7]) { if (gameGrid[6] == 'X' && gameGrid[7] == 'X' && gameGrid[8] == 'X') { player1WinDisplay() } else { player2WinDisplay() } } else if (gameGrid[0] != '' && gameGrid[0] == gameGrid[3] && gameGrid[6] == gameGrid[3]) { if (gameGrid[0] == 'X' && gameGrid[3] == 'X' && gameGrid[6] == 'X') { player1WinDisplay() } else { player2WinDisplay() } } else if (gameGrid[1] != '' && gameGrid[1] == gameGrid[4] && gameGrid[7] == gameGrid[4]) { if (gameGrid[1] == 'X' && gameGrid[4] == 'X' && gameGrid[7] == 'X') { player1WinDisplay() } else { player2WinDisplay() } } else if (gameGrid[2] != '' && gameGrid[2] == gameGrid[5] && gameGrid[8] == gameGrid[5]) { if (gameGrid[2] == 'X' && gameGrid[5] == 'X' && gameGrid[8] == 'X') { player1WinDisplay() } else { player2WinDisplay() } } else if (gameGrid[0] != '' && gameGrid[0] == gameGrid[4] && gameGrid[8] == gameGrid[4]) { if (gameGrid[0] == 'X' && gameGrid[4] == 'X' && gameGrid[8] == 'X') { player1WinDisplay() } else { player2WinDisplay() } } else if (gameGrid[6] != '' && gameGrid[6] == gameGrid[4] && gameGrid[2] == gameGrid[4]) { if (gameGrid[6] == 'X' && gameGrid[4] == 'X' && gameGrid[2] == 'X') { player1WinDisplay() } else { player2WinDisplay() } } else if (gameGrid[0] && gameGrid[1] && gameGrid[2] && gameGrid[3] && gameGrid[4] && gameGrid[5] && gameGrid[6] && gameGrid[7] && gameGrid[8] != '') { console.log('Draw') document.querySelector('.display').textContent = `It's a draw`; return null } } const playGame = () => { let squares = document.querySelectorAll('.square'); squares.forEach((square) => { const a1 = document.querySelector('#a1'); const a2 = document.querySelector('#a2'); const a3 = document.querySelector('#a3'); const b1 = document.querySelector('#b1'); const b2 = document.querySelector('#b2'); const b3 = document.querySelector('#b3'); const c1 = document.querySelector('#c1'); const c2 = document.querySelector('#c2'); const c3 = document.querySelector('#c3'); square.addEventListener('click', () => { //Locks in div display and array if div has been clicked; prevents takeTurns and turn display from changing if clicking on filled square determineWinner() if (square.textContent != '') { return } else if (square.textContent == '' && gameOver == false) { takeTurns() if (square == a1) { gameGrid[0] = currentPlayer; a1.textContent = currentPlayer; } else if (square == b1) { gameGrid[1] = currentPlayer; b1.textContent = currentPlayer; } else if (square == c1) { gameGrid[2] = currentPlayer; c1.textContent = currentPlayer; } else if (square == a2) { gameGrid[3] = currentPlayer; a2.textContent = currentPlayer; } else if (square == b2) { gameGrid[4] = currentPlayer; b2.textContent = currentPlayer; } else if (square == c2) { gameGrid[5] = currentPlayer; c2.textContent = currentPlayer; } else if (square == a3) { gameGrid[6] = currentPlayer; a3.textContent = currentPlayer; } else if (square == b3) { gameGrid[7] = currentPlayer; b3.textContent = currentPlayer; } else if (square == c3) { gameGrid[8] = currentPlayer; c3.textContent = currentPlayer; } console.log(gameGrid); } }) }) }
问题分析与修改方案
核心问题
- 调用时机错误:原代码中
determineWinner()在点击事件开头调用,此时还未更新gameGrid和DOM,检测的是上一步状态,无法及时响应当前落子的胜负结果。 - 冗余DOM操作:在
forEach循环内重复获取所有方块的DOM元素,完全没必要且影响性能。 - 外部调用失效原因:如果把
determineWinner()放在forEach外部,只会在游戏初始化时执行一次,不会在每次落子后自动触发,自然失效。
修改步骤
- 调整检测函数调用时机:在玩家落子、更新完
gameGrid和DOM后立即调用determineWinner(),确保检测最新状态。 - 简化方块索引映射:通过方块ID与
gameGrid索引的映射关系,去掉大量冗余的if-else判断。 - 优化获胜检测逻辑:用数组存储所有获胜组合,通过循环遍历检测,代码更易维护。
修改后的JavaScript代码
const addPlayer = (name, letter) => { return { name, letter }; } document.querySelector('#submit').addEventListener('click', (e) => { e.preventDefault(); player1 = addPlayer(document.querySelector('#player1Name').value, 'X'); if (player1.name === '') { player1.name = 'Player 1' } player2 = addPlayer(document.querySelector('#player2Name').value, 'O'); if (player2.name === '') { player2.name = 'Player 2' } document.querySelector('.board').style.display = 'flex'; document.querySelector('.nameInput').style.display = 'none'; document.querySelector('.display').textContent = `${player1.name}'s turn`; document.querySelector('.display').style.fontSize = '2em'; playGame() }) const gameGrid = ['', '', '', '', '', '', '', '', '']; // 建立方块ID到网格索引的映射 const squareIdToIndex = { 'a1': 0, 'b1': 1, 'c1': 2, 'a2': 3, 'b2': 4, 'c2': 5, 'a3': 6, 'b3': 7, 'c3': 8 }; let currentPlayer = ''; let playerTurn = 1; let gameOver = false; const takeTurns = () => { if (playerTurn === 1) { currentPlayer = player1.letter; document.querySelector('.display').textContent = `${player2.name}'s turn`; playerTurn = 0; } else if (playerTurn === 0) { currentPlayer = player2.letter; document.querySelector('.display').textContent = `${player1.name}'s turn`; playerTurn = 1; } } const player1WinDisplay = () => { console.log(`${player1.name} wins!`) gameOver = true; document.querySelector('.display').textContent = `${player1.name} wins!`; } const player2WinDisplay = () => { console.log(`${player2.name} wins!`) gameOver = true; document.querySelector('.display').textContent = `${player2.name} wins!`; } const determineWinner = () => { // 所有获胜组合的索引集合 const winCombos = [ [0,1,2], [3,4,5], [6,7,8], // 横向 [0,3,6], [1,4,7], [2,5,8], // 纵向 [0,4,8], [2,4,6] // 对角线 ]; // 检查是否有玩家获胜 for (const combo of winCombos) { const [a, b, c] = combo; if (gameGrid[a] && gameGrid[a] === gameGrid[b] && gameGrid[a] === gameGrid[c]) { gameGrid[a] === 'X' ? player1WinDisplay() : player2WinDisplay(); return; } } // 检查是否平局 if (!gameGrid.includes('')) { console.log('Draw') document.querySelector('.display').textContent = `It's a draw`; gameOver = true; } } const playGame = () => { const squares = document.querySelectorAll('.square'); squares.forEach((square) => { square.addEventListener('click', () => { // 游戏结束或方块已被占用,直接返回 if (gameOver || square.textContent !== '') { return; } takeTurns(); // 通过ID获取对应网格索引 const index = squareIdToIndex[square.id]; gameGrid[index] = current
相关产品推荐
相关产品推荐

