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

井字棋游戏Bug排查:X三连有胜利提示,O三连无响应

井字棋游戏胜利提示异常问题排查与修复

问题现象

开发的井字棋游戏存在以下异常:

  • 玩家(X)连成三连时,能正常弹出胜利提示;
  • 电脑(O)连成三连时,无任何胜利提示;
  • 切换初始玩家为O后,问题反转,仅O能触发胜利提示,X无法触发。

代码片段

HTML代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>Tic Tac Toe</title>
</head>
<body>
    <div id="board" class="container">
        <!-- Create a 3x3 grid here -->
    </div>
    <script src="script.js"></script>
</body>
</html>

JavaScript代码

document.addEventListener('DOMContentLoaded', () => {  
  const board = document.getElementById('board');  
  let currentPlayer = 'X';  
  let gameStarted = false;  
  let gameOver = false; // 跟踪游戏状态变量  

  // 创建棋盘
  function createBoard() {  
    for (let i = 0; i < 3; i++) {  
      for (let j = 0; j < 3; j++) {  
        const cell = document.createElement('div');  
        cell.classList.add('cell');  
        cell.setAttribute('data-row', i);  
        cell.setAttribute('data-col', j);  
        board.appendChild(cell);  
        cell.addEventListener('click', handleCellClick);  
      }  
    }  
  }  

  // 处理单元格点击事件
  function handleCellClick(event) {  
    if (gameOver) return; // 游戏结束则不响应点击
    const clickedCell = event.target;  
    // 检查单元格是否已被填充
    if (!clickedCell.textContent.trim()) {  
      clickedCell.textContent = currentPlayer;  
      clickedCell.classList.add('clicked');  
      // 检查胜利或平局
      if (checkWinner(row, col)) {  
        announceWinner();  
      } else if (checkTie()) {  
        announceTie();  
      } else {  
        // 延迟切换玩家并执行电脑落子
        setTimeout(() => {  
          currentPlayer = 'O';  
          makeComputerMove();  
          currentPlayer = 'X';  
        }, 500);  
      }  
    }  
  }  

  // 电脑随机落子
  function makeComputerMove() {  
    const emptyCells = document.querySelectorAll('.cell:not(.clicked)');  
    if (emptyCells.length > 0) {  
      const randomIndex = Math.floor(Math.random() * emptyCells.length);  
      const randomCell = emptyCells[randomIndex];  
      randomCell.textContent = currentPlayer;  
      randomCell.classList.add('clicked');  
    }  
  }  

  // 检查是否有玩家获胜
  function checkWinner(row, col) {  
    // 检查行
    if (  
      checkLine(0, 0, 0, 1, 0, 2) ||  
      checkLine(1, 0, 1, 1, 1, 2) ||  
      checkLine(2, 0, 2, 1, 2, 2)  
    ) {  
      return true;  
    }  
    // 检查列
    if (  
      checkLine(0, 0, 1, 0, 2, 0) ||  
      checkLine(0, 1, 1, 1, 2, 1) ||  
      checkLine(0, 2, 1, 2, 2, 2)  
    ) {  
      return true;  
    }  
    // 检查对角线
    if (  
      checkLine(0, 0, 1, 1, 2, 2) ||       
      checkLine(0, 2, 1, 1, 2, 0)      
    ) {  
      return true;  
    }  
    return false;  
  }  

  // 辅助函数:检查一行/列/对角线是否连成三连
  function checkLine(row1, col1, row2, col2, row3, col3) {  
    const cell1 = document.querySelector(`[data-row="${row1}"][data-col="${col1}"]`);  
    const cell2 = document.querySelector(`[data-row="${row2}"][data-col="${col2}"]`);  
    const cell3 = document.querySelector(`[data-row="${row3}"][data-col="${col3}"]`);  
    return (  
      cell1.textContent.trim() !== '' &&  
      cell1.textContent === cell2.textContent &&  
      cell1.textContent === cell3.textContent  
    );  
  }  

  // 检查是否平局
  function checkTie() {  
    const cells = document.querySelectorAll('.cell');  
    for (const cell of cells) {  
      if (!cell.textContent.trim()) {  
        return false;  
      }  
    }  
    return true;  
  }  

  // 重置游戏
  function resetGame() {  
    currentPlayer = 'X';  
    gameOver = false;  
    const cells = document.querySelectorAll('.cell');  
    cells.forEach(cell => {  
      cell.textContent = '';  
      cell.classList.remove('clicked');  
    });  
  }  

  // 弹出胜利提示
  function announceWinner() {  
    const winner = currentPlayer === 'X' ? 'Player X' : 'Player O';  
    popUpMenu.popupBox(`${winner} wins!`);  
    gameOver = true;  
  }  

  // 弹出平局提示
  function announceTie() {  
    popUpMenu.popupBox(`It's a Tie!`);  
    gameOver = true;  
  }  

  // 弹窗模块
  const popUpMenu = (() => {  
    const resetPage = () => {  
        location.reload();  
    }  
    const popupBox = (outcome) => {  
      const overlay = document.createElement('div');  
      overlay.className = 'overlay';  
      const popup = document.createElement('div');  
      popup.className = 'popup-box';  
      const result = document.createElement('div');  
      result.id = 'win-lose';  
      result.textContent = outcome;  
      const replayBtn = document.createElement('button');  
      replayBtn.id = 'play-again-btn';  
      replayBtn.textContent = 'Play Again?';  
      replayBtn.addEventListener('click', resetPage);  
      popup.appendChild(result);  
      popup.appendChild(replayBtn);  
      overlay.appendChild(popup);  
      document.body.appendChild(overlay);  
    };  
    return { popupBox };  
  })();  

  // 页面加载时创建棋盘
  createBoard();  
});

