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

r/test/catch.r · 8278 字节 · 下载原文

#include "../../../Lib/r/vml_ui.r"

# 接方块 —— 用 **R** 写的手机游戏

# Catch -- a mobile game written in **R**

#

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

# Gameplay: the left/right arrow keys move the paddle at the bottom and bounce the falling ball back; missing it ends the round. Each catch = +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="r" Libs="vmlui.vml">` 挂上来。

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

#

# ◆ 修掉的那条前端缺陷(2026-09-17)

# ◆ The frontend defect that was fixed (2026-09-17)

#

# `GenerateFuncDef` 此前是**写死的 `EmitPrologueWithFrame(64)`** —— 每个函数只给 64 字节帧,

# `GenerateFuncDef` used to be a **hard-coded `EmitPrologueWithFrame(64)`** -- every function got only a 64-byte frame,

# 而局部量按 `[R12-4]、[R12-8]…` 分配、`R12 == R13`,**超出的部分直接写进调用方的帧**。

# while locals were allocated as `[R12-4]`, `[R12-8]`, ... with `R12 == R13`, so **anything beyond that was written straight into the caller's frame**.

# 有意思的是同一个文件里,**顶层**(`GenerateCode`)早就改成了「序言占位 + 生成完回填」,

# Interestingly, in the same file the **top level** (`GenerateCode`) had already been changed to "reserve a prologue placeholder + backfill when done",

# 只有函数那条路还留着写死的 64 —— 两个口径并存。

# and only the function path still kept the hard-coded 64 -- two conventions coexisting.

# 现已统一成回填式;判据是一个含 20 个局部量(80 字节 > 64)的函数:修前崩、修后得 210 ✓。

# It is now unified to the backfill form; the criterion is a function with 20 locals (80 bytes > 64): it crashed before the fix and returns 210 after it.

# 骨架照不出来,因为骨架的函数都极小。

# The skeleton cannot show this, because all of its functions are tiny.

# ↳ 2026-10-04 复验仍成立:20 个局部量(80 字节 > 64)的函数实测得 `LOCAL20=210`(与判据一致)。

# ↳ Still holds, re-checked 2026-10-04: a function with 20 locals (80 bytes > 64) measures `LOCAL20=210` (matches the criterion).

#

# ◆ 写法要求(沿用 corpus/r/skel.r)

# ◆ Style requirements (following corpus/r/skel.r)

#

#   · 循环**不写** `for (i in 1:4)`:`:` 被解析成 `CallNode(":")`、GenerateCall 无此分支

#   · Do **not** write `for (i in 1:4)`: `:` is parsed into `CallNode(":")` and GenerateCall has no branch for it

#     ⇒ 编出 `CALL func_:`(带冒号的标签,永不解析)。用 `c(1,2,3,4)`,循环变量拿到元素值。

#     => it compiles to `CALL func_:` (a label with a colon, which never resolves). Use `c(1,2,3,4)`; the loop variable takes the element value.

#   · R 向量 **1 基**,下标写 1..n。

#   · R vectors are **1-based**; subscripts are written 1..n.

#   · `cat` / `print` 只打印**第一个实参** ⇒ 分数画成图形,不走控制台。

#   · `cat` / `print` print only the **first argument** => the score is drawn as a graphic instead of going to the console.

#   · 颜色写负数十进制(词法器不认 0x)。

#   · Colors are written as negative decimals (the lexer does not accept 0x).


# 状态:1=挡板x 2=球x 3=球y 4=球dx 5=球dy 6=分数 7=最高 8=存活 9=屏宽 10=屏高

# State: 1=paddle x 2=ball x 3=ball y 4=ball dx 5=ball dy 6=score 7=best 8=alive 9=screen w 10=screen h

lang <- ui_scr_get_language()
A <- c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

resetGame <- function() {
  A[1] <<- A[9] / 2 - 40
  A[2] <<- A[9] / 2
  A[3] <<- 70
  A[4] <<- 3
  A[5] <<- 5
  A[6] <<- 0
  A[8] <<- 1
}

