ruby/test/catch.rb · 7653 字节 · 下载原文
# 接方块 —— 用 **Ruby** 写的手机游戏
# Catch the ball — a mobile game written in **Ruby**
#
# 玩法:左右方向键移动底部挡板,把落下来的球弹回去;没接住就结束。每接住一次 +10 分。
# Gameplay: left/right arrow keys move the paddle at the bottom, bouncing the falling ball back; miss it and the game ends. Each catch scores +10 points.
#
# ◆ 手机那套 UI
# ◆ The phone UI stack
#
# 开窗 / 绘图 / 输入 / 定时器是 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="ruby" Libs="vmlui.vml">` 挂上来。
# pulled in by `vmltool.config.xml`'s `<Language Name="ruby" Libs="vmlui.vml">`.
#
# ◆ 为什么这份是**完全平铺**的(没有 def、逻辑内联在主循环里)
# ◆ Why this file is **completely flat** (no def, the logic inlined into the main loop)
#
# 两条本前端实测出来的限制(2026-09-17):
# Two limits measured in practice on this frontend (2026-09-17):
# ① **词法器不认 `&&`** —— `Unexpected char: &`。多个条件只能写成嵌套 `if`。
# ① **The lexer does not accept `&&`** — `Unexpected char: &`. Multiple conditions can only be written as nested `if`.
# ② **`def` 的支持有问题** —— 一个最朴素的 `def resetGame … end` 会报
# ② **`def` support is broken** — the plainest `def resetGame … end` reports
# `Unexpected token: End(end)`(报在配对的 `end` 上,像是块没被正确打开)。
# `Unexpected token: End(end)` (reported on the matching `end`, as if the block was never opened properly).
# `corpus/ruby/skel.rb` 里**一个函数都没有**,所以这条从来没被骨架覆盖到。
# `corpus/ruby/skel.rb` has **not a single function**, so this item was never covered by the skeleton.
# ⇒ 本份例程不用任何方法,状态放顶层局部变量、draw/tick 内联进主循环。
# ⇒ This example uses no methods at all: state lives in top-level locals, draw/tick are inlined into the main loop.
# 这两条要修的话在前端(`RubyCompiler`),不是例程的问题。
# Fixing these two belongs in the frontend (`RubyCompiler`), not in the example.
#
# 另一处写法:颜色写负数十进制;裸调库函数不写声明(同 skel.rb)。
# Another convention: colors are written as negative decimals; library functions are called bare with no declaration (same as skel.rb).
# 状态: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 width 9=screen height
# UI 库的显式引用(用户 2026-10-01 定的口径,见 docs/UI库导入约定.md)。
# Explicit reference to the UI library (rule set by the user on 2026-10-01; see the UI-library import convention doc under docs).
# 声明文件 Lib/ruby/vml_ui.rb 由 GenLib 生成、不要手改:
# dotnet run --project tools/GenLib -- -u -c vmlui -l ruby -o Lib/ruby/vml_ui.rb
# ⚠ 实测:本条是**文档级**引用 —— 删掉它示例照样编得过、跑得对(Ruby 按名字解析)。
# ⚠ Measured: this line is a **documentation-level** reference — delete it and the example still compiles and runs (Ruby resolves by name).
require 'vml_ui'
a0 = 0
a1 = 0
a2 = 0
a3 = 0
a4 = 0
a5 = 0
a6 = 0
a7 = 0
lang = ui_scr_get_language()
# ⚠ **先开窗、再问尺寸**:开窗前 `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 then ui_win_open("接方块", 0, 0) else ui_win_open("Catch", 0, 0) end
w = ui_scr_get_w()
h = ui_scr_get_h()
if w <= 0
w = 360
end
if h <= 0
h = 620
end
ui_scr_set_keep_on(1)
a0 = w / 2 - 40
a1 = w / 2
a2 = 70
a3 = 3
a4 = 5
a5 = 0
a6 = 0
a7 = 1
tid = ui_timer_set(40, 0)
while ui_win_is_closed() == 0
# ── draw ──
ui_2d_clear(-15724520)
if lang == 0 then ui_2d_text(8, 8, "得分", -6643536, 13, VML_ANCHOR_LEFT) else ui_2d_text(8, 8, "Score", -6643536, 13, VML_ANCHOR_LEFT) end
ui_2d_rect(58, 11, a5, 10, -11409298, 1, 0, 0)
if lang == 0 then ui_2d_text(w / 2, 8, "最高", -6643536, 13, VML_ANCHOR_CENTER) else ui_2d_text(w / 2, 8, "Best", -6643536, 13, VML_ANCHOR_CENTER) end
ui_2d_rect(w / 2 + 46, 11, a6, 10, -63488, 1, 0, 0)
ui_2d_rect(a0, h - 40, 80, 12, -63488, 1, 0, 6)
ui_2d_circle(a1, a2, 9, -131246, 1, 0)
if a7 == 0
if lang == 0 then ui_2d_text(w / 2, h / 2, "按回车重开", -131246, 16, VML_ANCHOR_CENTER) else ui_2d_text(w / 2, h / 2, "Press Enter to restart", -131246, 16, VML_ANCHOR_CENTER) end
end
ui_2d_present()
t = ui_msg_fetch_wait(0)
if t == 10
break
end
if t == 9
if a7 != 0
a1 = a1 + a3
a2 = a2 + a4
if a1 < 10
a1 = 10
a3 = 0 - a3
end
if a1 > w - 10
a1 = w - 10
a3 = 0 - a3
end
if a2 < 30
a2 = 30
a4 = 0 - a4
end
# ⚠ 不认 `&&` ⇒ 嵌套 if
# ⚠ no `&&` ⇒ nested if
if a2 > h - 52
if a2 < h - 30
if a1 > a0 - 9
if a1 < a0 + 89
a4 = 0 - a4
a2 = h - 52
a5 = a5 + 10
if a5 > a6
a6 = a5
end
# 音效:单音 ui_audio_beep(v0.96.509 从音序器换回来 ——
# Sound: single-tone ui_audio_beep (v0.96.509 switched back from the sequencer —
# 那一版多声部叠加 / 长音拖尾在真机上破音)
# that version's multi-voice layering / long note tails crackled on real devices)
ui_audio_beep(1047, 165)
end
end
end
end
if a2 > h
a7 = 0
# 音效:单音 ui_audio_beep;**结局音取最低音**(接住 1047 / 没接住 131,差得开)
# Sound: single-tone ui_audio_beep; **the ending tone takes the lowest pitch** (caught 1047 / missed 131, far apart)
ui_audio_beep(131, 320)
if lang == 0 then r = ui_dlg_msg("接方块", "没接住,这一局结束。\n再来一局?(选「否」退出)", VML_DLG_INFO) else r = ui_dlg_msg("Catch", "Missed. Round over.\nPlay again? (choose 'No' to quit)", VML_DLG_INFO) end
if r != 0
ui_win_close()
break
end
a0 = w / 2 - 40
a1 = w / 2
a2 = 70
a3 = 3
a4 = 5
a5 = 0
a7 = 1
end
end
end
if t == 1
k = ui_msg_get_a()
if k == 27
break
end
if k == 37
a0 = a0 - 20
if a0 < 4
a0 = 4
end
end
if k == 39
a0 = a0 + 20
if a0 > w - 84
a0 = w - 84
end
end
if k == 13
a0 = w / 2 - 40
a1 = w / 2
a2 = 70
a3 = 3
a4 = 5
a5 = 0
a7 = 1
end
end
end
ui_timer_kill(tid)
ui_scr_set_keep_on(0)
ui_win_close()