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

d/test/catch.d · 8915 字节 · 下载原文

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

// Catch the Ball —— a mobile game written in **D**

//

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

// Gameplay: the left/right arrow keys move the paddle along the bottom, bouncing the falling ball back up; miss it and the game ends. Every catch is +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="d" Libs="vmlui.vml">` 挂上来。

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

//

// ◆ 写法要求(沿用 corpus/d/skel.d 的核对结论)

// ◆ Writing requirements (carrying over the verified conclusions from corpus/d/skel.d)

//

//   · D **没有 extern / asm** ⇒ 裸调 `ui_2d_rect(...)` 会被编成 `CALL func_ui_rect`,

//   · D **has no extern / asm** ⇒ a bare call to `ui_2d_rect(...)` compiles into `CALL func_ui_rect`,

//     靠链接器剥 `func_` 前缀解析到 `lib_vmlui_ui_rect`。不需要声明。

//     the linker strips the `func_` prefix and resolves it to `lib_vmlui_ui_rect`. No declaration needed.

//   · 颜色写负数十进制:`0xFFFF0000` 会在词法层 OverflowException。

//   · Colors are written as negative decimals: `0xFFFF0000` raises an OverflowException in the lexer.

//   · `writeln` 实参之间不加分隔符、末尾补换行(本份不用控制台)。

//   · `writeln` puts no separator between arguments and appends a newline (this file does not use the console).

//   · 状态与逻辑全内联在 `main`:D 的顶层语句本来就会执行(`main` 不是必须的),

//   · All state and logic are inlined in `main`: D top-level statements do run anyway (`main` is not required),

//     但没有可靠的文件级可变状态可用 —— 不冒这个险。

//     but no reliable file-level mutable state is available —— not taking that risk.

//

// ◆ 已修的前端缺陷(patch 0030)

// ◆ Frontend defects already fixed (patch 0030)

//

// 早期数组三处全坏:`[1,2,3,4]` 字面量抛 InvalidCastException、`int[4] a;` 的维度被丢掉、

// Arrays used to be broken in three places: the `[1,2,3,4]` literal threw InvalidCastException, the dimension of `int[4] a;` was dropped,

// `a[i] = v` 被压成 `a = v`(下标整个丢弃)。本份实测

// and `a[i] = v` was collapsed into `a = v` (the whole index was dropped). Measured on this file:

// `int[4] a; a[0]=7; a[3]=5;` 求和得 12 ✓ —— 那三条都已好。

// `int[4] a; a[0]=7; a[3]=5;` sums to 12 ✓ —— all three are fixed now.

//

// ✅ 2026-10-04 复验(实跑本文件):三条仍都是好的 —— `int[4] a; a[0]=7; a[3]=5; a[0]+a[3]` 实测 12。

// ✅ Re-checked 2026-10-04 (this file actually run): all three still hold -- `int[4] a; a[0]=7; a[3]=5; a[0]+a[3]` measures 12.

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

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


#include "../../../Lib/d/vml_ui.d"
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

    int[10] A;

    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)

                // Caught: the ball is on the paddle band and horizontally in range (multiple conditions use nested if)

                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 (v0.96.509 switched back from the sequencer ——

                                //   那一版多声部叠加 / 长音拖尾在真机上破音)

                                //   that version's multi-voice layering / long-note tails crackled 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 pitch** (caught 1047 / missed 131, far apart)

                    ui_audio_beep(131, 320);
                    // 选「否/拒绝」→ 退出游戏(ui_dlg_msg 返回 0=是 / 1=否)。

                    // Choosing "No / reject" → quit the game (ui_dlg_msg returns 0=yes / 1=no).

                    // 此前不接返回值 ⇒ 两个按钮一个样、游戏还退不出去(用户实测报的)

                    // Previously the return value was not used ⇒ both buttons behaved the same and the game could not even be exited (reported from real-device testing)

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