javascript/test/catch.js · 9446 字节 · 下载原文
// 接方块 —— 用 **JavaScript** 写的手机游戏
// Catch -- a mobile game written in **JavaScript**
//
// 玩法:左右方向键移动底部挡板,把落下来的球弹回去;没接住就结束。每接住一次 +10 分。
// Gameplay: the left/right arrow keys move the paddle at the bottom and bounce the falling ball back; missing it ends the round. Each catch = +10 points.
//
// ◆ 手机那套 UI
// ◆ The mobile UI set
//
// 开窗 / 绘图 / 输入 / 定时器是 C 写的(`Lib/shared/src/vmlui.c` → `vmlui.vml`),
// Window opening / drawing / input / timers are written in C (`Lib/shared/src/vmlui.c` -> `vmlui.vml`),
// 由 `vmltool.config.xml` 的 `<Language Name="javascript" Libs="vmlui.vml">` 挂上来。
// pulled in by `<Language Name="javascript" Libs="vmlui.vml">` in `vmltool.config.xml`.
//
// ◆ 两条写法要求(不是偏好)
// ◆ Two style requirements (not preferences)
//
// ① 库函数声明走**一条显式引用**:`#include "vml_ui.js"`(就在下面),别再手抄原型。
// ① Library declarations come from **one explicit include**: `#include "vml_ui.js"` (right below); do not hand-copy prototypes.
// 那份文件由生成器产出(`GenLib -n -c vmlui -l javascript -o Lib/javascript/vml_ui.js`),
// That file is produced by the generator (`GenLib -n -c vmlui -l javascript -o Lib/javascript/vml_ui.js`),
// 155 个 `ui_*` 全在里面。
// and holds all 155 `ui_*` entries.
//
// ⚠ **实测(2026-10-01):把这条 `#include` 删掉,本文件照样编得过、跑出来的画面一样。**
// ⚠ **Measured (2026-10-01): delete this `#include` and the file still compiles and draws the same frames.**
// 本前端早就修成「不认识的函数名直接发裸 `CALL <名>`、由链接期裁决」了
// This frontend was fixed long ago to emit a bare `CALL <name>` for unknown names and let the linker decide
// (`CodeGenerator.Calls.cs` 末尾那两段注释,v0.96.282)—— 文件头原先写的
// (see the two comments at the end of `CodeGenerator.Calls.cs`, v0.96.282). The claim the header used to make --
// 「不写声明会编成 MOVE R1, var_<名>; CALL R0 ⇒ 运行期跳野地址」是**那之前的旧行为**。
// "without a declaration it compiles to MOVE R1, var_<name>; CALL R0 => a wild jump" -- describes the **older** behaviour.
// ⇒ 所以这条引用是**「显式引用」**(口径见 `docs/UI库导入约定.md`),**不是「不写就编不过」的强制项**;
// => So this include is an **explicit reference** (see the UI-library import convention doc under docs), **not a requirement whose absence breaks the build**;
// 本仓点名批评"假开关",这里如实标注,不假装它强制。
// this repo calls out fake switches, so it is labelled honestly here instead of being passed off as mandatory.
// ⚠ **2026-10-04 复验仍成立**:/tmp 副本删掉这条 `#include "vml_ui.js"` 后 `✔ 编译完成`(画面照旧)。
// ⚠ **still holds on 2026-10-04**: a /tmp copy with this `#include "vml_ui.js"` removed still `✔ compiles` (frames unchanged).
// 补一句现状:**真正不认识**的函数名(`noSuchFunc(3)`)现在报 `error: 未定义的函数 'noSuchFunc'`、编译失败。
// one update: a genuinely **unknown** function name (`noSuchFunc(3)`) now reports `error: undefined function 'noSuchFunc'` and fails to compile.
// (`asm()` 已从本前端移除,用不了。)
// (The `asm()` form has been removed from this frontend and cannot be used.)
// ② 颜色写**负数十进制**(`-65536` = `0xFFFF0000`)。
// ② Colors are written as **negative decimals** (`-65536` = `0xFFFF0000`).
//
// ⚠ `corpus/javascript/skel.js` 的文件头记着「8 参的 ui_2d_rect 参数会整体反序」——
// ⚠ The header of `corpus/javascript/skel.js` notes that "the 8 arguments of ui_2d_rect come out in reverse order" --
// 本次读生成汇编,`call lib_vmlui_ui_rect` 之前是**从右到左**逐参压栈、与 C 前端同形,
// reading the generated assembly this time, the arguments before `call lib_vmlui_ui_rect` are pushed **right to left**, the same shape as the C frontend,
// **没看到反序**。该注记疑为调用约定统一前的旧观察。
// so **no reversal was seen**. That note is probably an observation from before the calling convention was unified.
// 真机画面以 `scripts/maui-vml-verify/corpus.tsv` 的 `game-javascript-catch` 为准。
// For what it looks like on a real device, trust `game-javascript-catch` in `scripts/maui-vml-verify/corpus.tsv`.
// UI 库声明:**一条显式引用**,不再手抄函数原型。
// UI library declarations: **one explicit include**, no hand-copied prototypes any more.
// 文件由生成器产出(`GenLib -n -c vmlui -l javascript -o Lib/javascript/vml_ui.js`),
// The file is produced by the generator (`GenLib -n -c vmlui -l javascript -o Lib/javascript/vml_ui.js`);
// 155 个 `ui_*` 全在里面,覆盖本文件(以及同目录其它例子)用到的每一个。
// all 155 `ui_*` entries live there and cover every one this file (and the other examples here) uses.
#include "vml_ui.js"
// 状态:0=挡板x 1=球x 2=球y 3=球dx 4=球dy 5=分数 6=最高 7=存活 8=屏宽 9=屏高 10=界面语言
// State: 0=paddle x 1=ball x 2=ball y 3=ball dx 4=ball dy 5=score 6=best 7=alive 8=screen w 9=screen h 10=UI language
let A = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
function resetGame() {
A[0] = A[8] / 2 - 40;
A[1] = A[8] / 2;
A[2] = 70;
A[3] = 3;
A[4] = 5;
A[5] = 0;
A[7] = 1;
}
function draw() {
let lang = A[10];
ui_2d_clear(-15724520);
ui_2d_text(8, 8, lang == 0 ? "得分" : "Score", -6643536, 13, 0);
ui_2d_rect(58, 11, A[5], 10, -11409298, 1, 0, 0);
ui_2d_text(A[8] / 2, 8, lang == 0 ? "最高" : "Best", -6643536, 13, 1);
ui_2d_rect(A[8] / 2 + 46, 11, A[6], 10, -63488, 1, 0, 0);
ui_2d_rect(A[0], A[9] - 40, 80, 12, -63488, 1, 0, 6);
ui_2d_circle(A[1], A[2], 9, -131246, 1, 0);
if (A[7] == 0) {
ui_2d_text(A[8] / 2, A[9] / 2, lang == 0 ? "按回车重开" : "Press Enter to restart", -131246, 16, 1);
}
ui_2d_present();
}
function step() {
let lang = A[10];
if (A[7] == 0) {
return;
}
A[1] = A[1] + A[3];
A[2] = A[2] + A[4];
if (A[1] < 10) { A[1] = 10; A[3] = 0 - A[3]; }
if (A[1] > A[8] - 10) { A[1] = A[8] - 10; A[3] = 0 - A[3]; }
if (A[2] < 30) { A[2] = 30; A[4] = 0 - A[4]; }
if (A[2] > A[9] - 52 && A[2] < A[9] - 30 && A[1] > A[0] - 9 && A[1] < A[0] + 89) {
A[4] = 0 - A[4];
A[2] = A[9] - 52;
A[5] = A[5] + 10;
if (A[5] > A[6]) { A[6] = A[5]; }
// 音效:单音 ui_audio_beep(v0.96.509 从音序器换回来 ——
// Sound: single-tone ui_audio_beep (switched back from the sequencer in v0.96.509 --
// 那一版多声部叠加 / 长音拖尾在真机上破音)
// that version's multi-voice layering / long-note tails crackled on the real device)
ui_audio_beep(1047, 165);
}
if (A[2] > A[9]) {
A[7] = 0;
// 音效:单音 ui_audio_beep;**结局音取最低音**(接住 1047 / 没接住 131,差得开)
// Sound: single-tone ui_audio_beep; **the ending tone takes the lowest pitch** (catch 1047 / miss 131, far enough apart)
ui_audio_beep(131, 320);
draw();
if (ui_dlg_msg(lang == 0 ? "接方块" : "Catch", lang == 0 ? "没接住,这一局结束。\n再来一局?(选「否」退出)" : "Missed. Round over.\nPlay again? (choose 'No' to quit)", 0) != 0) { ui_win_close(); return; }
resetGame();
}
}
function main() {
A[10] = ui_scr_get_language();
let lang = A[10];
// ⚠ **先开窗、再问尺寸**:开窗前 `ui_scr_get_w/h` 只能按**默认 chrome**(假设要手柄)估算,
// 与真正开出来的画布对不上(见 E141)。⇒ 开窗尺寸传 0 交给宿主,**开完再问**真实可用绘图区。
// ⚠ **Open the window first, then ask for the size**: before the window exists `ui_scr_get_w/h`
// can only estimate from the **default chrome** (which assumes a gamepad) and will not match the
// real canvas (see E141). So pass 0 here and ask for the real usable area **after** opening.
ui_win_open(lang == 0 ? "接方块" : "Catch", 0, 0);
let w = ui_scr_get_w();
let h = ui_scr_get_h();
if (w <= 0) { w = 360; }
if (h <= 0) { h = 620; }
A[8] = w;
A[9] = h;
ui_scr_set_keep_on(1);
resetGame();
let tid = ui_timer_set(40, 0);
while (ui_win_is_closed() == 0) {
draw();
let t = ui_msg_fetch_wait(0);
if (t == 10) { break; }
if (t == 9) { step(); }
if (t == 1) {
let k = ui_msg_get_a();
if (k == 27) { break; }
if (k == 37) { A[0] = A[0] - 20; if (A[0] < 4) { A[0] = 4; } }
if (k == 39) { A[0] = A[0] + 20; if (A[0] > w - 84) { A[0] = w - 84; } }
if (k == 13) { resetGame(); }
}
}
ui_timer_kill(tid);
ui_scr_set_keep_on(0);
ui_win_close();
}