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

如何修复井字棋replayGame重置后placeMark符号读取异常问题

问题:井字棋重玩后placeMark()无法从头读取board数组

我开发了一款多人井字棋游戏,玩家选择“Player One”或“Player Two”即可开始游戏。选择后会调用placeMark()函数并更新chosenPlayer变量,该函数负责填充数组board = Gameboard.gameboard。填充后的数组包含9个'x'和'o'符号,玩家可通过点击事件将符号填入屏幕上的div,同时这些符号会被添加到二维数组game中。当game数组中的符号达成获胜条件时,会调用getWinner()函数,触发模态框弹窗和重玩按钮。点击重玩按钮会调用replayGame()函数,该函数能正确清除div的innerHTML、恢复光标指针、清空二维数组以开启新回合,并移除模态框。

我的问题在于:希望点击重玩按钮时重新调用placeMark()函数,使其不再从board数组当前状态的位置读取符号,而是从头开始读取,但目前每次玩家获胜后点击重玩按钮,placeMark()函数仍会从board数组上次中断的位置开始选择符号。我尝试重置board数组并根据所选玩家重新调用placeMark()函数,但仍无法解决问题。


完整代码

HTML

<div class="container-outer">
  <header>
    <h1>Tic-Tac-Toe</h1>
  </header>

  <div class="players">
    <button type="button" id="one">Player One</button>
    <button type="button" id="two">Player Two</button>
  </div>

  <div class="container-inner">
    <div class="cell-1 cells" id="00" data-index="0"></div>
    <div class="cell-2 cells" id="01" data-index="1"></div>
    <div class="cell-3 cells" id="02" data-index="2"></div>
    <div class="cell-4 cells" id="10" data-index="3"></div>
    <div class="cell-5 cells" id="11" data-index="4"></div>
    <div class="cell-6 cells" id="12" data-index="5"></div>
    <div class="cell-7 cells" id="20" data-index="6"></div>
    <div class="cell-8 cells" id="21" data-index="7"></div>
    <div class="cell-9 cells" id="22" data-index="8"></div>
  </div>
</div>
<div class="modal-wrapper">
  <div class="modal">
    <div class="modal-content">
      <div id="popup"></div>
      <button type="button" id="replay">Replay</button>
    </div>
  </div>
</div>

CSS

html {
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin-top: 3em;
}

.container-inner {
  display: grid;
  grid-template-columns: repeat(3, 150px);
  margin: 0 auto;
  width: 750px;
}

.container-outer {
  margin: 0 auto;
  width: 750px;
}

.modal-wrapper {
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  position: absolute;
  top: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  display: none;
}

.modal {
  position: relative;
  background-color: #fff;
  padding: 80px 20px 20px;
  overflow: hidden;
  height: 300px;
  width: 500px;
  border-radius: 10px;
  box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.4);
  margin: 0 auto;
  text-align: center;
}

.modal-button {
  background: #e20540;
  color: #fff;
  position: absolute;
  top: 20px;
  left: 20px;
}

.cells {
  border: 1px solid #000;
  width: 150px;
  height: 150px;
}

JavaScript

const GameBoard = (() => {
  let gameboard = [];
  return {
    gameboard
  };
})();

const players = (() => {
  const player1 = {
    name: '',
    sign: "x"
  };
  const player2 = {
    name: '',
    sign: "o"
  };
  let one = player1.sign;
  let two = player2.sign;
  return {
    one,
    two
  };
})();

