如何在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
相关产品推荐
相关产品推荐