核心问题分析

问题根源在于电脑(O)落子后未执行胜利检查逻辑:

  • 玩家(X)落子后,代码会调用checkWinner检查是否胜利,若满足条件则弹出提示;
  • 但电脑(O)落子后,仅完成落子操作,直接切换回X玩家,完全跳过了胜利检查和平局检查流程;
  • 因此当O连成三连时,游戏无法触发announceWinner函数,自然不会弹出胜利提示。

修复方案

1. 给电脑落子函数添加胜利检查

修改makeComputerMove,在落子完成后立即检查胜利或平局:

function makeComputerMove() {
  const emptyCells = document.querySelectorAll('.cell:not(.clicked)');
  if (emptyCells.length > 0) {
    const randomIndex = Math.floor(Math.random() * emptyCells.length);
    const randomCell = emptyCells[randomIndex];
    randomCell.textContent = currentPlayer;
    randomCell.classList.add('clicked');
    
    // 新增:电脑落子后检查胜利状态
    if (checkWinner()) {
      announceWinner();
    } else if (checkTie()) {
      announceTie();
    }
  }
}

2. 移除checkWinner的无用参数

原checkWinner函数接收的row, col参数并未实际使用,直接移除即可:

function checkWinner() {
  // 保留原有检查逻辑,删除冗余参数
  if (
    checkLine(0, 0, 0, 1, 0, 2) ||
    checkLine(1, 0, 1, 1, 1, 2) ||
    checkLine(2, 0, 2, 1, 2, 2)
  ) {
    return true;
  }
  if (
    checkLine(0, 0, 1, 0, 2, 0) ||
    checkLine(0, 1, 1, 1, 2, 1) ||
    checkLine(0, 2, 1, 2, 2, 2)
  ) {
    return true;
  }
  if (
    checkLine(0, 0, 1, 1, 2, 2) ||
    checkLine(0, 2, 1, 1, 2, 0)
  ) {
    return true;
  }
  return false;
}

3. 同步修改玩家落子的调用逻辑

将handleCellClick里调用checkWinner的地方,去掉多余的row, col参数:

// 检查胜利或平局
if (checkWinner()) {
  announceWinner();
} else if (checkTie()) {
  announceTie();
}

额外优化建议

  • 移除未使用的gameStarted变量,或补充游戏初始化逻辑;
  • 将弹窗中的location.reload()改为调用resetGame(),避免页面重载带来的体验损耗。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:09:51