// 打砖块 —— 用 **C#** 写的手机游戏（不是 C）
// Breakout — a phone game written in **C#** (not C)
//
// 玩法：挡板在底下，球在场上弹。**拖动手指**（挡板跟着手指走）或方向键左右移动挡板，
// Gameplay: the paddle sits at the bottom and the ball bounces around the field. **Drag your
// 点一下（或空格/回车）发球。球打光一整片砖就过关（挡板与球都提速、砖块加多一行），
// finger** (the paddle follows it) or use the left/right arrows to move the paddle; tap (or
// 三条命打完就结束。球碰到挡板的哪个位置决定反弹角度 —— 打中间弹得直、打边缘弹得斜。
// Space/Enter) to serve. Clear every brick to advance a level (paddle and ball speed up, one
// more row of bricks); three lives, then it is over. Where the ball hits the paddle sets the
// bounce angle — the middle sends it straight back, the edges send it sideways.
//
// ## 为什么写得这么"平"
// ## Why it is written so "flat"
//
// C# 前端只支持一个子集（`Examples/csharp/g2048.cs` 的文件头列了完整名单），这里沿用同一套
// The C# front end supports only a subset (the full list is in `Examples/csharp/g2048.cs`'s header);
// 已被验证过的写法：一个类 + `static` 字段/方法 + 定长数组 + `while`/`if`/`switch`。
// this file sticks to the same verified constructs: one class, `static` fields/methods, fixed-length
// 全篇**整数**物理（1 像素/拍），不碰浮点 —— 与本仓其它例程同一取舍。
// arrays and `while`/`if`/`switch`. The physics is **all integers** (1 pixel per tick), no floating
// point — the same trade-off as the other examples in this repo.
//
// ## 音效按「一次事件一个音」
// ## Sound: one beep per event
//
// `ui_audio_beep` 是**单通道**的（后一个音掐掉前一个），所以这里给每种事件挑一个**不同音高**、
// `ui_audio_beep` is **single-channel** (a new tone cuts the previous one), so every event gets its own
// 而且一拍里最多发一个：砖块 880 / 挡板 440 / 丢命 131 / 过关 1047 / 结束 98。
// pitch and at most one beep per tick: brick 880 / paddle 440 / lost life 131 / level clear 1047 /
// game over 98.
//
// ## 每帧图元数
// ## Primitives per frame
//
// 砖块 42 + 墙 4 + 挡板 2 + 球 2 + HUD ≈ **60**，远在 400 之下。数值由**宿主自己**报
// 42 bricks + 4 walls + 2 paddle + 2 ball + HUD ≈ **60**, far below 400. The number comes from the
// （`ui_res_get_count(0)`，每个新高峰打印一行 `PRIMAX n`，见 `draw()`）。
// **host itself** (`ui_res_get_count(0)`; every new high water mark prints a `PRIMAX n` line — see `draw()`).
//
// 数值的唯一真源仍是 `Lib/c/vml_ui.h`。
// The single source of truth for these numbers is still `Lib/c/vml_ui.h`.

// 显式引用 UI 库：`ui_*` 的声明在 `Lib/csharp/VmlUi.cs`（tools/GenLib 生成，别手改）。
// Explicit reference to the UI library: `ui_*` is declared in `Lib/csharp/VmlUi.cs` (generated by tools/GenLib; don't hand-edit).
using Vml.Ui;

class Breakout
{
    // ── 消息类型（源：Lib/c/vml_ui.h）──
    // ── Message types (source: Lib/c/vml_ui.h) ──
    const int MSG_NONE = 0;
    const int MSG_KEYDOWN = 1;
    const int MSG_TOUCHDOWN = 6;
    const int MSG_TOUCHMOVE = 7;
    const int MSG_TOUCHUP = 8;
    const int MSG_TIMER = 9;
    const int MSG_WINDOWCLOSE = 10;
    const int MSG_WINDOWRESIZE = 11;

