井字棋游戏VSplayer与VScomp模式切换逻辑异常问题
井字棋模式切换异常问题解决
问题根源
每次调用VSplayer()或VScomp()时,都会为所有.box元素新增点击事件监听器,但之前模式绑定的监听器并未被移除。这导致多个监听器同时存在于元素上,点击格子时会触发所有曾经绑定过的逻辑,因此首次启动的模式逻辑会一直生效。
修复方案
在切换模式时,先清除所有.box元素上的点击事件监听器,再绑定当前模式的逻辑;同时重置游戏状态(清空棋盘、重置游戏结束标识等)。
修改后的JS代码
// 通用工具函数:重置棋盘并清除事件监听器 function resetGameState() { const Boxes = document.querySelectorAll('.box'); const info = document.getElementById('info'); const resetBtn = document.getElementById('reset'); // 克隆元素以清除所有事件监听器 Boxes.forEach(box => { const newBox = box.cloneNode(true); box.parentNode.replaceChild(newBox, box); newBox.innerHTML = ""; newBox.classList.remove("winner"); newBox.style.cursor = "pointer"; }); // 重置信息文本和游戏状态 info.innerText = ""; // 移除重置按钮的旧监听器(避免重复绑定) resetBtn.replaceWith(resetBtn.cloneNode(true)); } // Computer Logic function VScomp() { resetGameState(); const Boxes = document.querySelectorAll('.box'); const info = document.getElementById('info'); const reset = document.getElementById('reset'); let currentPlayer = "X"; let gameover = false; info.innerText = `Player X Turn`; Boxes.forEach(box => { box.addEventListener('click', () => { if (box.innerHTML === "" && !gameover) { box.innerHTML = currentPlayer; checkWins(); if (!gameover) { compchoice(); } if (gameover) { Boxes.forEach(box => box.style.cursor = "not-allowed"); } } }); }); // Check Wins const checkWins = () => { const wins = [ [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6] ]; wins.forEach(line => { const [a,b,c] = line; if (Boxes[a].innerHTML && Boxes[a].innerHTML === Boxes[b].innerHTML && Boxes[b].innerHTML === Boxes[c].innerHTML) { info.innerText = `${Boxes[a].innerHTML} Won`; gameover = true; line.forEach(idx => Boxes[idx].classList.add("winner")); } }); }; // 重置按钮逻辑 reset.addEventListener('click', () => { Boxes.forEach(box => { box.innerHTML = ""; box.classList.remove("winner"); box.style.cursor = "pointer"; }); gameover = false; currentPlayer = "X"; info.innerText = `Player X Turn`; }); function compchoice() { if (!gameover) { let emptyBoxes = Array.from(Boxes).filter(box => box.innerHTML === ""); if (emptyBoxes.length > 0) { const randomIdx = Math.floor(Math.random() * emptyBoxes.length); emptyBoxes[randomIdx].innerHTML = "O"; checkWins(); } } } } // VS player Logic function VSplayer() { resetGameState(); const Boxes = document.querySelectorAll('.box'); const info = document.getElementById('info'); const reset = document.getElementById('reset'); let currentPlayer = "X"; let gameover = false; info.innerText = `${currentPlayer} Turn`; Boxes.forEach(box => { box.addEventListener('click', () => { if (box.innerHTML === "" && !gameover) { box.innerHTML = currentPlayer; checkWins(); if (!gameover) { currentPlayer = currentPlayer === "X" ? "O" : "X"; info.innerText = `${currentPlayer} Turn`; } if (gameover) { Boxes.forEach(box => box.style.cursor = "not-allowed"); } } }); }); // Check Wins const checkWins = () => { const wins = [ [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6] ]; wins.forEach(line => { const [a,b,c] = line; if (Boxes[a].innerHTML && Boxes[a].innerHTML === Boxes[b].innerHTML && Boxes[b].innerHTML === Boxes[c].innerHTML) { info.innerText = `${Boxes[a].innerHTML} Won`; gameover = true; line.forEach(idx => Boxes[idx].classList.add("winner")); } }); }; // 重置按钮逻辑 reset.addEventListener('click', () => { Boxes.forEach(box => { box.innerHTML = ""; box.classList.remove("winner"); box.style.cursor = "pointer"; }); gameover = false; currentPlayer = "X"; info.innerText = `${currentPlayer} Turn`; }); } // 初始启动人机模式 VScomp();
关键改动说明
- 新增
resetGameState函数:负责克隆所有.box元素(清除原有事件监听器),清空棋盘,重置信息文本,并重置重置按钮的事件监听器,避免重复绑定。 - 每个模式函数开头调用
resetGameState:确保切换模式时完全清除之前的逻辑状态和事件绑定。 - 优化电脑选择逻辑:改为先筛选空盒子再随机选择,避免循环等待的低效写法。
- 统一变量命名:将变量
a改为currentPlayer,提升代码可读性。
额外优化建议
- 可以将
checkWins函数提取为全局通用函数,避免两个模式中重复代码。 - 使用一个全局变量跟踪当前游戏模式,避免重复绑定事件的问题,进一步简化逻辑。
内容的提问来源于stack exchange,提问作者Aakash
相关产品推荐
相关产品推荐

