如何基于单选按钮选择动态执行不同游戏逻辑(PvP 或 PvC)

5次阅读

如何基于单选按钮选择动态执行不同游戏逻辑(PvP 或 PvC)

本文详解 javascript 中单选按钮(radio)触发对应游戏逻辑的正确实现方式,重点解决因作用域、函数嵌套、执行时机不当导致的“函数未定义”或“逻辑不执行”问题,并提供可立即运行的结构化示例。

在开发井字棋(Tic-Tac-Toe)等交互式游戏时,常需根据用户选择的游戏模式(如“玩家对玩家”PvP 或“玩家对电脑”PvC)动态启用不同的核心逻辑。但初学者常陷入一个典型误区:将整个游戏逻辑封装为嵌套函数(如 pvp() 内部定义 boxClick、isWinner 等),再试图在事件回调中调用该外层函数——结果是内部函数在调用时处于闭包私有作用域,外部事件处理器无法访问,报错 undefined

根本原因在于:JavaScript 中函数作用域是词法作用域(Lexical Scope),pvp() 内部声明的变量和函数仅在其执行上下文中有效;若未显式暴露或挂载到全局/模块级作用域,btnStart.addEventListener 的回调中调用 pvp() 仅执行了该函数体,却未将内部逻辑(如事件监听、状态管理)与 dom 元素真正绑定。

✅ 正确思路是:分离配置与执行,按需初始化,而非嵌套定义。即:

  • 将 PvP/PvC 的共用基础能力(如棋盘渲染、状态更新、计时器)抽离为可复用工具函数;
  • 将模式专属逻辑(如对手行为、胜负判定扩展)封装为独立初始化函数(如 initPvP() / initPvC());
  • 在用户点击“开始”后,根据 radio 选中值一次性调用对应初始化函数,完成事件绑定与状态准备。

以下为精简、可直接运行的实践方案:

选择游戏模式:




请先选择模式并点击“开始”
// JavaScript 核心逻辑(推荐置于 script 标签末尾或使用 DOMContentLoaded) document.addEventListener('DOMContentLoaded', () => {   const board = document.getElementById('board');   const statusEl = document.getElementById('status');   const startBtn = document.getElementById('startBtn');   const boxes = board.querySelectorAll('[data-index]');   let currentPlayer = 'X';   let gameActive = false;   let gameMode = 'pvp'; // 默认模式    // ✅ 共用工具函数(非嵌套,全局可用)   const togglePlayer = () => currentPlayer = currentPlayer === 'X' ? 'O' : 'X';   const resetBoard = () => {     boxes.forEach(box => {       box.textContent = '';       box.classList.remove('win');     });   };   const updateStatus = (msg) => statusEl.textContent = msg;    // ✅ PvP 模式初始化:绑定玩家点击逻辑   const initPvP = () => {     gameActive = true;     updateStatus(`游戏开始!${currentPlayer} 先手`);      boxes.forEach(box => {       box.addEventListener('click', function() {         if (!gameActive || this.textContent !== '') return;         this.textContent = currentPlayer;         if (checkWin()) {           updateStatus(`? ${currentPlayer} 获胜!`);           gameActive = false;         } else if (isBoardFull()) {           updateStatus('? 平局!');           gameActive = false;         } else {           togglePlayer();           updateStatus(`${currentPlayer} 的回合`);         }       });     });   };    // ✅ PvC 模式初始化(简化版):玩家点击 + 电脑自动落子   const initPvC = () => {     gameActive = true;     updateStatus(`游戏开始!你执 X,先手`);      boxes.forEach(box => {       box.addEventListener('click', function() {         if (!gameActive || this.textContent !== '') return;         this.textContent = 'X';          if (checkWin()) {           updateStatus('? 你获胜!');           gameActive = false;           return;         }         if (isBoardFull()) {           updateStatus('? 平局!');           gameActive = false;           return;         }          // 电脑随机落子(简易 AI)         setTimeout(() => {           const emptyBoxes = Array.from(boxes).filter(b => b.textContent === '');           if (emptyBoxes.length && gameActive) {             const randomBox = emptyBoxes[Math.floor(Math.random() * emptyBoxes.length)];             randomBox.textContent = 'O';             if (checkWin()) {               updateStatus('? 电脑获胜!');               gameActive = false;             }           }         }, 400);       });     });   };    // ✅ 通用胜负判定(提取为共享逻辑)   const winPatterns = [     [0,1,2], [3,4,5], [6,7,8], // 行     [0,3,6], [1,4,7], [2,5,8], // 列     [0,4,8], [2,4,6]           // 对角线   ];   const checkWin = () => {     return winPatterns.some(pattern => {       const [a,b,c] = pattern.map(i => boxes[i].textContent);       return a && a === b && b === c;     });   };   const isBoardFull = () => ![...boxes].some(box => box.textContent === '');    // ✅ 启动逻辑:读取 radio 选择,调用对应初始化函数   startBtn.addEventListener('click', () => {     const selected = document.querySelector('input[name="gameMode"]:checked');     if (!selected) {       updateStatus('⚠️ 请先选择一种游戏模式!');       return;     }      gameMode = selected.value;     resetBoard();      if (gameMode === 'pvp') {       initPvP();     } else if (gameMode === 'pvc') {       initPvC();     }   });    // ? 可选:添加“新游戏”按钮重置逻辑   document.getElementById('newGameBtn')?.addEventListener('click', () => {     resetBoard();     gameActive = false;     updateStatus('请重新选择模式并开始');   }); });

? 关键注意事项

  • 避免嵌套函数污染作用域:pvp() 内部定义的 boxClick 不会被外部访问,应改为在 initPvP() 中直接绑定事件。
  • 确保 DOM 加载完成:使用 DOMContentLoaded 或将

text=ZqhQzanResources