    // ── 键码 ──
    // ── Key codes ──
    const int KEY_ENTER = 13;
    const int KEY_ESCAPE = 27;
    const int KEY_SPACE = 32;
    const int KEY_LEFT = 37;
    const int KEY_UP = 38;
    const int KEY_RIGHT = 39;
    const int KEY_DOWN = 40;
    const int KEY_PAD_A = 65;

    // ── 锚点 / 样式 / 竖对齐 ──
    // ── Anchors / styles / vertical alignment ──
    const int ANCHOR_LEFT = 0;
    const int ANCHOR_CENTER = 1;
    const int ANCHOR_RIGHT = 2;
    const int FONT_BOLD = 1;
    const int VANCHOR_MIDDLE = 1;

    // ── 语言 / 对话框 ──
    // ── Language / dialog ──
    const int LANG_ZH = 0;
    const int DLG_INFO = 0;

    // ── 窗口声明（开窗之前生效）──
    // ── Window declarations (take effect before the window opens) ──
    const int WIN_PORTRAIT = 0;
    const int WIN_NO_GAMEPAD = 0;

    // ── 规则常量 ──
    // ── Rule constants ──
    const int COLS = 7;
    const int MAXB = 42;                 // 6 行 × 7 列的上限
    // upper bound: 6 rows x 7 columns
    const int ROWS_MAX = 6;
    const int START_LIVES = 3;
    const int TICK_MS = 33;              // ≈30fps
    const int BALL_R = 6;
    const int PADDLE_H = 12;

    // ── 颜色（0xAARRGGBB 的十进制负数形式）──
    // ── Colors (the decimal-negative form of 0xAARRGGBB) ──
    const int BG = -15724520;            // 0xFF101018 底色
    // 0xFF101018 background
    const int WALL = -15263713;          // 0xFF17181F 墙
    // 0xFF17181F wall
    const int PADDLE_C = -11409298;      // 0xFF51E86E 挡板
    // 0xFF51E86E paddle
    const int BALL_C = -1;               // 0xFFFFFFFF 球
    // 0xFFFFFFFF ball
    const int HUD_C = -6643536;          // 0xFF9AA0B0 次要文字
    // 0xFF9AA0B0 secondary text
    const int WHITE = -1;
    const int ACCENT = -11409298;        // 0xFF51E86E
    const int DARKTXT = -15724520;

    // 砖块按行分色（越靠下越硬、颜色越暖）
    // Bricks are colored by row (lower rows are tougher and warmer)
    const int B0 = -11890471;            // 0xFF4A90D9
    const int B1 = -6962941;             // 0xFF95A5B3
    const int B2 = -2509750;             // 0xFFD9B44A
    const int B3 = -2069424;             // 0xFFE06C50
    const int B4 = -2523025;             // 0xFFD9534F
    const int B5 = -7623434;             // 0xFF8B5CF6

    // ── 状态 ──
    // ── State ──
    static int[] bAlive = new int[MAXB];
    static int[] bHp = new int[MAXB];
    static int sw;
    static int sh;
    static int lang;

    static int score;
    static int best;
    static int lives;
    static int level;
    static int rows;                     // 本关砖块行数
    // brick rows in this level
    static int left;                     // 还剩几块砖
    // bricks still standing
    static int state;                    // 0=待发球 1=进行中 2=过关横幅 3=结束
    // 0=awaiting serve 1=playing 2=level-cleared banner 3=over
    static int banner;                   // 横幅还剩几拍
    // how many ticks the banner still lasts
    static int frames;
    static int acc;                      // 运动墙钟欠账（毫秒）—— 按墙钟补步，用户 2026-10-04 口径
    // Motion wall-clock debt in ms — steps are caught up from the wall clock (user's 2026-10-04 rule)
    static int lastMs;                   // 上次补步取的墙钟时刻（进主循环时初始化）
    // wall-clock moment of the last catch-up (initialized on entering the main loop)

