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

如何在Tic Tac Toe中阻止玩家修改已占据单元格的标记

解决方案:阻止在已占据单元格落子的实现

核心逻辑是先检查目标单元格是否为空,只有为空时才允许落子,且落子成功后再切换玩家回合,具体修改如下:

1. 优化Gameboard模块的setCell方法

在setCell中添加单元格为空的检查,确保只有空单元格能被更新,同时返回操作结果方便外部判断:

const Gameboard = (() => {
    "use strict"
    let gameboard = new Array(9).fill("");
    const setCell = (index, value) => {
        // 仅当单元格为空时才更新
        if (gameboard[index] === "") {
            gameboard[index] = value;
            return true; // 落子成功
        }
        return false; // 落子失败
    };
    const resetBoard = () => {
        gameboard = ["", "", "", "", "", "", "", "", "",];
    }
    const getBoard = () => [...gameboard];

    return {
        getBoard,
        resetBoard,
        setCell
    }
})();

2. 调整玩家回合逻辑,避免无效切换

原来的playerTurn函数每次调用都会强制切换回合,即使落子失败,现在拆分出获取当前标记和切换回合的独立函数:

const gameController = (() => {
    "use strict"
    const playerFunction = (player, marker) => {
        return {
            player,
            marker,
        }
    }

    let player1 = playerFunction("Test1", "X")
    let player2 = playerFunction("Test2", "O")
    let isPlayerOneTurn = true

    // 获取当前玩家的标记,不切换回合
    const getCurrentPlayerMarker = () => {
        return isPlayerOneTurn ? player1.marker : player2.marker;
    };

    // 专门的回合切换函数
    const switchTurn = () => {
        isPlayerOneTurn = !isPlayerOneTurn;
    };

    // 后续代码...
})();

3. 修改点击事件处理逻辑

点击单元格时先检查对应位置是否已被占据,只有空单元格才执行落子、切换回合和渲染操作:

const boardEl = displayController.board;
displayController.renderBoard(Gameboard.getBoard());
boardEl.addEventListener("click", (e) => {
    if (e.target.classList.contains("cell")) {
        const index = e.target.getAttribute("cell-data");
        const currentBoard = Gameboard.getBoard();
        
        // 检查单元格是否已被占据,已占则直接返回
        if (currentBoard[index] !== "") {
            return;
        }
        
        const currentMarker = getCurrentPlayerMarker();
        const isSuccess = Gameboard.setCell(index, currentMarker);
        
        if (isSuccess) {
            // 落子成功才切换回合并重新渲染
            switchTurn();
            displayController.clearBoard();
            displayController.renderBoard(Gameboard.getBoard());
        }
    }
});

4. 可选:添加视觉提示(优化用户体验)

给已占据的单元格添加样式,让用户直观知道该格子不可点击:

修改displayController的renderBoard方法:

const renderBoard = (arr) => {
    for (let i = 0; i < arr.length; i++) {
        let cell = document.createElement("div")
        cell.id = i;
        cell.classList.add("cell"); // 改用add避免覆盖类
        if (arr[i] !== "") {
            cell.classList.add("occupied"); // 添加已占用类
        }
        cell.setAttribute("cell-data", i)
        cell.textContent = arr[i]
        board.append(cell)
    }
}

添加CSS样式:

.cell.occupied {
    cursor: not-allowed;
    background-color: rgba(200, 200, 200, 0.3);
}

.cell.occupied:hover {
    background-color: rgba(200, 200, 200, 0.3); /* 覆盖hover效果 */
}

完整修改后的代码

JavaScript

const Gameboard = (() => {
    "use strict"
    let gameboard = new Array(9).fill("");
    const setCell = (index, value) => {
        if (gameboard[index] === "") {
            gameboard[index] = value;
            return true;
        }
        return false;
    };
    const resetBoard = () => {
        gameboard = ["", "", "", "", "", "", "", "", "",];
    }
    const getBoard = () => [...gameboard];

    return {
        getBoard,
        resetBoard,
        setCell
    }
})();

const displayController = (() => {
    "use strict"
    const board = document.querySelector(".board")

    const renderBoard = (arr) => {
        for (let i = 0; i < arr.length; i++) {
            let cell = document.createElement("div")
            cell.id = i;
            cell.classList.add("cell");
            if (arr[i] !== "") {
                cell.classList.add("occupied");
            }
            cell.setAttribute("cell-data", i)
            cell.textContent = arr[i]
            board.append(cell)
        }
    }
    const clearBoard = () => {
        while (board.hasChildNodes()) {
            board.removeChild(board.firstChild);
        }
    }
    return {
        board,
        renderBoard,
        clearBoard
    }
})();
    
const gameController = (() => {
    "use strict"
    const playerFunction = (player, marker) => {
        return {
            player,
            marker,
        }
    }

    let player1 = playerFunction("Test1", "X")
    let player2 = playerFunction("Test2", "O")
    let isPlayerOneTurn = true

    const getCurrentPlayerMarker = () => {
        return isPlayerOneTurn ? player1.marker : player2.marker;
    };

    const switchTurn = () => {
        isPlayerOneTurn = !isPlayerOneTurn;
    };

    const boardEl = displayController.board;
    displayController.renderBoard(Gameboard.getBoard());
    boardEl.addEventListener("click", (e) => {
        if (e.target.classList.contains("cell")) {
            const index = e.target.getAttribute("cell-data");
            const currentBoard = Gameboard.getBoard();
            
            if (currentBoard[index] !== "") {
                return;
            }
            
            const currentMarker = getCurrentPlayerMarker();
            const isSuccess = Gameboard.setCell(index, currentMarker);
            
            if (isSuccess) {
                switchTurn();
                displayController.clearBoard();
                displayController.renderBoard(Gameboard.getBoard());
            }
        }
    });
})();

CSS

body {
    margin-top: 10rem;
    font-family: 'Dongle', sans-serif;
}

header {
    display: flex;
    justify-content: center;
}

h1 {
    color: rgba(0, 0, 0);
    font-size: 70px;
    font-weight: 500;
    margin-bottom: auto;
}

.board {
    display: grid;
    grid-template-columns: repeat(3, 100px);
    justify-content: center;
}

.cell {
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 70px;
    border: solid 1px;
    width: 100px;
    user-select: none;
    height: 100px;
}

.cell:hover {
    background-color: rgb(175, 175, 175, .5);
}

.cell.occupied {
    cursor: not-allowed;
    background-color: rgba(200, 200, 200, 0.3);
}

.cell.occupied:hover {
    background-color: rgba(200, 200, 200, 0.3);
}

.cell:nth-child(1) {
    border-top: none;
    border-left: none;
}

.cell:nth-child(2) {
    border-top: none;
}

.cell:nth-child(3),
.cell:nth-child(4),
.cell:nth-child(6) {
    border-top: none;
    border-right: none;
    border-left: none;
}

.cell:nth-child(5) {
    border-top: none;
}

.cell:nth-child(7) {
    border: none;
}

.cell:nth-child(8) {
    border-top: none;
    border-bottom: none;
}

.cell:nth-child(9) {
    border: none;
}

HTML

<div class="board"></div>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 00:02:23