const gameflow = (() => {
  let board = GameBoard.gameboard;
  let oneBtn = document.getElementById('one')
  let twoBtn = document.getElementById('two');
  let cells = document.getElementsByClassName('cells');
  let array = Array.from(cells);
  let game = [
    ['', '', ''],
    ['', '', ''],
    ['', '', '']
  ];
  let value = false;
  let chosenPlayer = "";
  let moveIndex = 0; // 记录当前步数索引

  const addSymbol = (row, col, sign) => {
    if (game[row][col] === '') {
      game[row][col] = sign;
    }
  }

  const getWinner = (...sign) => {
    // 横向判断
    const checkRow = (row, playerSign) => {
      return game[row][0] === playerSign && game[row][1] === playerSign && game[row][2] === playerSign;
    };
    // 纵向判断
    const checkCol = (col, playerSign) => {
      return game[0][col] === playerSign && game[1][col] === playerSign && game[2][col] === playerSign;
    };
    // 对角线判断
    const checkDiag = (playerSign) => {
      return (game[0][0] === playerSign && game[1][1] === playerSign && game[2][2] === playerSign) ||
             (game[0][2] === playerSign && game[1][1] === playerSign && game[2][0] === playerSign);
    };

    // 检查X获胜
    if (checkRow(0, sign[0]) || checkRow(1, sign[0]) || checkRow(2, sign[0]) ||
        checkCol(0, sign[0]) || checkCol(1, sign[0]) || checkCol(2, sign[0]) ||
        checkDiag(sign[0])) {
      stopClick();
      toggleModal();
      value = true;
    }

    // 检查O获胜
    if (checkRow(0, sign[1]) || checkRow(1, sign[1]) || checkRow(2, sign[1]) ||
        checkCol(0, sign[1]) || checkCol(1, sign[1]) || checkCol(2, sign[1]) ||
        checkDiag(sign[1])) {
      stopClick();
      toggleModal();
      value = true;
    }
  }

  const replayGame = () => {
    const modalWrapper = document.querySelector('.modal-wrapper');
    const replay = document.getElementById('replay');
    replay.addEventListener('click', () => {
      modalWrapper.style.display = "none";
      
      // 重置单元格内容
      array.forEach(cell => {
        cell.innerHTML = "";
        cell.style.pointerEvents = 'auto';
      });
      
      // 重置game数组
      for (let i = 0; i < game.length; i++) {
        for (let j = 0; j < game.length; j++) {
          game[i][j] = "";
        }
      }
      
      // 重置状态变量
      value = false;
      moveIndex = 0;
      board.length = 0;

      // 重新初始化board数组
      if (chosenPlayer === "x") {
        initBoard(players.one);
      }
      if (chosenPlayer === "o") {
        initBoard(players.two);
      }
    });
  }

  // 初始化board数组
  const initBoard = (sign) => {
    board.length = 0;
    let values = sign === players.one ? ["x", "o"] : ["o", "x"];
    while (board.length < 9) {
      board.push(values[board.length % 2]);
    }
  }

  // 绑定单元格点击事件(仅执行一次)
  const bindCellEvents = () => {
    array.forEach(cell => {
      cell.addEventListener('click', function() {
        if (cell.innerHTML === "" && !value) {
          cell.innerHTML = board[moveIndex++];
          let strings = cell.id;
          let row = parseInt(strings.charAt(0));
          let col = parseInt(strings.charAt(1));
          addSymbol(row, col, cell.innerHTML);
          getWinner(players.one, players.two);
          checkTie();
        }
      });
    });
  }

  const placeMark = (sign) => {
    initBoard(sign);
    moveIndex = 0; // 重置步数索引
    value = false; // 重置获胜状态
  }

  const choosePlayer = () => {
    oneBtn.addEventListener('click', () => {
      chosenPlayer = "x";
      placeMark(players.one);
    });
    twoBtn.addEventListener('click', () => {
      chosenPlayer = "o";
      placeMark(players.two);
    });
  }

  const stopClick = () => {
    array.forEach(cell => {
      cell.style.pointerEvents = 'none';
    });
  }

  const checkTie = () => {
    let isFull = game.every(row => row.every(cell => cell !== ""));
    if (isFull && !value) {
      document.getElementById('popup').innerHTML = "Tie Game!";
      toggleModal();
    }
  }

  const toggleModal = () => {
    const modalWrapper = document.querySelector('.modal-wrapper');
    modalWrapper.style.display = "flex";
  }

  // 初始化绑定事件
  bindCellEvents();
  choosePlayer();

  return {
    replayGame
  };
})();

gameflow.replayGame();

修复说明

  1. 分离事件绑定与状态初始化:将单元格点击事件绑定移到bindCellEvents()函数,仅在初始化时执行一次,避免重复绑定导致的逻辑冲突。
  2. 全局步数索引:新增moveIndex变量记录当前使用的board数组索引,重玩时重置为0,确保每次新游戏都从数组第一个元素开始读取。
  3. 拆分职责:将placeMark()拆分为状态重置和数组初始化逻辑,initBoard()专门负责填充board数组,代码职责更清晰。
  4. 全面重置状态:重玩时不仅重置board和game数组,还重置value(获胜状态)和moveIndex,确保所有状态回到初始值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 21:29:51