    // ── 几何（开窗时算一次；画与命中判定共用同一套数）──
    // ── Geometry (computed once at open; drawing and hit-testing share the same numbers) ──
    static int fl;
    static int fr;
    static int ft;
    static int fb;
    static int bw;
    static int bh;
    static int btop;
    static int pw;
    static int px;
    static int py;

    // ── 球（整数像素/拍）──
    // ── Ball (integer pixels per tick) ──
    static int bx;
    static int by;
    static int vx;
    static int vy;

    // ── 触摸 ──
    // ── Touch ──
    // ⚠ 挡板跟手用的是**轮询**（`ui_touch_fetch/ui_touch_get_x`），不是事件。
    // ⚠ The paddle follows the finger by **polling** (`ui_touch_fetch/ui_touch_get_x`), not by events.
    //   理由：拖动是"连续量、只关心最新位置"，而事件队列会积压（实测一次拖动能涨到上千条，
    //   Reason: a drag is a continuous quantity and only its newest position matters, while the event
    //   触摸于是要等半天才反应）。轮询读的是**宿主此刻的手指状态**，积压与否都不影响。
    //   queue piles up (measured: one drag can grow it to four figures, after which touch reacts
    //   sluggishly). Polling reads the host's **current** finger state and is immune to that.
    //   事件只用来**发起球**（那是离散语义，一次点击算一次）。
    //   Events are used only to **serve** (a discrete action: one tap counts once).
    static int lastPx;

    // ── 事件文本（**每回合只拼一次**，不在逐帧路径上）──
    // ── Event text (**built once per event**, never on the per-frame path) ──
    // ⚠ 字符串的 `+` 是**真拼接**（v0.96.592 修的，用的是共享库的 strcat），但它**每次都
    // ⚠ The string `+` really is a **concatenation** now (fixed in v0.96.592, built on the shared
    //   要 alloc 一块**不回收**的内存** ⇒ 放进 `draw()` 里就是每帧漏几块。这里的规矩是：
    //    library's strcat), but **every one of them allocs a block that is never freed** ⇒ putting
    //    **逐帧要显示的数走 `drawNum`（逐位画、零分配），要拼的话就在事件里拼一次存进字段**。
    //    it inside `draw()` would leak a few blocks per frame. The rule here: **numbers drawn every
    //    frame go through `drawNum` (digit by digit, zero allocation); anything that needs
    //    concatenation is built once, in the event, and kept in a field.**
    static string bannerText;
    static string overText;
    static string liveText;

    static int primMax;

    static string T(string zh, string en)
    {
        if (lang == LANG_ZH) return zh;
        return en;
    }

    static string digitText(int d)
    {
        if (d == 0) return "0";
        if (d == 1) return "1";
        if (d == 2) return "2";
        if (d == 3) return "3";
        if (d == 4) return "4";
        if (d == 5) return "5";
        if (d == 6) return "6";
        if (d == 7) return "7";
        if (d == 8) return "8";
        return "9";
    }

    static int numDigits(int v)
    {
        if (v < 10) return 1;
        if (v < 100) return 2;
        if (v < 1000) return 3;
        if (v < 10000) return 4;
        if (v < 100000) return 5;
        return 6;
    }

    static int pow10(int n)
    {
        if (n <= 0) return 1;
        if (n == 1) return 10;
        if (n == 2) return 100;
        if (n == 3) return 1000;
        if (n == 4) return 10000;
        return 100000;
    }

