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

java/test/catch.java · 10912 字节 · 下载原文

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

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

//

// 玩法:左右方向键移动底部挡板,把落下来的球弹回去;没接住就结束。每接住一次 +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.

// 比贪吃蛇短一截(无增长、无自撞、无环形回绕),刻意选它当 Java 的第一份例程 ——

// Shorter than Snake (no growth, no self-collision, no wrap-around) — deliberately chosen as Java's first example,

// 把「前端能不能写游戏」这件事与「游戏逻辑本身有多复杂」解耦。

// decoupling "can the frontend write games" from "how complex the game logic itself is".

//

// ◆ 手机那套 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="java" Libs="vmlui.vml">` 挂上来。

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

//

// ◆ 两条写法要求(不是偏好)

// ◆ Two style requirements (not preferences)

//

//   ① 调库函数必须声明成 `static native` —— `asm()` 已从 Java 前端移除

//   ① Library calls must be declared `static native` — `asm()` has been removed from the Java frontend

//      (`CodeGenerator.Expressions.cs:447` 注明「仅限 C/ObjC/C++」),

//      (`CodeGenerator.Expressions.cs:447` notes "C/ObjC/C++ only"),

//      不声明就发 `CALL method_ui_rect`、链接期找不到标签。

//      and without a declaration it emits `CALL method_ui_rect`, whose label cannot be found at link time.

//   ② 颜色写**负数十进制**(`-65536` = `0xFFFF0000`)—— 能跑就别动。~~避开十六进制字面量在词法层的解读差异~~

//   ② Write colors as **negative decimals** (`-65536` = `0xFFFF0000`) — leave working code alone. ~~avoiding the lexer's differing interpretation of hex literals~~

//      —— 2026-10-04 复验十六进制**已修**(`0xFF101018` 直接得 -15724520、`0xFF` 得 255);新写的代码可以直接用十六进制。

//      -- re-verified 2026-10-04: hex is **fixed** (`0xFF101018` gives -15724520 directly, `0xFF` gives 255); new code may use hex directly.

//

// ⚠ `corpus/java/skel.java` 的文件头记着「8 参的 ui_2d_rect 参数会整体错位一格」——

// ⚠ The header of `corpus/java/skel.java` records that "ui_2d_rect's 8 parameters are shifted by one slot overall" —

//   本次写例程时读了生成汇编,`call lib_vmlui_ui_rect` 之前是**从右到左**逐参压栈、

//   when writing this example the generated assembly was read: before `call lib_vmlui_ui_rect` the arguments are pushed **right to left**,

//   与 C 前端同形(`10` 最后压 = arg0 在栈顶),**没看到错位**。该注记疑为调用约定统一前的旧观察。

//   the same shape as the C frontend (`10` pushed last = arg0 on top of the stack), and **no shift was seen**. That note is suspected to be an old observation from before the calling convention was unified.

//   ✅ 2026-10-04 复验仍成立:本文件 `ui_2d_rect(58, 11, A[5], 10, -11409298, 1, 0, 0)` 生成的是

//   ✅ Re-verified 2026-10-04, still holds: this file's `ui_2d_rect(58, 11, A[5], 10, -11409298, 1, 0, 0)` emits

//      `push 0,0,1,-11409298,10,A[5],11,58` 后 `call lib_vmlui_ui_2d_rect` —— arg0=58(x) 落在 `[@R12+12]`,**无错位**。

//      `push 0,0,1,-11409298,10,A[5],11,58` then `call lib_vmlui_ui_2d_rect` -- arg0=58(x) lands at `[@R12+12]`, **no shift**.

//   真机上的画面是否正确,以 `scripts/maui-vml-verify/corpus.tsv` 的 `game-java-catch` 为准。

//   Whether the picture is right on a real device is governed by `game-java-catch` in `scripts/maui-vml-verify/corpus.tsv`.

#include "../../../Lib/java/vml_ui.java"
class Catch {

    static native int ui_scr_get_w();
    static native int ui_scr_get_h();
    static native int ui_scr_get_language();
    static native int ui_win_open(String t, int w, int h);
    static native int ui_win_is_closed();
    static native int ui_win_close();
    static native void ui_2d_clear(int c);
    static native void ui_2d_rect(int x, int y, int w, int h, int c, int fill, int lw, int r);
    static native void ui_2d_circle(int cx, int cy, int r, int c, int fill, int lw);
    static native void ui_2d_text(int x, int y, String s, int c, int size, int anchor);
    static native void ui_2d_present();
    static native int ui_timer_set(int ms, int tag);
    static native void ui_timer_kill(int id);
    static native int ui_msg_fetch_wait(int timeout);
    static native int ui_msg_get_a();
    static native void ui_audio_beep(int freq, int ms);
    static native void ui_scr_set_keep_on(int on);
    static native void ui_dlg_msg(String title, String body, int style);

    // 状态: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 width, 9=screen height, 10=UI language

    //

    // ⚠ 语言码寄在数组里、各方法再读进局部变量 `lang` —— 本文件保持这个写法(**能跑就别动**)。

    // ⚠ The language code lives in the array and each method reads it into a local `lang` -- this file keeps that style (**leave working code alone**).

    //   ~~不能用 `static int lang`:字段初始化器从不生成、跨方法读写静态标量读回来是错的(`b = 3` 之后别的方法读到 0)~~

    //   ~~do not use `static int lang`: field initializers were never emitted and cross-method static scalars read wrong (`b = 3`, then another method reads 0)~~

    //   —— 2026-10-04 复验**已修**:`static int b = 3` 读回 3,setter 写 42 后 getter 读到 42。

    //   -- **fixed**, re-verified 2026-10-04: `static int b = 3` reads 3, and after a setter writes 42 the getter reads 42.

    static int[] A = new int[11];

    static void 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;
    }

    static void draw() {
        int 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();
    }

    static void step() {
        int 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]; }
        // 接住:球落到挡板带上、且横向落在挡板范围内

        // Catch: the ball falls on the paddle band and is horizontally within the paddle's range

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

    public static void main(String[] args) {
        // 界面语言:开局查一次存进 A[10](`ui_scr_get_language` 是 syscall,别每帧调)

        // UI language: query once at startup into A[10] (`ui_scr_get_language` is a syscall, do not call it every frame)

        int lang = ui_scr_get_language();
        A[10] = lang;
        // ⚠ **先开窗、再问尺寸**:开窗前 `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);
        resetGame();
        int tid = ui_timer_set(40, 0);

        while (ui_win_is_closed() == 0) {

            draw();
            int t = ui_msg_fetch_wait(0);
            if (t == 10) { break; }
            if (t == 9) { step(); }
            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] > w - 84) { A[0] = w - 84; } }
                if (k == 13) { resetGame(); }
            }
        }
        ui_timer_kill(tid);
        ui_scr_set_keep_on(0);
        ui_win_close();
    }
}