0

0

如何基于单选按钮选择动态执行不同游戏逻辑

心靈之曲

心靈之曲

发布时间:2026-02-11 17:06:27

|

826人浏览过

|

来源于php中文网

原创

如何基于单选按钮选择动态执行不同游戏逻辑

本文详解 javascript 中单选按钮(radio)值驱动函数执行的核心机制,重点解决因作用域、函数声明时机与调用时序不当导致的“函数未定义”问题,并提供可立即落地的模块化架构方案。

在开发如井字棋(Tic-Tac-Toe)这类支持多种对战模式(玩家 vs 玩家 / 玩家 vs 电脑)的游戏时,一个常见误区是:将核心游戏逻辑(如 pvp())封装为内部定义的嵌套函数,再试图在事件回调中直接调用它——这会导致 ReferenceError: pvp is not defined。根本原因在于:嵌套函数仅在其父作用域内可见,且不会被提升(hoisted);若未显式调用其外层函数,内部函数根本不会被声明到当前作用域中。

回顾原代码中的关键问题:

  • pvp() 函数体被完整包裹在顶层作用域中,但它内部定义的 boxClick、updateBox、isWinner 等函数全部是局部变量,仅在 pvp() 执行时才创建;
  • init() 在页面加载时立即执行,此时 game 变量仍为 "no game radio button",而 pvp() 从未被调用,因此其内部函数根本不存在;
  • 后续点击棋盘时尝试触发 boxClick,但该函数尚未声明 → 报错 undefined。

✅ 正确解法:分离「配置」与「执行」,采用工厂函数 + 事件驱动初始化模式

MediSearch
MediSearch

Medisearch是一个AI驱动的医疗健康搜索引擎,旨在根据可信来源提供医学问题的直接答案

下载

以下是一个结构清晰、可维护性强的重构方案:

✅ 推荐架构:模块化游戏控制器

// 1. 定义通用游戏状态(全局或模块级)
const gameState = {
  gameMode: 'none', // 'pvp' | 'pvc' | 'none'
  board: ['', '', '', '', '', '', '', '', ''],
  currentPlayer: 'x',
  running: false,
  timerCounter: 0
};

// 2. 工厂函数:返回特定模式的游戏逻辑对象
const GameModes = {
  pvp: () => ({
    onBoxClick: (index) => {
      if (!gameState.running || gameState.board[index] !== '') return;
      gameState.board[index] = gameState.currentPlayer;
      updateBoardUI();
      if (checkWin()) {
        endGame(`Player ${gameState.currentPlayer} wins!`);
      } else if (gameState.board.every(cell => cell !== '')) {
        endGame('Game tied!');
      } else {
        gameState.currentPlayer = gameState.currentPlayer === 'x' ? 'o' : 'x';
        updateStatus(`${gameState.currentPlayer}'s turn`);
      }
    },
    onStart: () => {
      console.log('PvP mode activated');
      startTimer(); // 可复用的计时器
      gameState.running = true;
      updateStatus(`${gameState.currentPlayer}'s turn`);
    }
  }),

  pvc: () => ({
    onBoxClick: (index) => {
      // 此处添加 AI 决策逻辑(如 minimax)
      if (!gameState.running || gameState.board[index] !== '') return;
      gameState.board[index] = 'x';
      updateBoardUI();
      if (checkWin()) return endGame('You win!');
      if (gameState.board.every(cell => cell !== '')) return endGame('Tied!');

      // 模拟 AI 落子(简化版)
      setTimeout(() => {
        const emptyIndices = gameState.board
          .map((cell, i) => cell === '' ? i : -1)
          .filter(i => i !== -1);
        if (emptyIndices.length > 0) {
          const aiMove = emptyIndices[Math.floor(Math.random() * emptyIndices.length)];
          gameState.board[aiMove] = 'o';
          updateBoardUI();
          if (checkWin()) endGame('Computer wins!');
        }
      }, 500);
    },
    onStart: () => {
      console.log('PvC mode activated');
      startTimer();
      gameState.running = true;
      updateStatus("Your turn (X)");
    }
  })
};