    // 逐位画一个数（不拼字符串、不分配内存）。anchor：0=左 1=中 2=右
    // Draw a number digit by digit (no string building, no allocation). anchor: 0=left 1=centre 2=right
    static void drawNum(int x, int y, int v, int color, int size, int anchor)
    {
        int n = numDigits(v);
        int dw = size * 14 / 25;             // 字宽实测值，见 g2048.cs 的文件头
        // measured glyph width, see the file header of g2048.cs
        int w = n * dw;
        int startX = x;
        if (anchor == ANCHOR_CENTER) startX = x - w / 2;
        if (anchor == ANCHOR_RIGHT) startX = x - w;
        int q = v;
        if (q < 0) q = 0;
        int i = 0;
        while (i < n)
        {
            int d = (q / pow10(n - 1 - i)) % 10;
            ui_2d_text(startX + i * dw, y, digitText(d), color, size, ANCHOR_LEFT);
            i = i + 1;
        }
    }

    static int brickColor(int r)
    {
        if (r == 0) return B0;
        if (r == 1) return B1;
        if (r == 2) return B2;
        if (r == 3) return B3;
        if (r == 4) return B4;
        return B5;
    }

    static int brickX(int i)
    {
        return fl + (i % COLS) * bw;
    }

    static int brickY(int i)
    {
        return btop + (i / COLS) * bh;
    }

    // ════════════════════════════════════════════════════════════════════
    // 版面
    // Layout
    // ════════════════════════════════════════════════════════════════════
    static void layout()
    {
        int pad = sw / 24;
        if (pad < 6) pad = 6;
        fl = pad;
        fr = sw - pad;
        ft = 78;
        fb = sh - 74;
        bw = (fr - fl) / COLS;
        bh = 20;
        btop = ft + 10;
        pw = sw / 4;
        if (pw < 64) pw = 64;
        if (pw > fr - fl) pw = fr - fl;
        py = fb - 26;
        if (px < fl) px = fl;
        if (px + pw > fr) px = fr - pw;
    }

    // ════════════════════════════════════════════════════════════════════
    // 关卡
    // Levels
    // ════════════════════════════════════════════════════════════════════
    static void buildLevel()
    {
        rows = 3 + level;
        if (rows > ROWS_MAX) rows = ROWS_MAX;
        int n = rows * COLS;
        int i = 0;
        while (i < MAXB)
        {
            bAlive[i] = 0;
            bHp[i] = 0;
            i = i + 1;
        }
        i = 0;
        while (i < n)
        {
            bAlive[i] = 1;
            // 最下面一行要打两下（越往下越硬）
            // The bottom row takes two hits (lower rows are tougher)
            if (i / COLS == rows - 1 && level > 0) bHp[i] = 2;
            else bHp[i] = 1;
            i = i + 1;
        }
        left = n;
    }

    static int ballSpeed()
    {
        int s = 6 + level;
        if (s > 12) s = 12;
        return s;
    }

    static void serve()
    {
        state = 0;
        px = (fl + fr) / 2 - pw / 2;
        bx = px + pw / 2;
        by = py - BALL_R - 1;
        vx = 3;
        vy = 0 - ballSpeed();
        println_str("SERVE");
    }

    static void launch()
    {
        if (state != 0) return;
        state = 1;
        ui_audio_beep(660, 60);
        println_str("LAUNCH");
    }

    static void newGame()
    {
        score = 0;
        lives = START_LIVES;
        level = 0;
        banner = 0;
        bannerText = "";
        buildLevel();
        serve();
        println_str("NEWGAME");
    }

    static void levelClear()
    {
        level = level + 1;
        buildLevel();
        // 过关横幅**在这里拼一次**（不在 draw 里拼 —— 那样每帧都要 alloc）
        // The level banner is **built here, once** (not inside draw — that would alloc every frame)
        bannerText = T("过关！第 ", "LEVEL CLEAR!  Lv ") + (level + 1);
        banner = 34;
        state = 2;
        ui_audio_beep(1047, 200);
        println_str("LEVELCLEAR");
        print_str("LEVEL ");
        println_int(level + 1);
    }

