{ 接方块 —— 用 **Pascal** 写的手机游戏
  Catch -- a mobile game written in Pascal

  玩法：左右方向键移动底部挡板，把落下来的球弹回去；没接住就结束。每接住一次 +10 分。
  Gameplay: move the bottom paddle with the left/right arrow keys and bounce the falling ball back; missing it ends the game. Each catch scores +10.

  ◆ 手机那套 UI
  * The mobile UI set

  开窗 / 绘图 / 输入 / 定时器是 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="pascal" Libs="vmlui.vml">` 挂上来。
  pulled in by the vmltool.config.xml entry Language Name="pascal" Libs="vmlui.vml".

  ◆ 为什么 Pascal 这份可以按「全局数组 + 无参过程」自然写
  * Why this Pascal version can be written naturally as global array + parameterless procedures

  别的语言（Kotlin / Swift / Go / Python）都有「模块级数组基址不对」或「函数看不见它」
  Other languages (Kotlin / Swift / Go / Python) all have problems like a wrong module-level array base address or
  这类问题，只能把状态塞进 main 或内联。Pascal 的 `var` 段全局量是语言原生语义、
  a function not seeing it, so state has to be pushed into main or inlined. Pascal's var-section globals are native language semantics and
  过程直接可见，所以这份保留了最自然的结构 —— 也正因为如此，它是**结构上最接近
  are directly visible to procedures, so this version keeps the most natural structure; because of that it is the structurally closest
  C 版**的一份，适合当对照。
  to the C version and makes a good reference.

  ◆ 写法要求
  * Coding rules

    · 裸调库函数不写声明（同 corpus/pascal/skel.pas）。
  . call library functions without declarations (same as corpus/pascal/skel.pas).
    · 整除用 `div`；颜色写负数十进制。
  . use div for integer division; write colours as negative decimals.
    · 过程名不叫 `step`：Pascal 标准过程里已有 `Inc()` 这类，避开常见名免得撞上。
  . do not name a procedure step: Pascal already has standard procedures like Inc(), so avoid common names to prevent collisions. }

program Catch;

{ `ui_*`（窗口 / 图元 / 文本 / 帧呈现 / 计时）声明在 `Lib/pascal/vml_ui.pas`。
  The ui_* host UI library (window / primitives / text / present / ticks) is declared in
  ⚠ 必需而非装饰：零参/无括号的 `ui_*`（`ui_scr_get_w`、`ui_2d_present`、`ui_time_get_ms`…）
  Lib/pascal/vml_ui.pas. WARNING: required, not decoration -- a zero-arg ui_* call has the
  在 Pascal 里与变量同形，只能靠 `uses` 拉进来的**声明**分辨"这是调用"。
  same syntax as a variable, so only the declaration brought in by `uses` marks it as a
  裸写那种写法删掉这一行就报「未声明的变量」（实测）。
  call -- written that way -- reports an undeclared variable without this line (measured).
  （本文件的 `ui_*` 恰好全带括号，但裸写是 Pascal 的常态写法，这条 `uses` 留着。）
  (every ui_* call here happens to be parenthesised, but the bare form is idiomatic
  Pascal, so the reference stays.) }
uses vml_ui;

var
  { 状态：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 w 9=screen h
  A: array[0..9] of integer;

  { 界面语言：0 = 中文 / 1 = 英文（ui_scr_get_language 是 syscall，开局查一次存进 lang，
    UI language: 0 = Chinese / 1 = English (ui_scr_get_language is a syscall: query it once at startup into lang,
    文案也在这里一次算好；之后每帧绘制只用变量）
    and compute the strings here too; every later frame only uses the variables) }
  lang: integer;
  sScore, sBest, sRestart, sTitle, sOver: string;

procedure initLang();
begin
  lang := ui_scr_get_language();
  if lang = 0 then sScore := '得分' else sScore := 'Score';
  if lang = 0 then sBest := '最高' else sBest := 'Best';
  if lang = 0 then sRestart := '按回车重开' else sRestart := 'Press Enter to restart';
  if lang = 0 then sTitle := '接方块' else sTitle := 'Catch';
  if lang = 0 then sOver := '没接住，这一局结束。再来一局？（选「否」退出）' else sOver := 'Missed - this round is over. Play again? (choose No to quit)';
end;

procedure resetGame();
begin
  A[0] := A[8] div 2 - 40;
  A[1] := A[8] div 2;
  A[2] := 70;
  A[3] := 3;
  A[4] := 5;
  A[5] := 0;
  A[7] := 1;
end;

procedure draw();
begin
  ui_2d_clear(-15724520);
  ui_2d_text(8, 8, sScore, -6643536, 13, VML_ANCHOR_LEFT);
  ui_2d_rect(58, 11, A[5], 10, -11409298, 1, 0, 0);
  ui_2d_text(A[8] div 2, 8, sBest, -6643536, 13, VML_ANCHOR_CENTER);
  ui_2d_rect(A[8] div 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 then
  begin
    ui_2d_text(A[8] div 2, A[9] div 2, sRestart, -131246, 16, VML_ANCHOR_CENTER);
  end;
  ui_2d_present();
end;

procedure tick();
begin
  if A[7] = 0 then
  begin
    exit;
  end;
  A[1] := A[1] + A[3];
  A[2] := A[2] + A[4];
  if A[1] < 10 then
  begin
    A[1] := 10;
    A[3] := 0 - A[3];
  end;
  if A[1] > A[8] - 10 then
  begin
    A[1] := A[8] - 10;
    A[3] := 0 - A[3];
  end;
  if A[2] < 30 then
  begin
    A[2] := 30;
    A[4] := 0 - A[4];
  end;
  { 接住：球落到挡板带上、且横向落在挡板范围内 }
  // caught: the ball reaches the paddle band and lands horizontally within the paddle span
  if (A[2] > A[9] - 52) and (A[2] < A[9] - 30) then
  begin
    if (A[1] > A[0] - 9) and (A[1] < A[0] + 89) then
    begin
      A[4] := 0 - A[4];
      A[2] := A[9] - 52;
      A[5] := A[5] + 10;
      if A[5] > A[6] then
      begin
        A[6] := A[5];
      end;
      // 音效：单音 ui_audio_beep（v0.96.509 从音序器换回来 ——
      // Sound effect: single-tone ui_audio_beep (switched back from the sequencer in v0.96.509 —
      //   那一版多声部叠加 / 长音拖尾在真机上破音）
      //   that version's multi-voice layering / long sustained tones broke up on real devices)
      ui_audio_beep(1047, 165);
    end;
  end;
  if A[2] > A[9] then
  begin
    A[7] := 0;
    // 音效：单音 ui_audio_beep；**结局音取最低音**（接住 1047 / 没接住 131，差得开）
    // Sound effect: single-tone ui_audio_beep; **the ending tone takes the lowest note** (caught 1047 / missed 131, far enough apart to tell)
    ui_audio_beep(131, 320);
    draw();
    if ui_dlg_msg(sTitle, sOver, VML_DLG_INFO) <> 0 then begin ui_win_close(); exit; end;
    resetGame();
  end;
end;

var
  w, h, tid, t, k: integer;

begin
  initLang();
  { ⚠ **先开窗、再问尺寸**：开窗前 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(sTitle, 0, 0);
  w := ui_scr_get_w();
  h := ui_scr_get_h();
  if w <= 0 then
  begin
    w := 360;
  end;
  if h <= 0 then
  begin
    h := 620;
  end;
  A[8] := w;
  A[9] := h;
  ui_scr_set_keep_on(1);
  resetGame();
  tid := ui_timer_set(40, 0);

  while ui_win_is_closed() = 0 do

  begin
    draw();
    t := ui_msg_fetch_wait(0);
    if t = 10 then
    begin
      break;
    end;
    if t = 9 then
    begin
      tick();
    end;
    if t = 1 then
    begin
      k := ui_msg_get_a();
      if k = 27 then
      begin
        break;
      end;
      if k = 37 then
      begin
        A[0] := A[0] - 20;
        if A[0] < 4 then
        begin
          A[0] := 4;
        end;
      end;
      if k = 39 then
      begin
        A[0] := A[0] + 20;
        if A[0] > w - 84 then
        begin
          A[0] := w - 84;
        end;
      end;
      if k = 13 then
      begin
        resetGame();
      end;
    end;
  end;

  ui_timer_kill(tid);
  ui_scr_set_keep_on(0);
  ui_win_close();
end.
