接接乐 Code Explanation

This page turns the game into a bilingual learning module. Each section shows Chinese and English side by side, so students can compare the same idea in both languages.

Back to Game
What students should learn / 学习目标
  • How HTML, CSS, and JavaScript cooperate in one file / HTML、CSS 和 JavaScript 如何协作。
  • Why the game loop uses requestAnimationFrame / 为什么使用 requestAnimationFrame
  • How DOM elements move, collide, and disappear / DOM 元素如何移动、碰撞和消失。
  • How the audio controller creates background music / 音频控制器如何生成背景音乐。

Each topic below is shown in Chinese on the left and English on the right.

中文

页面是怎么组织的

这个游戏是一个单文件 HTML 程序。HTML 负责页面结构,CSS 负责外观,JavaScript 负责游戏状态、 动画、存储和音频。

像浏览器一样思考 浏览器先读 HTML,再应用 CSS,最后执行 JavaScript。JavaScript 不是替代浏览器,而是告诉浏览器 该显示什么、什么时候改变。
English

How the page is organized

The game is a single HTML file with three jobs: the markup creates the pages, the CSS paints the interface, and the JavaScript controls state, animation, storage, and audio.

Think like a browser The browser reads the HTML, paints the CSS, and then runs the JavaScript. The JavaScript does not replace the browser; it tells the browser what to show and when to change it.
中文

核心数据

  • pages 保存开始、设置和游戏页面。
  • foods 保存当前屏幕上的所有下落物体。
  • heartsgoodCountbadCount 保存分数状态。
  • music 负责启动和停止 Web Audio 音乐循环。
English

Core building blocks

  • pages stores the start, setup, and game sections.
  • foods stores every falling object currently on screen.
  • hearts, goodCount, and badCount store score state.
  • music starts and stops the Web Audio loop.
中文

HTML 和 JavaScript 各自做什么?

HTML 像舞台搭建图,负责把按钮、分数、角色和页面区域放出来。JavaScript 像导演和控制台,负责决定 什么时候切页、什么时候生成食物、什么时候播放音乐、什么时候结束游戏。

初学者记忆法 HTML 管“有什么”,JavaScript 管“什么时候发生”和“发生什么变化”。
English

What do HTML and JavaScript do?

HTML is like the stage plan: it places the buttons, score, character, and page areas. JavaScript is like the director and control panel: it decides when to switch pages, when to spawn food, when to play music, and when the round ends.

Beginner memory trick HTML answers "what exists," while JavaScript answers "when something happens" and "what changes."
flowchart TD A[Open 接接乐.html] --> B[Start page] B --> C[Choose difficulty] C --> D[Game page] D --> E[requestAnimationFrame loop] E --> F[Spawn food] E --> G[Move food downward] G --> H{Catch or miss?} H -->|Catch good food| I[Increase score] H -->|Catch bad food| J[Reduce hearts] H -->|Miss| K[Remove object] J --> L{Hearts = 0?} L -->|Yes| M[Game over overlay] L -->|No| E I --> E K --> E

This section explains the frame loop and collision logic in both languages.

中文

游戏循环一句话理解

每一帧,游戏都会测量时间、移动食物、检查碰撞、更新界面,然后请求浏览器继续下一帧。

为什么不用普通定时器?

setInterval 虽然简单,但浏览器忙的时候可能会延迟。requestAnimationFrame 更适合动画,因为它和屏幕刷新同步。

English

Game loop in one sentence

The game repeats a small set of steps every frame: measure time, move foods, check collisions, update the UI, then ask the browser for the next frame.

Why not use a normal timer?

A fixed timer like setInterval is easier to write, but the browser may delay it when the tab is busy. requestAnimationFrame is better for smooth animation because it is synchronized with the screen refresh rate.

中文

食物到达玩家时会发生什么?

每个食物对象都有 xy 坐标。下落物进入接取区域后,如果玩家在水平距离 上足够接近,就会被判定为接住。

适合初学者的重点

这里不需要复杂物理引擎。因为玩家只左右移动,食物只向下移动,所以只要做简单的距离检测就够了。

English

What happens when food reaches the player?

Each food item has an x and y position. If a falling item enters the catch zone and the player is close enough horizontally, the item is considered caught.

Important beginner idea

Collision detection does not need advanced physics here. A simple distance check works because the player only moves left and right, and the falling items move straight down.

sequenceDiagram participant P as Player participant UI as Game page participant JS as JavaScript loop participant A as Audio P->>UI: Click Start Game UI->>JS: startGame() JS->>A: music.start() loop every frame JS->>JS: spawnFood() sometimes JS->>JS: updateFoods(dt) JS->>UI: move DOM nodes JS->>JS: requestAnimationFrame() end P->>UI: Catch good food JS->>UI: score +1, pop animation P->>UI: Lose all hearts JS->>UI: show game over overlay

The audio chapter explains design, controls, and rhythm sync in both languages.

中文

为什么这里使用 Web Audio?

游戏没有携带音频文件,而是在浏览器里直接生成振荡器。这样可以让项目更轻量,也能展示“代码生成音乐” 的工作方式。

浏览器规则 大多数浏览器不允许页面一打开就自动播放声音,所以音乐必须等用户点击之后再开始。
English

Why use Web Audio here?

The game does not ship with an audio file. Instead, it creates oscillators in the browser. That keeps the project lightweight and shows how music can be synthesized with code.

Browser rule Audio usually cannot start until the user clicks or taps. That is why the music begins when the game starts, not when the page first loads.
中文

音乐控制器做了什么

  • 只在需要时创建 AudioContext
  • 通过低通滤波器让音色更柔和。
  • 安排循环旋律和低音线。
  • 在关卡结束或关闭音乐时安全停止。