    static void loseLife()
    {
        lives = lives - 1;
        print_str("LIFE ");
        println_int(lives);
        ui_audio_beep(131, 320);
        if (lives <= 0)
        {
            state = 3;
            // 结束语也**拼一次**存起来
            // The closing line is likewise built once and kept
            overText = T("三条命打完了 · 得分 ", "Out of lives  ·  score ") + score;
            println_str("OVER");
            draw();
            int a = ui_dlg_msg(T("打砖块", "Breakout"),
                               T("三条命打完了。\n再来一局？", "Out of lives.\nPlay again?"),
                               DLG_INFO);
            if (a != 0) { ui_win_close(); }
            newGame();
            return;
        }
        serve();
    }

    // ════════════════════════════════════════════════════════════════════
    // 绘制
    // Drawing
    // ════════════════════════════════════════════════════════════════════
    static void draw()
    {
        ui_2d_clear(BG);

        // ── 场地边框（三条墙 + 底边）──
        // ── Field frame (three walls + the bottom edge) ──
        ui_2d_rect(fl - 4, ft - 4, (fr - fl) + 8, 4, WALL, 1, 0, 0);
        ui_2d_rect(fl - 4, ft - 4, 4, (fb - ft) + 8, WALL, 1, 0, 0);
        ui_2d_rect(fr, ft - 4, 4, (fb - ft) + 8, WALL, 1, 0, 0);
        ui_2d_rect(fl - 4, fb, (fr - fl) + 8, 4, WALL, 1, 0, 0);

        // ── 砖块 ──
        // ── Bricks ──
        int i = 0;
        while (i < rows * COLS)
        {
            if (bAlive[i] != 0)
            {
                int c = brickColor(i / COLS);
                ui_2d_rect(brickX(i) + 1, brickY(i) + 1, bw - 3, bh - 3, c, 1, 0, 3);
                if (bHp[i] > 1) ui_2d_rect(brickX(i) + 5, brickY(i) + bh / 2 - 1, bw - 11, 3, WHITE, 1, 0, 0);
            }
            i = i + 1;
        }

        // ── 挡板 + 球 ──
        // ── Paddle + ball ──
        ui_2d_rect(px, py, pw, PADDLE_H, PADDLE_C, 1, 0, 5);
        ui_2d_rect(px + 3, py + 3, pw - 6, 3, WHITE, 1, 0, 2);
        ui_2d_circle(bx, by, BALL_R, BALL_C, 1, 0);
        ui_2d_circle(bx, by, BALL_R + 3, ACCENT, 0, 1);

        // ── HUD ──
        // ── HUD ──
        ui_2d_text(fl, 26, T("打砖块", "Breakout"), WHITE, 17, ANCHOR_LEFT);
        ui_2d_text(fl, 56, T("得分", "Score"), HUD_C, 11, ANCHOR_LEFT);
        drawNum(fl + 40, 56, score, ACCENT, 15, ANCHOR_LEFT);
        ui_2d_text(sw / 2 + 10, 56, T("关卡", "Level"), HUD_C, 11, ANCHOR_LEFT);
        drawNum(sw / 2 + 50, 56, level + 1, WHITE, 15, ANCHOR_LEFT);
        // 「命」这两个字摆在**那排圆点的左边**（右对齐在圆点左边一点）——
        // The two "lives" glyphs sit **to the left of the dot row** (right-aligned just left of the dots) -
        // 直接右对齐到 `fr` 会与圆点叠在一起（实测第一版就是这样）。
        // right-aligning straight to `fr` would overlap the dots (that is what the first version did).
        int h = 0;
        ui_2d_text(fr - lives * 16 - 6, 56, T("命", "Lives"), HUD_C, 11, ANCHOR_RIGHT);
        while (h < lives)
        {
            ui_2d_circle(fr - 12 - h * 16, 51, 5, ACCENT, 1, 0);
            h = h + 1;
        }

        // ── 底部提示 / 结束语 ──
        // ── Bottom hint / closing line ──
        if (state == 3)
        {
            ui_2d_text(sw / 2, sh - 46, overText, WHITE, 13, ANCHOR_CENTER);
        }
        else if (state == 0)
        {
            ui_2d_text(sw / 2, sh - 46, T("拖动屏幕移动挡板，点一下发球", "Drag to move the paddle, tap to serve"),
                    ACCENT, 13, ANCHOR_CENTER);
        }
        else
        {
            ui_2d_text(sw / 2, sh - 46, T("拖动屏幕（或方向键）移动挡板", "Drag (or use the arrows) to move the paddle"),
                    HUD_C, 12, ANCHOR_CENTER);
        }

        // ── 过关横幅 ──
        // ── Level-clear banner ──
        if (banner > 0)
        {
            int bwd = fr - fl - 30;
            ui_2d_rect(fl + 15, fb / 2 - 34, bwd, 68, -16777216, 1, 0, 12);
            ui_2d_text_v(sw / 2, fb / 2, bannerText, ACCENT, 18, ANCHOR_CENTER, VANCHOR_MIDDLE, FONT_BOLD);
        }

        // 每帧图元数（宿主侧真值）—— **只在新高时打印**，被超时杀掉的运行也留得下数
        // Primitives per frame (the host's own count) — printed **only when it grows**, so a run
        // killed by the timeout still leaves the number behind
        int np = ui_res_get_count(0);
        if (np > primMax)
        {
            primMax = np;
            print_str("PRIMAX ");
            println_int(primMax);
        }

        ui_2d_present();
    }

