← 都来码 / Dolaima← 例程索引rust/app/breakout.rs原文

rust/app/breakout.rs · 15153 字节 · 下载原文

// 打砖块 —— 用 **Rust** 写的手机游戏

// Breakout -- a mobile game written in **Rust**

//

// 玩法:左右方向键移动底部挡板,把球弹进上方 6×4 的砖块阵;砖块清空即过关,

// Gameplay: the left/right arrow keys move the paddle at the bottom, bouncing the ball up into the 6x4 brick grid;

// 球落底即结束。每打掉一块 +10 分。比「接方块」多一层**数组遍历 + 矩形碰撞**,

// the round ends when the ball hits the bottom. Each brick destroyed = +10 points. One layer beyond "catch the blocks": **array traversal + rectangle collision**;

// 刻意换玩法是为了让这份例程照到别的前端路径(下标写入、二维布局换算)。

// the deliberately different gameplay is so this example also exercises other frontend paths (subscript writes, 2-D layout arithmetic).

//

// ◆ 手机那套 UI

// ◆ The mobile UI set

//

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

// Opening the window / drawing / input / timers are written in C (`Lib/shared/src/vmlui.c` -> `vmlui.vml`),

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

// pulled in by `<Language Name="rust" Libs="vmlui.vml">` in `vmltool.config.xml`.

//

// ◆ 为什么全部逻辑都在 `main` 里

// ◆ Why all the logic lives in `main`

//

// Rust 这份没有用文件级数组:前端对模块级可变状态的支持没有把握,而骨架

// This Rust version uses no file-level arrays: the frontend's support for module-level mutable state was uncertain, and the skeleton

// (`corpus/rust/skel.rs`)也只用了 `main` 内的局部数组。状态留在 `main` 内、

// (`corpus/rust/skel.rs`) also only used local arrays inside `main`. Keeping the state inside `main` and

// 逻辑内联是绕开不确定性,不是风格选择。

// inlining the logic avoids that uncertainty; it is not a style choice.

//

// 已实测可用(2026-09-17):`let mut a = [0,0,0,0]; a[i] = v;` 与读回都对

// Measured working (2026-09-17): `let mut a = [0,0,0,0]; a[i] = v;` and reading it back are both correct

// (最小的 `a[0]=7; a[3]=5;` 求和得 12 ✓)—— `corpus/rust/skel.rs` 文件头记的

// (the minimal `a[0]=7; a[3]=5;` sums to 12 OK) -- the note in the `corpus/rust/skel.rs` file header

// 「下标左值会落回表达式语句」那条**现在不成立了**。

// that "a subscript lvalue falls back to an expression statement" **no longer holds**.

//

// ◆ 写法:颜色写负数十进制;循环更新写 `i = i + 1`。

// ◆ Style: write colors as negative decimals; write the loop update as `i = i + 1`.


// ◆ UI 库的显式引用

//   The explicit reference to the UI library

//

// `use vml_ui::*;` —— 声明文件是 `Lib/rust/vml_ui.rs`(GenLib 生成,155 个 `ui_*`,**不要手改**):

// `use vml_ui::*;` -- the declaration file is `Lib/rust/vml_ui.rs` (generated by GenLib, 155 `ui_*`; **do not hand-edit**):

//

//     cd third_party/vml && dotnet run --project tools/GenLib -- -n -c vmlui -l rust -o Lib/rust/vml_ui.rs

//

// 本前端**不需要**声明就能解析 `ui_*`(它按 `ui_` 前缀自动链 `vmlui.vml`),

// This frontend resolves `ui_*` **without** any declaration (it auto-links `vmlui.vml` by the `ui_` prefix),

// 这条引用是**文档级**的:它标明"这个程序用 UI 库",并让这份例程与其余语言同形。

// so this line is **documentation-level**: it states that this program uses the UI library and keeps

// this example shaped like every other language's.

// 实测(删掉这一行,产物与画面完全一致):见仓库 docs 里那份「UI 库导入约定」的第二节 rust 行。

// Measured: deleting this line changes nothing -- the same instruction count and the same drawn picture;

// see the rust row in section two of the UI-library import convention document under docs.

#include "../../../Lib/rust/vml_ui.rs"
use vml_ui::*;