draw <- function() {
  ui_2d_clear(-15724520)
  if (lang == 0) ui_2d_text(8, 8, "得分", -6643536, 13, VML_ANCHOR_LEFT) else ui_2d_text(8, 8, "Score", -6643536, 13, VML_ANCHOR_LEFT)
  ui_2d_rect(58, 11, A[6], 10, -11409298, 1, 0, 0)
  if (lang == 0) ui_2d_text(A[9] / 2, 8, "最高", -6643536, 13, VML_ANCHOR_CENTER) else ui_2d_text(A[9] / 2, 8, "Best", -6643536, 13, VML_ANCHOR_CENTER)
  ui_2d_rect(A[9] / 2 + 46, 11, A[7], 10, -63488, 1, 0, 0)
  ui_2d_rect(A[1], A[10] - 40, 80, 12, -63488, 1, 0, 6)
  ui_2d_circle(A[2], A[3], 9, -131246, 1, 0)
  if (A[8] == 0) {
    if (lang == 0) ui_2d_text(A[9] / 2, A[10] / 2, "按回车重开", -131246, 16, VML_ANCHOR_CENTER) else ui_2d_text(A[9] / 2, A[10] / 2, "Press Enter to restart", -131246, 16, VML_ANCHOR_CENTER)
  }
  ui_2d_present()
}

tick <- function() {
  if (A[8] == 0) {
    return(0)
  }
  A[2] <<- A[2] + A[4]
  A[3] <<- A[3] + A[5]
  if (A[2] < 10) {
    A[2] <<- 10
    A[4] <<- 0 - A[4]
  }
  if (A[2] > A[9] - 10) {
    A[2] <<- A[9] - 10
    A[4] <<- 0 - A[4]
  }
  if (A[3] < 30) {
    A[3] <<- 30
    A[5] <<- 0 - A[5]
  }
  # 接住:球落到挡板带上、且横向落在挡板范围内(多条件用嵌套 if —— 本前端对 && 的支持未验证)

  # Catch: the ball lands on the paddle band and is horizontally within the paddle (multiple conditions use nested if -- this frontend's support for && is unverified)

  if (A[3] > A[10] - 52) {
    if (A[3] < A[10] - 30) {
      if (A[2] > A[1] - 9) {
        if (A[2] < A[1] + 89) {
          A[5] <<- 0 - A[5]
          A[3] <<- A[10] - 52
          A[6] <<- A[6] + 10
          if (A[6] > A[7]) {
            A[7] <<- A[6]
          }
          # 音效:单音 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 layering / long-note tails crackled on the real device)

          ui_audio_beep(1047, 165)
        }
      }
    }
  }
  if (A[3] > A[10]) {
    A[8] <<- 0
    # 音效:单音 ui_audio_beep;**结局音取最低音**(接住 1047 / 没接住 131,差得开)

    # Sound: single-tone ui_audio_beep; **the ending tone takes the lowest pitch** (catch 1047 / miss 131, far enough apart)

    ui_audio_beep(131, 320)
    draw()
    if (lang == 0) ans <- ui_dlg_msg("接方块", "没接住,这一局结束。\n再来一局?(选「否」退出)", VML_DLG_INFO) else ans <- ui_dlg_msg("Catch", "Missed. Round over.\nPlay again? (choose 'No' to quit)", VML_DLG_INFO)
    if (ans != 0) { ui_win_close(); return(0) }
    resetGame()
  }
  return(0)
}

# ── 主流程 ──

# -- Main flow --

# ⚠ **先开窗、再问尺寸**:开窗前 `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.

if (lang == 0) ui_win_open("接方块", 0, 0) else ui_win_open("Catch", 0, 0)
A[9] <- ui_scr_get_w()
A[10] <- ui_scr_get_h()
if (A[9] <= 0) {
  A[9] <- 360
}
if (A[10] <= 0) {
  A[10] <- 620
}
ui_scr_set_keep_on(1)
resetGame()
tid <- ui_timer_set(40, 0)

while (ui_win_is_closed() == 0) {

  draw()
  t <- ui_msg_fetch_wait(0)
  if (t == 10) {
    break
  }
  if (t == 9) {
    tick()
  }
  if (t == 1) {
    k <- ui_msg_get_a()
    if (k == 27) {
      break
    }
    if (k == 37) {
      A[1] <- A[1] - 20
      if (A[1] < 4) {
        A[1] <- 4
      }
    }
    if (k == 39) {
      A[1] <- A[1] + 20
      if (A[1] > A[9] - 84) {
        A[1] <- A[9] - 84
      }
    }
    if (k == 13) {
      resetGame()
    }
  }
}

ui_timer_kill(tid)
ui_scr_set_keep_on(0)
ui_win_close()