如何修复井字棋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();
修复说明
- 分离事件绑定与状态初始化:将单元格点击事件绑定移到
bindCellEvents()函数,仅在初始化时执行一次,避免重复绑定导致的逻辑冲突。 - 全局步数索引:新增
moveIndex变量记录当前使用的board数组索引,重玩时重置为0,确保每次新游戏都从数组第一个元素开始读取。 - 拆分职责:将
placeMark()拆分为状态重置和数组初始化逻辑,initBoard()专门负责填充board数组,代码职责更清晰。 - 全面重置状态:重玩时不仅重置
board和game数组,还重置value(获胜状态)和moveIndex,确保所有状态回到初始值。
内容的提问来源于stack exchange,提问作者icehengbem
相关产品推荐
相关产品推荐