fn main() {
    // 24 块砖(6 列 × 4 行),1 = 还在

    // 24 bricks (6 columns x 4 rows), 1 = still there

    // ⚠ 数组字面量**必须写在一行** —— 跨行的 `];` 报「非法 token: SEMICOLON」(实测)

    // ⚠ The array literal **must be written on one line** -- a line-broken `];` reports "invalid token: SEMICOLON" (measured)

    let mut bricks = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
    let mut pad = 0;
    let mut bx = 0;
    let mut by = 0;
    let mut bdx = 0;
    let mut bdy = 0;
    let mut score = 0;
    let mut left = 24;
    let mut alive = 0;

    // 界面语言:开局问一次宿主要中文还是英文(0=中文 1=英文),之后整局按它分支。

    // UI language: ask the host once at the start whether Chinese or English (0=Chinese 1=English), then branch on it for the whole round.

    // ⚠ 别在每帧里调 —— 那是一次 syscall。`if` 表达式(本前端支持,见语言规范 §条件语句)

    // ⚠ Do not call it in every frame -- that is a syscall. The `if` expression (supported by this frontend, see the language spec section on conditionals)

    //   在这里比 `let mut` + `if` 语句短,且分支文本与条件写在同一行。

    //   is shorter here than `let mut` + an `if` statement, and keeps the branch text on the same line as the condition.

    let lang = ui_scr_get_language();
    let t_title = if lang == 0 { "打砖块" } else { "Breakout" };
    let t_score = if lang == 0 { "得分" } else { "Score" };
    let t_left = if lang == 0 { "余砖" } else { "Bricks left" };
    let t_restart = if lang == 0 { "按回车重开" } else { "Press Enter to restart" };
    let t_cleared = if lang == 0 { "全清了!这一局结束。\n再来一局?(选「否」退出)" } else { "Cleared! Round over.\nPlay again? (choose \"No\" to quit)" };
    let t_fell = if lang == 0 { "球落底了,这一局结束。\n再来一局?(选「否」退出)" } else { "Ball fell. Round over.\nPlay again? (choose \"No\" to quit)" };

    // 开窗时传 0:尺寸交给宿主(用真实可用区)。**开窗之后**再问一次真实画布 —— 开窗前问到的只是估算值。

    // Pass 0 when opening: the size is the host's to decide (the real usable area). Ask the **real** canvas only after the window is open -- before that the host can only estimate.

    ui_win_open(t_title, 0, 0);
    ui_scr_set_keep_on(1);

    // 可用绘图区(手机上约 395x578)

    // The usable drawing area (about 395x578 on a phone)

    let mut w = ui_scr_get_w();
    let mut h = ui_scr_get_h();
    if w <= 0 { w = 360; }
    if h <= 0 { h = 620; }

    let bw = (w - 16) / 6;
    let bh = 20;
    let top = 60;

    pad = w / 2 - 45;
    bx = w / 2;
    by = h - 120;
    bdx = 4;
    bdy = 0 - 5;
    alive = 1;

    let mut tid = ui_timer_set(34, 0);
    // 运动墙钟欠账(毫秒)—— 按墙钟补步,用户 2026-10-04 口径(定时器只唤醒,走几步看欠了多少)

    // Motion wall-clock debt in ms — steps are caught up from the wall clock (user's 2026-10-04 rule)

    let mut acc = 0;
    // 上次补步取的墙钟时刻(进主循环时初始化)

    // wall-clock time of the last catch-up step (initialized when entering the main loop)

    let mut lastMs = ui_time_get_ms();

    while ui_win_is_closed() == 0 {

        // ── draw ──

        ui_2d_clear(-15724520);
        ui_2d_text(8, 8, t_score, -6643536, 13, VML_ANCHOR_LEFT);
        ui_2d_rect(58, 11, score, 10, -11409298, 1, 0, 0);
        ui_2d_text(w / 2, 8, t_left, -6643536, 13, VML_ANCHOR_CENTER);
        ui_2d_rect(w / 2 + 46, 11, left * 4, 10, -63488, 1, 0, 0);

        let mut i = 0;
        while i < 24 {
            if bricks[i] != 0 {
                let col = i - (i / 6) * 6;
                let row = i / 6;
                // 每行一色(越上越贵气一点)

                // One color per row (the higher up, the fancier)

                if row == 0 { ui_2d_rect(8 + col * bw, top, bw - 3, bh - 3, -63488, 1, 0, 3); }
                if row == 1 { ui_2d_rect(8 + col * bw, top + 24, bw - 3, bh - 3, -131246, 1, 0, 3); }
                if row == 2 { ui_2d_rect(8 + col * bw, top + 48, bw - 3, bh - 3, -11409298, 1, 0, 3); }
                if row == 3 { ui_2d_rect(8 + col * bw, top + 72, bw - 3, bh - 3, -15263713, 1, 0, 3); }
            }
            i = i + 1;
        }

        ui_2d_rect(pad, h - 40, 90, 12, -63488, 1, 0, 6);
        ui_2d_circle(bx, by, 8, -131246, 1, 0);
        if alive == 0 {
            ui_2d_text(w / 2, h / 2, t_restart, -131246, 16, VML_ANCHOR_CENTER);
        }
        ui_2d_present();

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

        if t == 9 {
            // 这一帧该走几步:**按墙钟欠账**(各平台定时器分辨率不同 ⇒ 不能拿"收到几拍"当时间)。

            // how many steps this frame takes: **driven by the wall-clock deficit** (timer resolution differs per platform => never treat "ticks received" as time).

            // 用户 2026-10-04 口径:所有游戏时钟取系统毫秒数。定时器只负责唤醒。

            // user's 2026-10-04 rule: all game clocks read system milliseconds; the timer only wakes the loop.

            let nowMs = ui_time_get_ms();
            acc = acc + (nowMs - lastMs);
            lastMs = nowMs;
            if acc > 34 * 8 { acc = 0; }          // 卡太久别一次补一大串(防瞬移)

                                                  // // do not catch up in one huge burst after a long stall (prevents teleporting)

            while acc >= 34 {
                acc = acc - 34;
                if alive == 0 { continue; }       // 本局结束:只把欠账消耗掉,不走

                                                  // // this run is over: consume the deficit without stepping

                bx = bx + bdx;
                by = by + bdy;
                if bx < 10 { bx = 10; bdx = 0 - bdx; }
                if bx > w - 10 { bx = w - 10; bdx = 0 - bdx; }
                if by < 30 { by = 30; bdy = 0 - bdy; }

                // 挡板:球落到挡板带上、横向在挡板范围内 → 弹回

                // Paddle: the ball lands on the paddle band and is horizontally within the paddle -> bounce back

                if by > h - 52 {
                    if by < h - 28 {
                        if bx > pad - 8 {
                            if bx < pad + 98 {
                                bdy = 0 - bdy;
                                by = h - 52;
                            }
                        }
                    }
                }

                // 砖块碰撞:命中就消一块、弹回、加分

                // Brick collision: on a hit, remove one brick, bounce back, add points

                i = 0;
                while i < 24 {
                    if bricks[i] != 0 {
                        let col = i - (i / 6) * 6;
                        let row = i / 6;
                        let rx = 8 + col * bw;
                        let ry = top + row * 24;
                        if bx > rx - 8 {
                            if bx < rx + bw + 8 {
                                if by > ry - 8 {
                                    if by < ry + bh + 8 {
                                        bricks[i] = 0;
                                        left = left - 1;
                                        score = score + 10;
                                        bdy = 0 - bdy;
                                        // 打砖得分:一声高而短的「叮」

                                        // Brick hit score: one high, short "ding"

                                        //   (单音 ui_audio_beep —— v0.96.509 从音序器换回来,

                                        //   (single-tone ui_audio_beep -- switched back from the sequencer in v0.96.509,

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

                                        //    that version's multi-voice layering / long notes crackled on the real device)

                                        ui_audio_beep(1047, 66);
                                    }
                                }
                            }
                        }
                    }
                    i = i + 1;
                }

                if left == 0 {
                    alive = 0;
                    // 过关(赢方):**最高音**、最长

                    // Round cleared (winner): **highest pitch**, longest

                    ui_audio_beep(1047, 320);
                    ui_2d_present();
                    if (ui_dlg_msg(t_title, t_cleared, VML_DLG_INFO)) != 0 { ui_win_close(); break; }
                    i = 0;
                    while i < 24 { bricks[i] = 1; i = i + 1; }
                    left = 24;
                    score = 0;
                    pad = w / 2 - 45;
                    bx = w / 2;
                    by = h - 120;
                    bdx = 4;
                    bdy = 0 - 5;
                    alive = 1;
                }

                if by > h {
                    alive = 0;
                    // 死(输方):「最低音」、最长 —— 与过关那条 1047 正好是两端

                    // Death (loser): "lowest pitch", longest -- exactly the opposite end from the 1047 of the round-cleared one

                    ui_audio_beep(131, 320);
                    ui_2d_present();
                    if (ui_dlg_msg(t_title, t_fell, VML_DLG_INFO)) != 0 { ui_win_close(); break; }
                    i = 0;
                    while i < 24 { bricks[i] = 1; i = i + 1; }
                    left = 24;
                    score = 0;
                    pad = w / 2 - 45;
                    bx = w / 2;
                    by = h - 120;
                    bdx = 4;
                    bdy = 0 - 5;
                    alive = 1;
                }
            }
        }

        if t == 1 {
            let k = ui_msg_get_a();
            if k == 27 { break; }
            if k == 37 {
                pad = pad - 24;
                if pad < 4 { pad = 4; }
            }
            if k == 39 {
                pad = pad + 24;
                if pad > w - 94 { pad = w - 94; }
            }
            if k == 13 {
                i = 0;
                while i < 24 { bricks[i] = 1; i = i + 1; }
                left = 24;
                score = 0;
                pad = w / 2 - 45;
                bx = w / 2;
                by = h - 120;
                bdx = 4;
                bdy = 0 - 5;
                alive = 1;
            }
        }
    }

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