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

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);
      }
    })
  })
}

问题分析与修改方案

核心问题

  1. 调用时机错误:原代码中determineWinner()在点击事件开头调用,此时还未更新gameGrid和DOM,检测的是上一步状态,无法及时响应当前落子的胜负结果。
  2. 冗余DOM操作:在forEach循环内重复获取所有方块的DOM元素,完全没必要且影响性能。
  3. 外部调用失效原因:如果把determineWinner()放在forEach外部,只会在游戏初始化时执行一次,不会在每次落子后自动触发,自然失效。

修改步骤

  1. 调整检测函数调用时机:在玩家落子、更新完gameGrid和DOM后立即调用determineWinner(),确保检测最新状态。
  2. 简化方块索引映射:通过方块ID与gameGrid索引的映射关系,去掉大量冗余的if-else判断。
  3. 优化获胜检测逻辑:用数组存储所有获胜组合,通过循环遍历检测,代码更易维护。

修改后的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
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:21:55