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()