English

What the music controller does

  • Creates an AudioContext only when needed.
  • Uses a low-pass filter so the tones sound softer.
  • Schedules a repeating melody and bass line.
  • Stops cleanly when the round ends or the player turns music off.
中文

控制方式

音乐有两个独立控制:一个开关控制“播不播放”,一个滑条控制“多大声”。这是一种很好的程序设计方式, 因为“是否播放”和“音量多大”是两种不同的数据。

  • musicEnabled 保存开关状态。
  • musicVolume 保存 0 到 1 的音量值。
  • 滑条直接更新 master gain 节点。
初学者收获 一个变量可以表示布尔选择,另一个变量可以表示连续范围。不同类型的数据应该配合不同的界面控件。
English

How the controls work

The music has two independent controls: one switch turns sound on or off, and one slider changes loudness. That is a useful pattern in programming because it separates the idea of whether audio plays from how loud it plays.

  • musicEnabled stores the on/off state.
  • musicVolume stores the number from 0 to 1.
  • The slider updates the master gain node directly.
Beginner takeaway One variable can represent a boolean choice, and another can represent a continuous range. The UI uses different controls because the data behaves differently.
中文

互动示例

这个示例不会真正播放声音,只是演示音量和节奏同步的关系。

75%

760 ms per spawn

English

Interactive control demo

This demo does not play sound. It just shows the relationship between volume and tempo sync.

75%

760 ms per spawn

flowchart LR A[User clicks Start] --> B[AudioContext resumes] B --> C[Tick scheduler] C --> D[Melody oscillator] C --> E[Bass oscillator] D --> F[Master gain] E --> F F --> G[Low-pass filter] G --> H[Speaker output]
flowchart TD A[Food spawn interval] --> B[tempo = 60000 / spawnInterval] B --> C[Music tick spacing] C --> D[Melody notes] A --> E[Spawn rhythm] E --> F[Gameplay feel] D --> F

The code chapter keeps the same sample code, but explains it in both languages.

中文

标注版 startGame()

// 在新一局开始前重置所有状态。
function startGame(difficulty){
  currentDifficulty = difficulty;
  hearts = MAX_HEARTS;
  goodCount = 0;
  badCount = 0;
  foods.forEach(f => f.el && f.el.remove());
  foods = [];

  // 切换到游戏页面并把角色放到中间。
  showPage('game');
  updateStageRect();
  charX = stageRect.width / 2;
  applyCharTransform();

  // 音乐只在玩家点击之后开始。
  music.start();
  gameActive = true;
  rafId = requestAnimationFrame(gameLoop);
}
English

Annotated startGame()

// Reset the round before it begins.
function startGame(difficulty){
  currentDifficulty = difficulty;
  hearts = MAX_HEARTS;
  goodCount = 0;
  badCount = 0;
  foods.forEach(f => f.el && f.el.remove());
  foods = [];

  // Show the game page and move the character to the center.
  showPage('game');
  updateStageRect();
  charX = stageRect.width / 2;
  applyCharTransform();

  // Music starts only after the player's click.
  music.start();
  gameActive = true;
  rafId = requestAnimationFrame(gameLoop);
}
中文

标注版 spawnFood()

// 决定这次掉落的是好东西还是坏东西。
function spawnFood(){
  const cfg = DIFF_CONFIG[currentDifficulty];
  const isBad = Math.random() < cfg.badProb;
  const emoji = isBad ? BAD_FOODS[...] : GOOD_FOODS[...];
  const speed = cfg.minSpeed + Math.random() * (cfg.maxSpeed - cfg.minSpeed);
  const x = 24 + Math.random() * (stageRect.width - 48);

  // 创建真实的 DOM 节点,让浏览器负责动画。
  const el = document.createElement('div');
  el.className = 'food';
  el.textContent = emoji;
  stageEl.appendChild(el);

  foods.push({ x, y:-40, speed, type:isBad ? 'bad' : 'good', el });
}
English

Annotated spawnFood()

// Decide whether the falling object is good or bad.
function spawnFood(){
  const cfg = DIFF_CONFIG[currentDifficulty];
  const isBad = Math.random() < cfg.badProb;
  const emoji = isBad ? BAD_FOODS[...] : GOOD_FOODS[...];
  const speed = cfg.minSpeed + Math.random() * (cfg.maxSpeed - cfg.minSpeed);
  const x = 24 + Math.random() * (stageRect.width - 48);

  // Create a real DOM node so the browser can animate it.
  const el = document.createElement('div');
  el.className = 'food';
  el.textContent = emoji;
  stageEl.appendChild(el);

  foods.push({ x, y:-40, speed, type:isBad ? 'bad' : 'good', el });
}
What the detailed comments in the game file are teaching

The comments explain why each block exists, not just what it does. For example, the music section explains the browser autoplay rule, the collision section explains why a simple distance check works, and the state section explains the three character moods.

The practice section lets students click through the logic in both languages.

中文

互动走读

点击下面的按钮,按浏览器执行的顺序一步一步查看逻辑。

第 1 步:开始游戏

玩家点击开始按钮,页面从菜单切换到游戏界面。

English

Interactive walkthrough

Click the buttons below to step through the logic the same way the browser executes it.

Step 1: Start the game

The player clicks the start button. The page switches from the menu to the game board.

中文

小测验

哪个对象保存所有正在下落的食物?

English

Mini quiz

Which object stores all falling foods?

中文

练习建议

找到 startGame()spawnFood()updateFoods()createMusicController(),分别解释它们负责什么:初始化、生成、移动还是声音。

English

Try this in the code

Search for the functions startGame(), spawnFood(), updateFoods(), and createMusicController(). Then explain to yourself what each one owns: setup, spawning, movement, or sound.