Web-Dev-For-Beginners 键盘游戏挑战:基于事件驱动编程设计并实现你自己的键盘游戏
本文是 Web-Dev-For-Beginners 课程“第 4 课:打字游戏”模块的进阶作业指南。在完成父课程的事件驱动编程打字游戏之后,本作业要求你独立设计并实现一款自己的键盘小游戏,围绕“事件监听、视觉反馈、游戏逻辑、用户界面”四项硬性要求展开。读完后,你将掌握如何把事件驱动编程的通用模式(监听 → 状态更新 → DOM 渲染)迁移到任意键盘交互应用中,并能对照课程给出的 Rubric 自评作品质量。
下图是课程配套打字游戏(本作业的基础原型)的运行演示:
任务定位:从“照着做”到“自己造”
作业原文(assignment.md)开篇即点明目标:在已经通过打字游戏掌握事件驱动编程(event-driven programming)基础之后,要“释放你的创造力”——设计并构建一款自己的键盘类游戏,用以证明你真正理解了事件处理(event handling)、DOM 操作(DOM manipulation)和用户交互模式。
游戏形式的自由度很高,原文列举了三种示例方向:
- 一种不同类型的打字游戏;
- 一款“击键作画”的艺术应用:每次按键在屏幕上画出一个像素;
- 一款用方向键操控的简易街机游戏;
- 或者任何其他你能想到的创意概念——关键是思考“不同的键如何触发不同的行为”。
这一定位与课程主线一脉相承。父课程 4-typing-game/README.md 将其概括为“事件驱动编程——构建打字游戏”,其核心流程是:玩家开始 → 显示随机引语 → 逐字符输入 → 判定对错 → 绿/红高亮 → 计算成绩 → 展示结果。而本作业正是要求你把这条“事件 → 状态 → 反馈”的链路从打字游戏迁移到全新的游戏场景中。
四项硬性需求:你的游戏必须包含什么
作业以一张需求表(Requirements)给出验收标准,四项要求缺一不可。下表完整继承自作业原文,并补充了参考实现中对应的技术对照点:
| 需求 | 说明 | 目的 |
|---|---|---|
| 事件监听(Event Listeners) | 至少响应 3 种不同的键盘事件 | 证明你理解事件处理机制 |
| 视觉反馈(Visual Feedback) | 对每次用户输入给出即时的视觉响应 | 展示 DOM 操作功底 |
| 游戏逻辑(Game Logic) | 包含计分、等级或进度机制 | 练习应用状态(application state)的实现 |
| 用户界面(User Interface) | 清晰的说明与直观的操控方式 | 培养用户体验设计能力 |
事件监听:至少 3 种不同的键盘事件
这是四项要求中最容易误解的一条。注意它要求的是 3 种不同的键盘事件,而不是 3 个按键。从源码结构看,参考实现 4-typing-game/solution/index.js 注册了两个监听器(第 89–91 行):
// Event Listeners
startButton.addEventListener("click", startGame);
typedValueElement.addEventListener("input", handleTyping);
参考实现用的是 click(鼠标事件)+ input(文本框输入事件),因为它是“通过文本框打字”的游戏。而作业要求的是纯键盘游戏,你需要把监听对象换成 document(或 window),监听标准 DOM 键盘事件,例如:
keydown:某个键被按下(持续按住会连续触发);keyup:某个键被释放;- 结合方向键、空格、字母键等,通过事件对象上的
key/code属性区分不同按键,即可轻松满足“至少 3 种不同键盘事件”的要求。
父课程教程 4-typing-game/typing-game/README.md 中特别强调:浏览器暴露了数十种事件,用户几乎在页面上的任何动作都会触发事件;而创建监听器有多种写法(匿名函数、具名函数、addEventListener() 等),课程统一采用 addEventListener() + 匿名函数的组合,因为它最通用、最灵活,事件名可以作为参数传入。你在自己的键盘游戏中也应沿用这一惯例。
视觉反馈:让每次击键“看得见”
作业要求“对用户输入提供即时的视觉响应”,目的是考察你对 DOM 操作的掌握。这一点在参考实现里有非常干净的范式可抄。其 4-typing-game/solution/index.css(第 1–8 行)只定义了两个反馈类:
.highlight {
background-color: yellow;
}
.error {
background-color: lightcoral;
border-color: red;
}
配合 4-typing-game/solution/index.js 第 43–48 行的 highlightWord(),每次按键后只需切换 class 就能完成高亮迁移:
// Utility: highlight current word
const highlightWord = (index) => {
[...quoteElement.children].forEach((el, i) => {
el.classList.toggle("highlight", i === index);
});
};
“CSS class 定义外观 + JS 动态切换 class”是视觉反馈最经典也最高效的模式:样式与逻辑解耦,事件处理器里只做状态变更和 class 切换,不做任何内联样式计算。你的键盘游戏(无论是像素画、贪吃蛇还是节奏游戏)都可以沿用这套模式——像素画用 canvas 或网格 div 上色,贪吃蛇用坐标数组驱动单元格 class,本质上都是“输入事件 → 改状态 → 刷 class/画布”。
游戏逻辑:计分、等级与进度
第三项要求你实现“应用状态(application state)”。参考实现展示了游戏状态的最小闭环:4-typing-game/solution/index.js 用三个顶层变量承载全部状态(第 14–17 行):
// State
let words = [];
let wordIndex = 0;
let startTime = 0;
words/wordIndex:记录“正在打哪个词”,是进度状态;startTime:记录开局时间,第 69–72 行用它算出耗时并生成成绩消息:
if (typedValue === currentWord && wordIndex === words.length - 1) {
// Game finished
const elapsedTime = (Date.now() - startTime) / 1000;
messageElement.textContent = messages.success(elapsedTime);
typedValueElement.disabled = true;
}
对你的键盘游戏而言,状态对象会稍有不同——可能是 { score, level, lives }、蛇身坐标数组、或像素画布二维数组——但模式一致:用一个清晰命名的状态容器,在每个事件回调里更新它,再触发一次渲染。作业原文对这一条的措辞是“计分、等级或进度机制(scoring, levels, or progression mechanics)”,三者实现其一即可满足底线,全实现则对应 Rubric 的“Exemplary”档。
用户界面:说明、控件与无障碍
第四项要求关注的是“清晰的说明和直观的操控”。参考实现的页面结构(4-typing-game/solution/index.html 第 42–61 行)给出了课程认可的 UI 基线:
<h1>
<i class="fas fa-keyboard"></i> Practice your typing
</h1>
<div>
<i class="fas fa-info-circle"></i>
Click start to have a quote displayed. Type the quote as fast as you can!
</div>
<p id="quote"></p>
<p id="message"></p>
<div>
<label for="typed-value" class="hidden-label">Current word</label>
<input type="text" aria-label="current word" id="typed-value" placeholder="Type here..." />
</div>
<div>
<button id="start" type="button">
<i class="fas fa-play"></i> Start
</button>
</div>
值得注意的细节有两处:一是输入框同时挂了 aria-label="current word" 和一个视觉隐藏的 <label>,保证屏幕阅读器也能识别输入框用途;二是引用展示区、状态消息区、输入区、按钮区各司其职,每个关键元素都有稳定的 id 供 JavaScript 精确选取(参考实现用 document.querySelector("#quote") 等获取,见 4-typing-game/solution/index.js 第 19–23 行)。你的键盘游戏同样应做到:开局前玩家就知道“哪些键做什么”、游戏进行中随时能看到得分/进度、结束后有明确的成绩与重开方式。
七个创意方向及事件拆解
作业原文给出了 7 个创意项目方向,这里完整保留,并补充“每个方向可以用哪些键盘事件来驱动”的拆解思路,帮你把点子落成 keydown 处理函数里的分支:
- 节奏游戏(Rhythm Game):玩家按音乐或视觉提示的节奏按键。典型事件映射:
keydown判定命中的音轨,keyup可用于长按音符的释放判定; - 像素画创作(Pixel Art Creator):不同键绘制不同颜色或图案。典型映射:方向键移动光标(
keydown),字母键选色(keydown),空格/回车确认落笔; - 单词构建器(Word Builder):按特定顺序输入字母组成单词。典型映射:字母键
keydown逐字符比对,可加Backspace事件支持撤销; - 贪吃蛇(Snake Game):用方向键控制蛇移动、收集物品。典型映射:四个方向键
keydown改速度向量,游戏主循环(如setInterval或requestAnimationFrame)驱动移动与碰撞判定; - 音乐合成器(Music Synthesizer):不同键触发不同音符或音效。典型映射:
keydown发声,keyup停音(实现音符的自然衰减与连击手感); - 极速打字变体(Speed Typing Variants):分类专属打字,如编程术语、外语词汇。可在父课程打字游戏的
quotes数组(见 4-typing-game/solution/index.js 第 4–12 行)基础上换词库并新增“连对 N 词升难度”的进度机制; - 键盘鼓手(Keyboard Drummer):把键位映射到不同鼓声,打出节奏。典型映射:
keydown触发对应鼓点采样并高亮对应打击垫,keyup取消高亮。
选哪个不重要,重要的是选定后按“事件 → 状态 → 渲染”三步走:先列出需要监听的键与事件,再画出游戏状态,最后确定每次状态变化要在界面上刷什么。
五条实现指南:作业原文的开发方法论
作业给出的实现指南(Implementation guidelines)完整保留如下,并逐条补充落地做法:
- Start(从简单概念起步):从最简单的概念开始,逐步增加复杂度。对应父课程“5 分钟 / 1 小时 / 一周”的渐进式开发节奏(见 4-typing-game/typing-game/README.md):先让按键能改变界面,再加判定,最后加分数与动画。
- Focus(专注流畅自然的操控):打磨平滑、跟手的操作手感。纯键盘游戏的体验核心在事件回调要轻——参考实现的事件处理器 handleTyping 内没有任何异步或重计算,只做了字符串比较、状态自增和 class 切换,这是“手感流畅”的工程保障。
- Include(明确的状态与进度指示):为游戏状态和玩家进度提供清晰的视觉指示。对照参考实现:高亮词 = 进度指示,
#message区 = 状态指示,两者分别绑定不同 DOM 节点,互不干扰。 - Test(让不同的人试玩):用不同用户测试游戏,确保玩法直观。自测清单可参考父教程“Test your application”一节的验证项:点击 Start 是否出现新内容、高亮是否正确前进、错误样式是否出现、完成时是否显示成绩消息。
- Document(注释你的事件处理策略):用注释解释你的事件处理策略。注释重点应放在“为什么监听这个事件、这个分支为什么用
keydown而不是keyup、状态在什么时机更新”,而不是复述代码字面意思。
评分标准(Rubric):四级自评对照表
作业给出了完整的四维度 Rubric,各维度分“优秀 / 合格 / 待改进”三档(外加维度描述)。以下为完整继承原文的评分表:
| 维度 | 优秀(Exemplary) | 合格(Adequate) | 待改进(Needs Improvement) |
|---|---|---|---|
| 功能完整性(Functionality) | 完整、打磨到位的游戏,功能丰富、操作流畅 | 可运行的游戏,具备演示键盘事件处理的基础功能 | 功能有限的极简实现,或存在明显 bug |
| 代码质量(Code Quality) | 组织良好、有注释、遵循最佳实践,事件处理高效 | 代码干净可读,合理使用事件监听与 DOM 操作 | 代码结构基础,存在组织问题或低效实现 |
| 用户体验(User Experience) | 操控直观、反馈清晰、玩法有吸引力,达到专业水准 | 界面功能完备,有适度引导,操控响应及时 | 界面基础,说明不清或响应不佳 |
| 创意(Creativity) | 原创概念,对键盘事件的运用有新意,解决问题方式巧妙 | 对常见游戏模式做有趣变体,事件处理运用得当 | 基本概念的简单实现,创意元素极少 |
对照参考实现,可以直观感受各档差异:4-typing-game/solution/index.js 的写法(工具函数 getRandomQuote / renderQuote / highlightWord 各司其职、messages 对象集中管理文案、classList.toggle 而非字符串拼接 class)大致对应“优秀/合格”档的“代码质量”要求;若把事件处理直接堆进一个上百行的回调、无任何注释,则落入“待改进”档。
参考实现源码走读:把作业要求对到每一段代码
参考实现位于 4-typing-game/solution/,由 index.html、index.css 和 index.js 三个文件组成。说明:父教程正文建议的项目文件名为 index.html / script.js / style.css,而 solution 目录实际使用 index.js / index.css,两者结构等价,你自己的项目任选其一保持一致即可。
状态与 UI 引用集中在文件头部
4-typing-game/solution/index.js 前 23 行完成了两件事:声明可变状态(words、wordIndex、startTime)和缓存 UI 元素引用(quoteElement、messageElement、typedValueElement、startButton)。这正是父教程“Add the constants”一节强调的做法——用常量化引用避免字符串 ID 拼写错误、便于 IDE 补全。你的键盘游戏也应如此:const keys = { up: "ArrowUp", ... } 这类常量放在文件顶部,事件回调里只引用常量。
渲染层与事件层分离
第 33–48 行是纯渲染层,不感知任何事件:
// Utility: pick random quote
const getRandomQuote = () => quotes[Math.floor(Math.random() * quotes.length)];
// Utility: render quote as spans
const renderQuote = (quote) => {
quoteElement.innerHTML = quote
.split(" ")
.map((word, i) => `<span ${i === 0 ? 'class="highlight"' : ""}>${word}</span>`)
.join(" ");
};
renderQuote 把引语拆词后包成带初始高亮的 <span> 列表一次性写入 innerHTML;highlightWord 负责后续的高亮迁移。事件层(startGame、handleTyping)只改状态并调用这些渲染函数。对“至少 3 种键盘事件”的游戏尤其重要:多个 keydown 分支(方向键、空格、回车……)最终都应汇入同一个“更新状态 → 调用 render”的出口,避免每个分支各自操作 DOM。
输入判定:四级瀑布分支
handleTyping 展示了课程强调的“瀑布式判定”(waterfall approach),从最具体的情况到最一般的情况依次检查:
const handleTyping = () => {
const currentWord = words[wordIndex];
const typedValue = typedValueElement.value;
if (typedValue === currentWord && wordIndex === words.length - 1) {
// Game finished
const elapsedTime = (Date.now() - startTime) / 1000;
messageElement.textContent = messages.success(elapsedTime);
typedValueElement.disabled = true;
} else if (typedValue.endsWith(" ") && typedValue.trim() === currentWord) {
// Word completed
typedValueElement.value = "";
wordIndex++;
highlightWord(wordIndex);
} else if (currentWord.startsWith(typedValue)) {
// Correct so far
typedValueElement.classList.remove("error");
} else {
// Error
typedValueElement.classList.add("error");
messageElement.textContent = messages.error;
}
};
四个分支分别是:整句完成(算时并禁用输入框)、单词完成(清空输入、wordIndex++、迁移高亮)、当前正确(移除错误样式)、出错(加 error class 并提示)。注意第一分支里的 typedValueElement.disabled = true——完成后禁用输入框,这是父教程“Challenge”部分要求的“UI 状态管理”能力的体现。你的键盘游戏同理需要一个“游戏结束态”:停止响应游戏按键(或只保留重开键),并展示最终成绩。
一个可直接套用的键盘游戏模板
综合作业四项要求与参考实现的模式,下面是一个最小可行的键盘游戏骨架(三文件结构,与父课程一致)。它不是仓库中的现成文件,而是基于参考实现提炼出的起步模板:
<!-- index.html -->
<html lang="en">
<head>
<title>My Keyboard Game</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>My Keyboard Game</h1>
<p>Controls: Arrow keys to move, Space to act, Enter to restart.</p> <!-- UI 要求:清晰说明 -->
<p id="score">Score: 0</p> <!-- 游戏逻辑要求:计分 -->
<div id="board"></div>
<script src="script.js"></script>
</body>
</html>
// script.js
// ---- 状态(游戏逻辑要求)----
let score = 0;
let level = 1;
let gameOver = false;
// ---- UI 引用 ----
const scoreElement = document.getElementById("score");
const board = document.getElementById("board");
// ---- 渲染层:状态变化时统一调用 ----
const render = () => {
scoreElement.textContent = `Score: ${score}`;
// 依据 score/level 更新 board 上的高亮 class(视觉反馈要求)
};
// ---- 事件层:至少 3 种不同键盘事件(事件监听要求)----
document.addEventListener("keydown", (e) => {
if (gameOver && e.key !== "Enter") return;
switch (e.key) {
case "ArrowUp": /* 更新状态后调用 render() */ break;
case "ArrowDown": break;
case " ": /* 空格:得分 + 升级判定 */ score++; level = Math.floor(score / 10) + 1; break;
case "Enter": /* 重开:重置状态 */ score = 0; level = 1; gameOver = false; break;
}
render();
});
document.addEventListener("keyup", (e) => {
// 例如:释放键时取消某高亮(第二种键盘事件)
});
window.addEventListener("keydown", (e) => {
// 可再结合第三个事件来源(如聚焦检测)构成第三种事件
});
render();
模板刻意保留了参考实现的三个结构特征:状态与 UI 引用置顶、渲染函数独立、事件回调只做“改状态 + 调 render”。把它扩展成像素画、贪吃蛇或节奏游戏,只是替换 render() 的画法和分支内容。
交付前自查清单
结合 Rubric 与作业要求,提交前按以下顺序过一遍(全部对应仓库内证据,可逐一验证):
- 事件监听:确认至少 3 种不同的键盘事件(
keydown/keyup等)通过addEventListener注册,而不是散落的内联onkeydown; - 视觉反馈:确认每次输入后 1 帧内能看到 class 切换或重绘,样式类集中在 CSS 文件(参照 4-typing-game/solution/index.css 的做法);
- 游戏逻辑:确认存在明确的状态变量(分数/等级/进度)与结束态处理(参照
disabled = true的做法); - 用户界面:确认页面有按键说明、有成绩显示、关键元素带
aria-label或<label>(参照 4-typing-game/solution/index.html); - 代码质量:事件处理函数有注释说明策略,无巨型回调,ID 常量集中管理;
- 创意:能一句话说出你的游戏与“普通打字游戏/贪吃蛇”的差异点。
延伸阅读与课程资源
- 父课程总览:4-typing-game/README.md(事件驱动编程与打字游戏的完整学习路线图);
- 打字游戏分步教程:4-typing-game/typing-game/README.md(含常见事件表、
addEventListener用法说明与逐段代码讲解); - 完整参考实现:4-typing-game/solution/index.js、4-typing-game/solution/index.html、4-typing-game/solution/index.css;
- 本作业的中文译本:4-typing-game/typing-game/translations/assignment.zh-cn.md,其余语言译本见 4-typing-game/typing-game/translations/ 目录。
掌握这套“监听键盘事件 → 维护游戏状态 → 切换 class/画布渲染”的闭环后,你面对的就不只是小游戏——任何响应键盘的交互应用(快捷键面板、代码编辑器、终端式 UI)都是同一个模式的放大版。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
