← 都来码 / Dolaima← 例程索引dart/test/catch.dart原文

dart/test/catch.dart · 9003 字节 · 下载原文

// 接方块 —— 用 **Dart** 写的手机游戏

// Catch — a phone game written in **Dart**

//

// 玩法:左右方向键移动底部挡板,把落下来的球弹回去;没接住就结束。每接住一次 +10 分。

// Gameplay: left/right arrows move the paddle at the bottom and bounce the falling ball back; miss it and the game ends. Each catch is +10 points.

//

// ◆ 手机那套 UI

// ◆ The phone UI

//

// 开窗 / 绘图 / 输入 / 定时器是 C 写的(`Lib/shared/src/vmlui.c` → `vmlui.vml`),

// Window / drawing / input / timers are written in C (`Lib/shared/src/vmlui.c` → `vmlui.vml`),

// 由 `vmltool.config.xml` 的 `<Language Name="dart" Libs="vmlui.vml">` 挂上来。

// Hooked up by `<Language Name="dart" Libs="vmlui.vml">` in `vmltool.config.xml`.

//

// ◆ 写法要求

// ◆ Style requirements

//

//   · 调库函数(`ui_*` / `tty_*` / `print_*`)的声明**由文件顶部那行 `import 'vml_ui.dart';` 提供**

//   · Declarations for the library calls (`ui_*` / `tty_*` / `print_*`) come from the `import 'vml_ui.dart';` line at the top

//     (2026-10-01 起;那个文件由 `tools/GenLib` 产出、**不要手写**)。本文件**不再手抄 `external` 块**。

//     (since 2026-10-01; that file is generated by `tools/GenLib` — **do not hand-edit it**). No hand-copied `external` block here any more.

//     ⚠ **实测:把 `import` 删掉也照样编得过、跑得对** —— 链接器会把 `func_ui_*` 剥前缀兜底。

//     ⚠ **Measured: deleting the `import` still compiles and runs** — the linker strips the `func_` prefix as a fallback.

//     所以它是「**文档级**」的显式引用,不是强制门(见 `docs/UI库导入约定.md` §九)。

//     So it is a **documentation-level** explicit reference, not an enforced gate.

//     The doc that pins this is the repo note on explicit UI imports, section 9.

//   · 颜色可以写 `0xAARRGGBB` 了(十六进制字面量 2026-10-01 起支持);负数十进制也仍然可以。

//   · Colors may now be written as `0xAARRGGBB` (hex literals since 2026-10-01); negative decimals still work.

//   · `print` 不加分隔符、不补换行 ⇒ 显式补 "\n"。

//   · `print` adds no separator and no newline ⇒ add "\n" explicitly.

//   · **状态与逻辑全内联在 `main` 里**:文件级可变状态在 Kotlin / Swift / Go 那几门上都出过问题

//   · **All state and logic is inlined in `main`**: file-level mutable state has gone wrong on Kotlin / Swift / Go

//     (最典型是 Kotlin 的文件级 `arrayOf` 读回 0),这里不冒这个险。

//     (most typically Kotlin's file-level `arrayOf` reading back 0), and this doesn't take that risk.

//

// ◆ 已修的前端缺陷(2026-09-17 前后)

// ◆ Frontend defects already fixed (around 2026-09-17)

//

//   · 数组:早期「没有下标表达式、`a[i]` 退化成一个裸名、`a[i] = x` 左值被丢弃」;

//   · Arrays: earlier "no index expression, `a[i]` degenerated into a bare name, the lvalue of `a[i] = x` was discarded";

//     patch 0030 修过。本份实测 `a[0]=7; a[3]=5;` 求和得 12 ✓。

//     fixed in patch 0030. Measured here: `a[0]=7; a[3]=5;` sums to 12 ✓.

//   · **栈帧**:早期 `GenerateVarDecl` 只写 `[R12-N]` 却不 `SUB R13`,带嵌套 push 的表达式

//   · **Stack frame**: earlier `GenerateVarDecl` only wrote `[R12-N]` without `SUB R13`, so expressions with nested pushes

//     会踩坏局部变量(语料文件头记的就是这条);patch 0030 一并修了。

//     clobbered local variables (this is what the corpus file header records); fixed together in patch 0030.

//

// ✅ 2026-10-04 复验(实跑本文件):两条仍都是好的 —— 数值下标读写 `a[0]=7; a[3]=5; a[0]+a[3]` 实测 12。

// ✅ Re-checked 2026-10-04 (this file actually run): both still hold -- indexed read/write `a[0]=7; a[3]=5; a[0]+a[3]` measures 12.

//    程序编译 68162 条指令、正常运行。

//    The program compiles to 68162 instructions and runs normally.


#include "../../../Lib/dart/vml_ui.dart"
import 'vml_ui.dart';   // UI library declarations (generated by GenLib, do not hand-edit)

void main() {
  // 状态:0=挡板x 1=球x 2=球y 3=球dx 4=球dy 5=分数 6=最高 7=存活 8=屏宽 9=屏高

  // State: 0=paddle x, 1=ball x, 2=ball y, 3=ball dx, 4=ball dy, 5=score, 6=best, 7=alive, 8=screen width, 9=screen height

  List<int> A = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

  // 界面语言:开局查一次(`ui_scr_get_language` 是 syscall,别每帧调)

  // UI language: queried once at start (`ui_scr_get_language` is a syscall, not once per frame)

  int lang = ui_scr_get_language();
// ⚠ **先开窗、再问尺寸**:开窗前 `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);
  int w = ui_scr_get_w();
  int 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);

  A[0] = w / 2 - 40;
  A[1] = w / 2;
  A[2] = 70;
  A[3] = 3;
  A[4] = 5;
  A[5] = 0;
  A[6] = 0;
  A[7] = 1;

  int tid = ui_timer_set(40, 0);

  while (ui_win_is_closed() == 0) {

    // ── draw ──

    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();

    int t = ui_msg_fetch_wait(0);
    if (t == 10) { break; }

    if (t == 9) {
      if (A[7] != 0) {
        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)

        // Catch: the ball falls on the paddle band and is horizontally within the paddle's range (nested if for multiple conditions)

        if (A[2] > A[9] - 52) {
          if (A[2] < A[9] - 30) {
            if (A[1] > A[0] - 9) {
              if (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 stacking / long-note tails broke up on real devices)

                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 note** (1047 on a catch / 131 on a miss — far enough apart)

          ui_audio_beep(131, 320);
          if (ui_dlg_msg(lang == 0 ? "接方块" : "Catch", lang == 0 ? "没接住,这一局结束。\n再来一局?(选「否」退出)" : "Missed. Round over.\nPlay again? (choose 'No' to quit)", 0) != 0) { ui_win_close(); break; }
          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;
        }
      }
    }

    if (t == 1) {
      int 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] > A[8] - 84) { A[0] = A[8] - 84; } }
      if (k == 13) {
        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;
      }
    }
  }

  ui_timer_kill(tid);
  ui_scr_set_keep_on(0);
  ui_win_close();
}