    // ════════════════════════════════════════════════════════════════════
    // 物理（整数像素/拍）
    // Physics (integer pixels per tick)
    // ════════════════════════════════════════════════════════════════════
    static void movePaddle(int x)
    {
        int nx = x - pw / 2;
        if (nx < fl) nx = fl;
        if (nx + pw > fr) nx = fr - pw;
        if (nx != px)
        {
            // `Math.Abs` **走的是前端新增的“标准方法 → 库标签”映射**（共享库的 `abs`，
            // `Math.Abs` **goes through the frontend's new "standard method -> library label" mapping** (the shared `abs`,
            // 与手写 `if (d < 0) d = 0 - d;` 等价 —— v0.96.592 修）。
            // equivalent to a hand-written `if (d < 0) d = 0 - d;` - fixed in v0.96.592).
            int d = Math.Abs(nx - px);
            px = nx;
            if (d >= 12)
            {
                // 跟随日志（只在真的动了一截时打，免得刷屏）
                // Follow log (printed only on a real move, to avoid flooding)
                print_str("PADDLE ");
                println_int(px);
                lastPx = px;
            }
        }
        if (state == 0)
        {
            bx = px + pw / 2;
            by = py - BALL_R - 1;
        }
    }

    static void tick()
    {
        frames = frames + 1;
        if (banner > 0) banner = banner - 1;
        if (state == 2 && banner == 0)
        {
            state = 0;
            serve();
        }

        // 触摸：**轮询**手指此刻在哪（拖动只关心最新位置，见 `lastPx` 上面的说明）
        // Touch: **poll** where the finger is right now (a drag only cares about the newest position,
        // see the note above `lastPx`)
        if (ui_touch_fetch(0) != 0 && ui_touch_is_down() != 0) movePaddle(ui_touch_get_x());

        // 键盘兜底：按住方向键持续移动（`ui_key_is_down` 是"此刻按着没"）
        // Keyboard fallback: hold an arrow to keep moving (`ui_key_is_down` is "is it held right now")
        if (ui_key_is_down(KEY_LEFT) != 0) movePaddle(px - 9 + pw / 2);
        if (ui_key_is_down(KEY_RIGHT) != 0) movePaddle(px + 9 + pw / 2);

        if (state != 1) return;

        bx = bx + vx;
        by = by + vy;

        // 左右墙
        // Side walls
        if (bx - BALL_R < fl)
        {
            bx = fl + BALL_R;
            vx = 0 - vx;
            ui_audio_beep(330, 30);
            println_str("WALLX");
        }
        if (bx + BALL_R > fr)
        {
            bx = fr - BALL_R;
            vx = 0 - vx;
            ui_audio_beep(330, 30);
            println_str("WALLX");
        }
        // 顶
        // Top
        if (by - BALL_R < ft)
        {
            by = ft + BALL_R;
            vy = 0 - vy;
            ui_audio_beep(330, 30);
            println_str("WALLY");
        }

        // ── 挡板 ──
        // ── Paddle ──
        if (vy > 0 && by + BALL_R >= py && by - BALL_R <= py + PADDLE_H &&
            bx + BALL_R >= px && bx - BALL_R <= px + pw)
        {
            by = py - BALL_R;
            // 打在挡板哪一段决定反弹的横速度：中间直、边缘斜
            // Where the ball lands on the paddle sets its horizontal speed: middle straight, edges angled
            int mid = px + pw / 2;
            int off = bx - mid;
            int span = pw / 2;
            if (span < 1) span = 1;
            int nvx = off * 5 / span;            // −5..+5
            if (nvx > 4) nvx = 4;
            if (nvx < -4) nvx = -4;
            if (nvx == 0) nvx = 1;               // 永不垂直（垂直会变成死循环弹不出去）
            // never vertical (vertical becomes an endless bounce and can never escape)
            vx = nvx;
            vy = 0 - ballSpeed();
            ui_audio_beep(440, 45);
            print_str("PADDLEHIT ");
            println_int(off);
        }

        // ── 砖块 ──
        // ── Bricks ──
        int i = 0;
        while (i < rows * COLS)
        {
            if (bAlive[i] != 0)
            {
                int rx = brickX(i);
                int ry = brickY(i);
                if (bx + BALL_R > rx && bx - BALL_R < rx + bw - 2 &&
                    by + BALL_R > ry && by - BALL_R < ry + bh - 2)
                {
                    // 穿入较浅的那个轴才是要反射的轴
                    // The axis with the shallower penetration is the one to reflect
                    int ox = (rx + bw - 2) - (bx - BALL_R);
                    int t2 = (bx + BALL_R) - rx;
                    if (t2 < ox) ox = t2;
                    int oy = (ry + bh - 2) - (by - BALL_R);
                    int t3 = (by + BALL_R) - ry;
                    if (t3 < oy) oy = t3;
                    if (ox < oy) { vx = 0 - vx; bx = bx + vx; }
                    else { vy = 0 - vy; by = by + vy; }

                    bHp[i] = bHp[i] - 1;
                    if (bHp[i] <= 0)
                    {
                        bAlive[i] = 0;
                        left = left - 1;
                    }
                    score = score + 10;
                    if (score > best) best = score;
                    ui_audio_beep(880, 35);
                    print_str("BRICK ");
                    print_int(i);
                    print_str(" LEFT ");
                    println_int(left);
                    if (left == 0) { levelClear(); return; }
                    return;                          // 一拍最多处理一块砖（防一次穿多块）
                    // one brick per tick at most (stops the ball passing through several)
                }
            }
            i = i + 1;
        }

        // 掉底
        // Fell past the bottom
        if (by - BALL_R > fb)
        {
            loseLife();
        }
    }

