首页
/ Web-Dev-For-Beginners 键盘游戏挑战:基于事件驱动编程设计并实现你自己的键盘游戏

Web-Dev-For-Beginners 键盘游戏挑战:基于事件驱动编程设计并实现你自己的键盘游戏

2026-09-06 12:15:41作者:沈韬淼Beryl

本文是 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 改速度向量,游戏主循环(如 setIntervalrequestAnimationFrame)驱动移动与碰撞判定;
  • 音乐合成器(Music Synthesizer):不同键触发不同音符或音效。典型映射:keydown 发声,keyup 停音(实现音符的自然衰减与连击手感);
  • 极速打字变体(Speed Typing Variants):分类专属打字,如编程术语、外语词汇。可在父课程打字游戏的 quotes 数组(见 4-typing-game/solution/index.js 第 4–12 行)基础上换词库并新增“连对 N 词升难度”的进度机制;
  • 键盘鼓手(Keyboard Drummer):把键位映射到不同鼓声,打出节奏。典型映射:keydown 触发对应鼓点采样并高亮对应打击垫,keyup 取消高亮。

选哪个不重要,重要的是选定后按“事件 → 状态 → 渲染”三步走:先列出需要监听的键与事件,再画出游戏状态,最后确定每次状态变化要在界面上刷什么。

五条实现指南:作业原文的开发方法论

作业给出的实现指南(Implementation guidelines)完整保留如下,并逐条补充落地做法:

  1. Start(从简单概念起步):从最简单的概念开始,逐步增加复杂度。对应父课程“5 分钟 / 1 小时 / 一周”的渐进式开发节奏(见 4-typing-game/typing-game/README.md):先让按键能改变界面,再加判定,最后加分数与动画。
  2. Focus(专注流畅自然的操控):打磨平滑、跟手的操作手感。纯键盘游戏的体验核心在事件回调要轻——参考实现的事件处理器 handleTyping 内没有任何异步或重计算,只做了字符串比较、状态自增和 class 切换,这是“手感流畅”的工程保障。
  3. Include(明确的状态与进度指示):为游戏状态和玩家进度提供清晰的视觉指示。对照参考实现:高亮词 = 进度指示,#message 区 = 状态指示,两者分别绑定不同 DOM 节点,互不干扰。
  4. Test(让不同的人试玩):用不同用户测试游戏,确保玩法直观。自测清单可参考父教程“Test your application”一节的验证项:点击 Start 是否出现新内容、高亮是否正确前进、错误样式是否出现、完成时是否显示成绩消息。
  5. 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.htmlindex.cssindex.js 三个文件组成。说明:父教程正文建议的项目文件名为 index.html / script.js / style.css,而 solution 目录实际使用 index.js / index.css,两者结构等价,你自己的项目任选其一保持一致即可。

状态与 UI 引用集中在文件头部

4-typing-game/solution/index.js 前 23 行完成了两件事:声明可变状态(wordswordIndexstartTime)和缓存 UI 元素引用(quoteElementmessageElementtypedValueElementstartButton)。这正是父教程“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> 列表一次性写入 innerHTMLhighlightWord 负责后续的高亮迁移。事件层(startGamehandleTyping)只改状态并调用这些渲染函数。对“至少 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 常量集中管理;
  • 创意:能一句话说出你的游戏与“普通打字游戏/贪吃蛇”的差异点。

延伸阅读与课程资源

掌握这套“监听键盘事件 → 维护游戏状态 → 切换 class/画布渲染”的闭环后,你面对的就不只是小游戏——任何响应键盘的交互应用(快捷键面板、代码编辑器、终端式 UI)都是同一个模式的放大版。

登录后查看全文
热门项目推荐
相关项目推荐