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

井字棋游戏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();

关键改动说明

  1. 新增resetGameState函数:负责克隆所有.box元素(清除原有事件监听器),清空棋盘,重置信息文本,并重置重置按钮的事件监听器,避免重复绑定。
  2. 每个模式函数开头调用resetGameState:确保切换模式时完全清除之前的逻辑状态和事件绑定。
  3. 优化电脑选择逻辑:改为先筛选空盒子再随机选择,避免循环等待的低效写法。
  4. 统一变量命名:将变量a改为currentPlayer,提升代码可读性。

额外优化建议

  • 可以将checkWins函数提取为全局通用函数,避免两个模式中重复代码。
  • 使用一个全局变量跟踪当前游戏模式,避免重复绑定事件的问题,进一步简化逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 19:18:09