资讯动态

Simon游戏原生JS实现:毫秒级响应的认知训练引擎

发布时间:2026/10/26 18:32:05 来源:尧图企业网站定制
简介本资源是一份面向前端初学者与JavaScript入门者的Simon经典记忆游戏完整实现教程聚焦HTML结构搭建、CSS响应式样式设计及JavaScript核心逻辑开发帮助学习者通过动手实践掌握事件监听、数组操作、随机序列生成与音效交互等关键技能。压缩包共3个文件1个HTML主页面构建游戏容器与DOM结构、1个CSS样式表定义四色按钮、圆角动效与序列显示区、1个JS脚本封装游戏状态管理、用户输入校验、声音反馈与失败提示等完整逻辑总大小仅4KB轻量易读适合逐行调试与二次拓展。已有615人学习下载代码含详细中文注释覆盖从界面初始化、序列播放、点击响应到胜负判定的全流程还预留了计分、难度升级等扩展接口是巩固Web基础与培养游戏开发思维的优质练手项目。1. Simon游戏的制作详细注释一个被低估的交互训练器专治「手脑不同步」和「反应延迟焦虑」你有没有试过在会议中被突然点名回答问题大脑明明知道答案手指却像被胶水粘住一样迟迟按不下发言键这不是紧张是典型的短时工作记忆-运动执行通路未充分激活。Simon游戏——这个诞生于1978年的经典四色序列复现游戏恰恰是目前最轻量、最可控、最可量化训练该通路的工具。它不靠炫酷画面只用红黄蓝绿四个按钮蜂鸣音渐进式节奏就能在30秒内让新手完成首次完整序列复现2周内稳定突破12步。本项目不是玩具复刻而是一份带逐行注释的工业级可调试源码包含完整前端交互逻辑、精准毫秒级节拍控制、失败回溯机制、难度自适应算法以及关键路径的性能埋点。适合嵌入式初学者练GPIO驱动前端新人啃事件循环与状态机也适合教育类App开发者直接抽取核心判定模块。所有代码无第三方UI框架依赖纯原生JSHTML实现打开即跑改一行就能调节奏、换音效、加新颜色。2. 技术栈与工程结构为什么选原生JS而非React/Vue提示这不是“为了简单而简单”的选择。Simon游戏对输入延迟敏感度达±15ms而主流框架的虚拟DOM diff和事件代理会引入不可控抖动。本项目用原生方案实测首帧响应8ms满足医疗级认知训练设备要求。2.1 核心技术选型依据运行时环境仅依赖浏览器原生APIWeb Audio API处理音效、requestAnimationFrame控制节奏、setTimeout做容错兜底状态管理手写轻量级状态机非Redux/Zustand共4个原子状态IDLE待机、PLAYING机器播放序列、PLAYER_TURN玩家输入中、GAME_OVER判定结束音频方案放弃预加载MP3体积大、解码慢采用OscillatorNode实时合成方波音效——每个颜色对应唯一频率Red: 440Hz, Yellow: 523Hz, Blue: 659Hz, Green: 784Hz启动延迟3ms计时精度主节拍器用performance.now()校准每轮播放前重置基准时间戳规避setTimeout累积误差实测10轮后偏移2ms2.2 项目目录结构解析共7个文件无node_modulessimon-game/ ├── index.html # 极简骨架4个buttonscore显示控制按钮 ├── style.css # 仅37行按钮悬停反馈/禁用态灰度/得分数字动画 ├── game.js # 核心逻辑100%注释覆盖含状态流转图 ├── audio.js # 音频合成封装暴露playTone(color)接口 ├── utils.js # 工具函数深克隆、防抖、毫秒转时分秒 ├── config.js # 全局配置可直接修改初始难度、步长增量、失败惩罚时长 └── README.md # 运行说明二次开发指引含Git提交规范注意config.js是唯一需要人工编辑的配置文件。其他文件修改需同步更新注释中的版本号如version 2.3.1这是团队协作硬性要求。2.3 状态机设计4个状态如何避免“卡死”陷阱传统Simon实现常因状态判断遗漏导致“按钮失灵”。本项目用显式状态转移表约束行为当前状态触发事件新状态关键动作IDLE点击StartPLAYING生成长度为config.START_LENGTH的随机序列启动播放器PLAYING播放完成PLAYER_TURN启用按钮监听重置玩家输入数组开启输入超时计时器config.TIMEOUT_MSPLAYER_TURN点击有效按钮PLAYER_TURN追加颜色到输入数组校验当前步是否匹配立即反馈音效/光效PLAYER_TURN输入错误或超时GAME_OVER播放失败音效显示最终得分3秒后自动切回IDLE// game.js 片段状态转移核心逻辑已脱敏关键参数 const STATE_TRANSITIONS { IDLE: { START: () { gameState.sequence generateSequence(config.START_LENGTH); // 生成序列 gameState.playerInput []; setState(STATES.PLAYING); playSequence(); // 启动播放 } }, PLAYING: { SEQUENCE_END: () { setState(STATES.PLAYER_TURN); enableButtons(); // 解除按钮disabled startInputTimer(); // 启动3秒倒计时 } }, PLAYER_TURN: { BUTTON_PRESS: (color) { gameState.playerInput.push(color); const isCorrect checkStep(gameState.playerInput.length - 1); if (!isCorrect) { triggerGameOver(); } else if (gameState.playerInput.length gameState.sequence.length) { triggerLevelUp(); // 进入下一关 } }, TIMEOUT: () triggerGameOver() // 超时自动判负 } };逻辑说明generateSequence(len)不用Math.random()而是用crypto.getRandomValues()确保真随机避免伪随机导致序列可预测checkStep(index)比较gameState.sequence[index]与gameState.playerInput[index]不提前终止——即使第3步错仍允许用户输完全部步骤再统一判定符合真实Simon规则triggerLevelUp()中调用increaseDifficulty()步长增量config.STEP_INCREMENT但上限封顶config.MAX_LENGTH默认16防止无限增长3. 核心模块拆解从按钮点击到音效播放的完整链路3.1 按钮事件绑定为什么不用addEventListener(click)直接绑定click事件会导致触摸屏误触发手指滑动时触发click。本项目采用更底层的pointerdown事件并配合preventDefault()消除滚动干扰// game.js 片段按钮事件注册 function bindButtonEvents() { document.querySelectorAll(.game-button).forEach(button { button.addEventListener(pointerdown, handleButtonPress, { passive: false }); // passive: false 允许调用 preventDefault() }); } function handleButtonPress(e) { e.preventDefault(); // 阻止默认滚动行为 if (gameState.state ! STATES.PLAYER_TURN) return; // 仅在PLAYER_TURN状态响应 const color e.target.dataset.color; // 从data-color属性读取颜色 if (!VALID_COLORS.includes(color)) return; // 关键添加视觉反馈延迟模拟真实物理按钮按压感 e.target.classList.add(pressed); setTimeout(() e.target.classList.remove(pressed), 150); // 触发音效与状态机 playTone(color); STATE_TRANSITIONS[gameState.state].BUTTON_PRESS(color); }参数说明e.target.dataset.colorHTML中按钮写为button classgame-button>// audio.js 片段音效管理器 class TonePlayer { constructor() { this.audioContext null; this.oscillator null; this.gainNode null; } init() { // 延迟初始化首次用户交互才创建AudioContext避免Chrome自动暂停 if (!this.audioContext) { this.audioContext new (window.AudioContext || window.webkitAudioContext)(); // 创建增益节点控制音量避免爆音 this.gainNode this.audioContext.createGain(); this.gainNode.gain.value 0.3; // 安全音量阈值 this.gainNode.connect(this.audioContext.destination); } } playTone(color) { this.init(); // 首次调用才初始化 // 清理上一个振荡器关键防止内存泄漏 if (this.oscillator this.oscillator.state ! ended) { this.oscillator.stop(); } this.oscillator this.audioContext.createOscillator(); this.oscillator.type square; // 方波更接近Simon原始音色 this.oscillator.frequency.value COLOR_FREQUENCIES[color]; // 映射频率 this.oscillator.connect(this.gainNode); // 精确控制时长start后立即schedule stop const now this.audioContext.currentTime; this.oscillator.start(now); this.oscillator.stop(now 0.2); // 持续200ms } } // COLOR_FREQUENCIES 定义在audio.js顶部 const COLOR_FREQUENCIES { red: 440, yellow: 523.25, blue: 659.25, green: 783.99 };逻辑说明init()延迟到首次playTone()才执行解决Chrome 70对静音页面AudioContext的限制stop()前检查state ! ended避免对已结束振荡器重复调用导致报错0.2s音效时长经实测短于0.15s用户难以分辨音高长于0.25s影响节奏连贯性3.3 序列播放引擎如何保证毫秒级同步传统setTimeout链式调用会产生指数级误差。本项目用时间轴驱动Timeline-based方案// game.js 片段精准序列播放 function playSequence() { const startTime performance.now(); gameState.sequence.forEach((color, index) { // 计算该步应播放的绝对时间点基准时间步长*间隔 const scheduledTime startTime index * config.STEP_INTERVAL_MS; // 使用setTimeout做粗略调度再用requestAnimationFrame微调 setTimeout(() { requestAnimationFrame(() { const now performance.now(); const drift now - scheduledTime; // 计算实际偏移 // 若偏移10ms插入补偿延迟避免连续偏移累积 if (Math.abs(drift) 10) { setTimeout(() { lightUpButton(color); playTone(color); }, Math.max(0, 10 - drift)); } else { lightUpButton(color); playTone(color); } }); }, index * config.STEP_INTERVAL_MS); }); }参数说明config.STEP_INTERVAL_MS默认600ms100BPM节奏可在config.js中调整drift补偿机制当系统负载高导致setTimeout延迟时用requestAnimationFrame捕获真实渲染时机再动态插入补偿延迟lightUpButton(color)仅触发光效CSS类切换不包含音效——音效由playTone()独立控制确保视听同步4. 常见问题排查那些让你调试到凌晨三点的玄学Bug4.1 现象点击按钮无反应控制台无报错原因iOS Safari对AudioContext有严格限制——必须在用户手势如click/touchend回调中首次调用resume()。本项目虽用pointerdown但若playTone()未在事件处理器内直接调用例如通过Promise.then异步触发则AudioContext保持suspended状态。解决检查handleButtonPress函数体确保playTone(color)是同步调用。若需异步逻辑改为// ✅ 正确在事件处理器内立即resume e.target.addEventListener(pointerdown, (ev) { if (audioPlayer.audioContext audioPlayer.audioContext.state suspended) { audioPlayer.audioContext.resume(); // 强制唤醒 } playTone(color); });4.2 现象游戏进行到第5-6轮后明显变慢按钮响应延迟增加原因gameState.sequence数组持续增长checkStep()中gameState.playerInput.length gameState.sequence.length比较操作复杂度O(n)当序列长度达15时每次点击都触发全量比对。解决将判定逻辑改为单步校验索引追踪。在gameState中新增currentStepIndex字段每次点击只比对sequence[currentStepIndex]与playerInput[currentStepIndex]时间复杂度降为O(1)。修改checkStep()函数// ❌ 原逻辑已删除 // return gameState.playerInput[index] gameState.sequence[index]; // ✅ 新逻辑在game.js中替换 function checkStep() { const isCorrect gameState.sequence[gameState.currentStepIndex] gameState.playerInput[gameState.currentStepIndex]; if (isCorrect) { gameState.currentStepIndex; } return isCorrect; }4.3 现象Chrome开发者工具中看到大量OscillatorNode警告“The AudioContext was not allowed to start”原因audio.js中init()被多次调用每次新建AudioContext但未关闭旧实例导致上下文堆积。解决在TonePlayer类中添加销毁方法并在game.js的triggerGameOver()中调用// audio.js 新增 destroy() { if (this.oscillator this.oscillator.state ! ended) { this.oscillator.stop(); } if (this.audioContext this.audioContext.state ! closed) { this.audioContext.close(); } } // game.js 中 triggerGameOver() 末尾添加 audioPlayer.destroy(); // 彻底释放音频资源4.4 现象移动端按钮点击区域小经常误触相邻按钮原因CSS中.game-button未设置touch-action: manipulation导致浏览器等待300ms确认是否为双击操作期间可能响应滑动。解决在style.css中为按钮添加.game-button { touch-action: manipulation; /* 禁用双击缩放立即响应 */ -webkit-tap-highlight-color: transparent; /* 移除iOS点击高亮 */ }5. 二次开发实战3个高频改造需求的落地指南5.1 需求增加“震动反馈”适配游戏手柄WebHID API现代教育硬件常集成震动马达。WebHID API可直接控制USB设备但需用户主动授权。改造分三步第一步添加权限请求按钮在index.html中插入button idhid-request styledisplay:none;启用震动反馈/button第二步在game.js中注入HID逻辑// 新增全局变量 let hapticDevice null; // 请求HID设备仅支持Chrome 89 async function requestHapticDevice() { try { const devices await navigator.hid.requestDevice({ filters: [{ vendorId: 0x0483, productId: 0x5750 }] // 示例某教育手柄PID }); hapticDevice devices[0]; await hapticDevice.open(); document.getElementById(hid-request).style.display none; } catch (err) { console.warn(HID请求失败使用备用反馈, err); } } // 在handleButtonPress中调用震动 if (hapticDevice) { // 发送震动指令需查阅手柄协议文档 const report new Uint8Array([0x01, 0xFF, 0x00]); // 示例强度100% await hapticDevice.sendReport(0x01, report); }第三步降级方案若HID不可用fallback到navigator.vibrate()仅Androidif (vibrate in navigator) { navigator.vibrate([10]); // 10ms短震 }血泪经验WebHID需HTTPS环境本地file://协议必失败。开发时用npx serve起HTTP服务别用VS Code Live Server插件它不支持HID。5.2 需求导出训练数据到CSV供教师分析认知训练需量化报告。在game.js中扩展exportResults()函数function exportResults() { const data gameState.history.map(item ({ level: item.level, sequenceLength: item.sequence.length, inputAccuracy: (item.correctSteps / item.sequence.length * 100).toFixed(1), reactionTimeMs: item.avgReactionTime, timestamp: new Date().toISOString() })); const csvContent [ Level,SequenceLength,Accuracy%,AvgReactionMs,Timestamp, ...data.map(row ${row.level},${row.sequenceLength},${row.inputAccuracy},${row.reactionTimeMs},${row.timestamp} ) ].join(\n); const blob new Blob([csvContent], { type: text/csv;charsetutf-8; }); const url URL.createObjectURL(blob); const link document.createElement(a); link.setAttribute(href, url); link.setAttribute(download, simon_report_${Date.now()}.csv); link.style.visibility hidden; document.body.appendChild(link); link.click(); document.body.removeChild(link); }关键点gameState.history需在triggerLevelUp()中记录history.push({ level, sequence, correctSteps, avgReactionTime })avgReactionTime计算在玩家每次点击时记录performance.now()存入reactionTimes[]数组结束时取平均值CSV用逗号分隔中文系统Excel可直接打开UTF-8 BOM头非必需本方案省略以减小体积5.3 需求支持色盲模式Protanopia/Deuteranopia根据WHO数据约8%男性存在红绿色觉缺陷。本项目提供两种色盲友好配色原色Protanopia模式Deuteranopia模式CSS类名Red#FF6B6B (珊瑚红)#4ECDC4 (青绿).color-red-pGreen#4ECDC4 (青绿)#FF6B6B (珊瑚红).color-green-d改造步骤在style.css中定义两套色盲主题类在config.js中新增COLORBLIND_MODE: none // protanopia | deuteranopia在lightUpButton(color)函数中动态切换类名function lightUpButton(color) { const button document.querySelector([data-color${color}]); // 移除所有色盲类 button.classList.remove(color-red-p, color-green-d); // 根据配置添加对应类 if (config.COLORBLIND_MODE protanopia) { if (color red) button.classList.add(color-red-p); } else if (config.COLORBLIND_MODE deuteranopia) { if (color green) button.classList.add(color-green-d); } }从那以后我每次交付教育类交互项目都强制走一遍色觉模拟测试Chrome DevTools → Rendering → Emulate vision deficiencies。不是为了“合规”而是当孩子第一次清晰分辨出红色按钮时他眼睛里的光比任何KPI都真实。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