// 3. 统一事件绑定与初始化入口
document.getElementById('start').addEventListener('click', () => {
  const pvpRadio = document.getElementById('pvp');
  const pvcRadio = document.getElementById('pvc');

  if (pvpRadio.checked) {
    gameState.gameMode = 'pvp';
    document.getElementById('messageGame').textContent = 'Player vs Player Game!';
  } else if (pvcRadio.checked) {
    gameState.gameMode = 'pvc';
    document.getElementById('messageGame').textContent = 'Player vs Computer Game!';
  } else {
    alert('Please select a game mode first!');
    return;
  }

  // ✅ 关键:获取当前模式的逻辑实例,并绑定事件
  const modeLogic = GameModes[gameState.gameMode]();

  // 清空并重新绑定棋盘点击事件
  document.querySelectorAll('.box').forEach((box, index) => {
    box.onclick = () => modeLogic.onBoxClick(index);
  });

  // 启动该模式专属初始化流程
  modeLogic.onStart();
});

// 4. 复用型辅助函数(脱离具体模式)
function updateBoardUI() {
  document.querySelectorAll('.box').forEach((box, i) => {
    box.innerHTML = gameState.board[i] || '';
  });
}

function updateStatus(text) {
  document.getElementById('status').textContent = text;
}

function endGame(message) {
  gameState.running = false;
  stopTimer();
  updateStatus(message);
}

function startTimer() {
  gameState.timerCounter = 0;
  document.getElementById('timeClock').textContent = '0 seconds';
  const tick = () => {
    gameState.timerCounter++;
    document.getElementById('timeClock').textContent = `${gameState.timerCounter} seconds`;
    if (gameState.running) setTimeout(tick, 1000);
  };
  tick();
}

function stopTimer() {
  // 无须 clearTimeout —— 我们通过 running 标志控制递归
}

⚠️ 关键注意事项

  • 禁止嵌套函数作为逻辑入口:pvp() 不应是“容器函数”,而应是返回逻辑对象的工厂函数;
  • 事件监听必须在用户确认模式后动态绑定:避免提前绑定未定义的处理函数;
  • 状态统一管理:使用 gameState 对象集中维护跨模式共享数据(如 board、running),避免闭包污染;
  • HTML 结构优化建议:将 onclick="startCount()" 从 HTML 移至 JS 中绑定,实现关注点分离;
  • 调试技巧:在 start 点击事件中加入 console.log({gameState, modeLogic}),快速验证模式是否正确加载。

✅ 总结

单选按钮驱动函数执行的本质,不是“条件调用某个嵌套函数”,而是根据用户输入动态装配一套行为契约(即事件处理器)。通过工厂函数生成模式专属逻辑、统一状态管理、延迟绑定事件,即可彻底规避作用域与提升陷阱,构建出可扩展、易测试、符合现代前端工程规范的游戏架构。

在线游戏
在线游戏

海量精品小游戏合集,无需安装即点即玩,休闲益智、动作闯关应有尽有,秒开即玩,轻松解压,快乐停不下来

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言闭包相关教程大全
go语言闭包相关教程大全

本专题整合了go语言闭包相关数据,阅读专题下面的文章了解更多相关内容。

140

2025.07.29

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

520

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

350

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

488

2023.08.03

js是什么意思
js是什么意思

JS是JavaScript的缩写,它是一种广泛应用于网页开发的脚本语言。JavaScript是一种解释性的、基于对象和事件驱动的编程语言,通常用于为网页增加交互性和动态性。它可以在网页上实现复杂的功能和效果,如表单验证、页面元素操作、动画效果、数据交互等。

5555

2023.08.17

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

487

2023.09.01

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

214

2023.09.04

Js中concat和push的区别
Js中concat和push的区别

Js中concat和push的区别:1、concat用于将两个或多个数组合并成一个新数组,并返回这个新数组,而push用于向数组的末尾添加一个或多个元素,并返回修改后的数组的新长度;2、concat不会修改原始数组,是创建新的数组,而push会修改原数组,将新元素添加到原数组的末尾等等。本专题为大家提供concat和push相关的文章、下载、课程内容,供大家免费下载体验。

239

2023.09.14

Rust异步编程与Tokio运行时实战
Rust异步编程与Tokio运行时实战

本专题聚焦 Rust 语言的异步编程模型,深入讲解 async/await 机制与 Tokio 运行时的核心原理。内容包括异步任务调度、Future 执行模型、并发安全、网络 IO 编程以及高并发场景下的性能优化。通过实战示例,帮助开发者使用 Rust 构建高性能、低延迟的后端服务与网络应用。

1

2026.02.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号