    // ════════════════════════════════════════════════════════════════════
    // 入口
    // Entry point
    // ════════════════════════════════════════════════════════════════════
    static void Main()
    {
        lang = ui_scr_get_language();
        // 开窗：尺寸传 0 ⇒ 交给宿主给真实可用区（开窗**之前**问到的只是估算值）
        // Opening: pass size 0 ⇒ the host supplies the real available area (a **pre-open** read is only an estimate)
        ui_win_open_ex(T("打砖块", "Breakout"), 0, 0, WIN_PORTRAIT, WIN_NO_GAMEPAD);
        ui_scr_set_keep_on(1);

        // 尺寸：开窗**之后**再问一次 —— 这时 `ui_scr_get_w/h` 给的是实测画布（真值）
        // Size: re-read **after** the open — `ui_scr_get_w/h` now report the measured canvas (the real value)
        sw = ui_scr_get_w();
        sh = ui_scr_get_h();
        if (sw <= 0) sw = 411;
        if (sh <= 0) sh = 726;
        layout();
        newGame();
        draw();

        int tid = ui_timer_set(TICK_MS, 0);
        acc = 0;
        lastMs = ui_time_get_ms();   // 墙钟起点：进主循环时取（不在模块级/全局初始化里调 syscall）
        // Wall-clock origin: taken when entering the main loop (no syscall at module level or in a global initializer)
        while (ui_win_is_closed() == 0)
        {
            int t = ui_msg_fetch_wait(TICK_MS);

            // 这一帧该走几步：**按墙钟欠账**（各平台定时器分辨率不同 ⇒ 不能拿"收到几拍"当时间）。
            // How many steps this frame: **by wall-clock debt** (timer resolution differs per platform, so
            // 用户 2026-10-04 口径：所有游戏时钟取系统毫秒数。
            // "how many ticks arrived" is not a measure of time).
            // User rule of 2026-10-04: every game clock reads system milliseconds.
            int now2 = ui_time_get_ms();
            acc = acc + (now2 - lastMs);
            lastMs = now2;
            if (acc > TICK_MS * 8) acc = 0;  // 卡太久别一次补一大串（防瞬移）
            // Do not catch up in one huge burst after a long stall (anti-teleport)
            if (acc >= TICK_MS)
            {
                while (acc >= TICK_MS) { acc = acc - TICK_MS; tick(); }
                draw();
            }

            if (t == MSG_NONE) continue;
            if (t == MSG_WINDOWCLOSE) break;
            if (t == MSG_WINDOWRESIZE)
            {
                sw = ui_msg_get_a();
                sh = ui_msg_get_b();
                if (sw <= 0) sw = 411;
                if (sh <= 0) sh = 726;
                layout();
                draw();
                continue;
            }
            if (t == MSG_TIMER)
            {
                // 定时器**只负责唤醒**（推进在上面按墙钟欠账走）
                // The timer **only wakes us up** — the advance happens above, by wall-clock debt
                continue;
            }
            if (t == MSG_TOUCHDOWN)
            {
                launch();
                draw();
                continue;
            }
            if (t == MSG_TOUCHMOVE)
            {
                // 位置靠轮询拿（见 `lastPx` 上面的说明），这类消息本身没有价值
                // the position is polled instead (see the note above `lastPx`); this kind of message carries no value in itself
                // ⇒ 直接丢掉，免得队列只涨不落（`ui_msg_drop(1)` = 丢掉"触摸"这一类）
                // The position comes from polling (see the note above `lastPx`), so these messages carry
                // nothing ⇒ drop them, so the queue cannot grow without bound (`ui_msg_drop(1)` drops
                // the "touch" kind)
                ui_msg_drop(1);
                continue;
            }
            if (t == MSG_TOUCHUP) continue;
            if (t == MSG_KEYDOWN)
            {
                int k = ui_msg_get_a();
                if (k == KEY_ESCAPE) break;
                if (k == KEY_SPACE || k == KEY_ENTER || k == KEY_PAD_A)
                {
                    if (state == 3) newGame();
                    else launch();
                    draw();
                }
                else if (k == KEY_LEFT) { movePaddle(px - 18 + pw / 2); draw(); }
                else if (k == KEY_RIGHT) { movePaddle(px + 18 + pw / 2); draw(); }
                continue;
            }
        }

        ui_timer_kill(tid);
        ui_scr_set_keep_on(0);
        ui_win_close();
        print_str("PRIMAX-FINAL ");
        println_int(primMax);
        println_str("breakout: done");
    }
}
