README.md · 15050 字节 · 下载原文
# 示例与游戏合集
手机 App 启动时会把这些示例解包到 **`<工作区>/examples/<语言>/`**
(`MauiBootstrap.EnsureExamples`),在「文件」页点开就能编译运行:
```bash
vml run examples/c/tetris.c # 各语言各自一个目录:c/ python/ basic/ csharp/ java/ …
```
**目录就是语言**:`examples/` 下每个子目录是一种语言,目录里放该语言的示例 ——
从前是平铺的(十来种语言堆在一个目录里,只能靠文件名猜语言),v0.96.184 起按语言分开。
## 分类
每种语言目录下**再按用途分一层**(2026-10-02 整理)—— 从前所有示例平铺在
`examples/<语言>/` 里,几十个文件混着演示、自检与游戏,找东西只能靠文件名猜:
| 子目录 | 放什么 | 例 |
|---|---|---|
| `demo/` | **标准输出**示例(各语言的"hello 级别") | `examples/c/demo/demo_std.c` |
| `test/` | **接口自检**:跑了要能判断对错的那种(不是演示) | `examples/c/test/sysinfo.c`、`catch.*`、`bench.*` |
| `ui/` | **宿主 UI**(`ui_*`)示例 | `examples/c/ui/demo_ui.c`、`draw_*.c` |
| `tty/` | **ANSI 终端**示例(光标/颜色/清屏) | `examples/c/tty/demo_tty.c`、`conio_*`、`curses_*` |
| `bgi/` | **BGI 图形**(老程序那套 `graphics.h`) | `examples/c/bgi/demo_bgi.c` |
| `3d/` | **3D**(`g3_*` 号段) | `examples/c/3d/solar3d.c`、`g3_all.c` |
| `app/` | **能玩的程序**(游戏与工具) | `examples/c/app/tetris.c`、`gorilla.*` |
| (语言目录根) | 说明书 / 许可证 / 构建脚本 | `CREDITS.md`、`LICENSES.txt`、`c/c.bat` |
**已有的语料目录原地不动**(`cpp/stm32/`、`c/old/`、`basic/freebasic/` 这类是"整块第三方素材",
不是按用途分的类;`pascal/swag_*` 那一批老程序语料也**保持在语言目录根**,
因为打包脚本的排除正则就是按 `Examples/pascal/<前缀>` 写的)。
⚠ 打包(`scripts/make-vml-lib.sh`)**收 1~3 层**,所以多这一层分类目录**自动进包** ✓;
手机上 `MauiBootstrap.EnsureExamples()` 按 zip 条目**保留目录结构**解包 ✓。
## 游戏
| 文件 | 说明 | 操作 |
|---|---|---|
| `c/gomoku.c` | **五子棋**(人机对战)。最后一手有记号,结束点任意处再来一局。 | 点棋盘落子 |
| `c/tetris.c` | **俄罗斯方块**。消行计分升级、下一块预览、长按方向键连发。 | 方向键移动 · `旋转` · `直落` · `暂停`/`重开` |
| `swift/plane.swift` | **飞机空战**(Swift)。自动开火、横向跑位拦敌机,**漏 3 架结束**(右上角三条命)| 方向键移动 · `SELECT` 暂停 · `回车` 重开 |
| `csharp/racer.cs` | **赛车**(C#)。三车道向下滚,躲开红车,躲过一辆 +10 分并提速;**刷车保证每排至少一条空道**(生成线往下 2×车身高内不叠车) | 点/拖屏幕换道(方向键兜底)· `SELECT` 暂停 · `回车` 重开 |
| `go/snake.go` | **贪吃蛇**(Go)。吃到食物变长并提速 | 方向键转向 · `SELECT` 暂停 · `回车` 重开 |
| `swift/snake.swift` · `csharp/snake.cs` | **贪吃蛇**(Swift / C#)| 同上 |
| `c/piano.c` | **钢琴**。两行黑白键(键数**按屏宽自适应**,一行整若干个八度)、键上标音名、**多指按和弦**(轮询 `ui_touch_read`)、示范曲、录音回放 | 按琴键 · 「−/+」移调 · 「示范」放小星星 · 「录音」 · 「清音」急停 |
| `c/audio_test.c` | **音频测试**(不是游戏)。音阶 / 和弦同时响 / 旋律 / 与旧式音效共存,四段 | 自动播放,看屏幕提示 |
| `c/tilt.c` | **水平仪 + 小球**(不是游戏)。把**加速度计 / 陀螺仪 / 融合姿态**三个传感器的实时读数画在屏幕上,小球跟着手机倾斜滚,底部还画着**电量 / 充电 / 省电模式**。写倾斜控制照它抄 | 歪手机 · 点屏幕任意处 = 校准水平 |
| `c/bench3d.c` | **3D 压力测试**(不是游戏)。每 60 帧新建 20 个物体各自公转+自转,一路加到 250(宿主 `MaxObjects`=256),看帧率在哪个物体数上开始掉。相位用**黄金角**散开(等差会与轨道档数共振,见 `docs/开发教训.md` E123)| 自动跑,`Esc` 退出 |
| `c/g3_all.c` | **3D 号段 600–632 逐号验收**(不是游戏)。33 个号每个都有一条能分辨「生效 / 没生效」的证据:返回值按语义断言、**同一个像素**调前调后必须不同、外加 5 条标 `REVERSE:` 的**反证**(该没效果时读数必须不变;另有「恢复后逐位相同」「非法参数被拒」等同类断言)。末行打 `OK=/FAIL=/UNIMPL=`,机器可判读。桌面跑 `vmlcli … --screen 393x757 --timeout 60`,秒级闭环 | 自动跑,不用操作 |
| `c/gyro.c` | **拧螺丝**。**转手机来拧** —— 20 秒内拧满 5 圈算赢,松手会回退。用的是陀螺仪的**角速度**(本游戏刻意不区分转动方向,理由见文件头)。结束后有个「分享成绩」按钮(**`ui_share_text` 的活样例**)| 像拧方向盘那样转手机 · 点屏幕重开 |
| `cpp/gorilla.cpp` | **大猩猩扔香蕉**(C++,QBasic GORILLA 的重新实现)。弹道 + 风 + 会塌的楼、飞过的鸟 / 飞机 / 飞碟、**天黑天亮**。**音效最完整的一个**:发射 / 命中 / 空中爆炸 / 轰鸣 / 警报 / 胜负各是一种音色 | 拖动调角度力度 · 「发射」 · 键盘方向键 + 回车也行 |
| `basic/gorilla.bas` · `basic/gorilla_pro.bas` | 同一玩法的 BASIC 两版(pro 多了飞过的鸟 / 飞机 / 飞碟与天黑天亮)。**音效与 C++ 版同一套设计** | 同上 |
| `cpp/1945.cpp` | **1945 竖版卷轴射击**(C++)。6 种敌机 / 6 种敌弹(含追踪弹)/ 3 种掉落物(武器 · 生命 · 护盾)/ 3 种 BOSS(扇形弹幕 · **激光**带充能预警 · 追踪弹幕);越往后敌人越密;通关有大奖杯、失败是墓碑 | 按住拖动移动(**自动开火**)· 键盘方向键兜底 |
| `cpp/minesweeper.cpp` | **扫雷**(C++,用类写)。列数固定 9、格子边长取"按宽 / 按高"两个上界的小者、行数随剩余高度算;**首点安全**、泛洪展开走显式栈不递归。排版全部由画布坐标空间推导 | 轻点翻开 · 长按插旗 |
| `csharp/g2048.cs` | **2048**(C#)。4×4 滑动合并,每步随机补一块 2(90%)/ 4(10%),凑出 2048 获胜 | 手指滑动(方向键兜底)· `R`/回车重开 |
| `csharp/breakout.cs` | **打砖块**(C#)。拖动挡板、点一下发球;打光一片砖过关(提速 + 加一行),三条命 | 拖动 / 方向键移挡板 · 点按或空格发球 |
| `swift/minesweeper.swift` | **扫雷**(Swift)。9 列自适应行数,**首点安全**;长按**还按着就开始计时**,≥450ms 立刻插旗(不等抬手);泛洪用显式栈 | 轻点翻开 · 长按插旗 |
| `swift/flappy.swift` | **过管子(Flappy 式)**(Swift)。点一下扇翅膀,穿过管子 +1 分,撞管或落地结束,**最高分存进设备** | 点屏幕 / 空格 / 上键扇翅膀 |
| `python/breakout.py` | **打砖块**(Python)。界面走共享调用库 `Lib/shared/src/vmlui.c`(Python 前端没有内联 `asm()`,只能按标签调 C 函数)| 拖动挡板 · 点按发球 |
| `python/wordle.py` | **猜词(Wordle)**(Python)。5 字母 6 次机会、逐位三色反馈(绿=位对 / 黄=有但位错 / 灰=没有)、内置词表 | **屏幕字母键盘**(物理键盘兜底)|
| `python/tetris.py` | **俄罗斯方块**(Python)。棋盘 10×20,消行计分 | 点左右两侧横移 · 点中间旋转 · 点最下直落 |
| `kotlin/snake.kt` | **贪吃蛇**(Kotlin)。16×13 竞技场,吃到红点变长并提速(240→90ms),撞墙或咬到自己结束 | 方向键 / WASD 转向 · 屏幕十字键 · `P` 暂停 · `R` 重开 |
| `kotlin/minesweeper.kt` | **扫雷**(Kotlin)。9×9 / 10 雷,**首击必安全**,翻到 0 连带掀一片,「插旗」是模式按钮(不绑长按) | 点格子挖开 · 「挖开/插旗」切模式 · 方向键+回车 · `F` 切模式 · `R` 重开 |
| `java/snake.java` | **贪吃蛇**(Java)。吃到食物长一节、快一档;撞墙或咬到自己结束,棋盘塞满通关 | 方向键/WASD 转向 · 点屏幕往那边拐 · `SELECT` 暂停 · `回车` 重开 |
| `java/minesweeper.java` | **扫雷**(Java)。9×12 埋 15 颗雷,**首点必安全**(雷在首次点击后才埋、避开点中格及八邻域);摊开 93 个非雷格算赢 | 轻点翻开 · **按住 0.4 秒插旗** · 方向键+空格/`F` · `H` 提示 · `回车` 重开 |
| `lua/snake.lua` | **贪吃蛇**(Lua)。吃到食物变长并提速(220→96ms),撞墙或咬到自己结束,长到 40 节通关;HUD 分数/长度是**自绘点阵数字** | 方向键/WASD 转向 · `SELECT` 暂停 · `回车` 重开 · 触摸朝那边拐 |
| `lua/g2048.lua` | **2048**(Lua)。四方向滑动合并同值方块,每滑一次补一块(90% 2 / 10% 4),合出 2048 通关,满盘无路可走结束 | 方向键/WASD · 触摸滑动 · `回车`/`R` 重开 |
| `dart/snake.dart` | **贪吃蛇**(Dart)。棋盘随屏自适应(竖屏 14×18 / 横屏 21×11),每局**第一个食物固定在头行尽头**(开局第一口必定吃到,无头测试才有确定分数)、每 12 步提速 6ms | 方向键/十字键转向 · `A` 暂停 · `B`/`ESC` 退出 · 结束后 `A`/空格/回车 重开 |
| `dart/minesweeper.dart` | **扫雷**(Dart)。11×14 埋 22 雷(密度随盘子自适应),**首击必安全**、泛洪走显式栈;踩雷全盘亮雷,翻满非雷格通关 | 方向键移光标 · `A`/空格翻开 · `B`/`X` 插旗 · `ESC` 退出 · 结束后 `A`/空格/回车 重开 |
| `javascript/typing_rain.js` | **打字坠落**(JavaScript)。单词从顶上落下,敲对字母逐位消掉;敲满 15 个通关、3 条命掉光结束 | 点屏幕 A–Z 键盘 · 物理键盘字母 · `回车` 重开 |
| `javascript/maze.js` | **迷宫寻路**(JavaScript)。随机挖一座 11×15 迷宫(DFS + 显式栈),点任意一格沿唯一通路朝它走一步;`H` 用 BFS 提示最短路,步数超过最优 3 倍算输 | 方向键/WASD · 点任意格自动寻路 · `H` 提示 · `R` 换迷宫 |
**音效**:游戏音效**默认用 `ui_audio_beep(freq, ms)` 单音** —— ⚠ **本段原先写"已全部换成复音音效
(`ui_sfx_add`/`ui_sfx_tick`)",那是过期的**:共享库那套音序器**真机上会破音**
(多声部叠加削波 + 长音拖尾),2026-09-26(v0.96.507~509)**已把全部 28 个游戏退回单音**。
"每种事件一种音色"仍然成立 —— 靠的是**不同频率**,不是和弦。
仍直接用音序器的只剩 `c/audio_all.c`(音频演示)、`c/piano.c` 那类**乐器**,
以及几份老的 BASIC 例程;判据与两条破音原因见 `docs/VML游戏开发指南.md` §4.5。
两个都用 `Lib/c/vml_ui.h` 那套接口(窗体 / 绘图 / 触摸与按键消息 / 定时器),
界面全用绘图指令画出来,**不依赖任何图片资源**;尺寸按 `ui_scr_get_w/h` 报的**实测可用绘图区**
自适应,所以手机、平板、模拟器上都能铺满(`ui_scr_get_h` 在手机上只有 578 左右 —— 标题栏与
手柄区都扣掉了;按屏幕尺寸算成 744 的话,底部内容会落到可视区外)。
## 暂时跑不了的(**已知**,别当新问题查)
| 文件 | 卡在哪 |
|---|---|
| `basic/tetris.bas` | BASIC 前端三个既有缺陷:SUB 内局部 `FOR` 循环死循环、SUB 内局部数组赋值读回 0、FUNCTION+SUB 组合挂起(回归语料见仓库 `scripts/basic-tests/` 的 t9/t10/t11)。`draw_board` 整片都是 SUB 内局部 FOR,一个都绕不过去。 |
**`python/tetris.py` 已经从这张表里移走了**(v0.96.592):它原先卡在"列表读可以、写不生效"
(`b[i] = v` 之后读回还是 0),真因是 **`def main()` 的程序顶层语句一条都不执行** ——
不是列表写不生效,是那段代码根本没跑过。前端修好后 `--frame` 出的图里棋盘、下落块、底部方块都在。
BASIC 那版先用 C 版交付;那几处前端缺陷要单独修。
## 再加一个游戏
1. 源文件放 **`Examples/c/xxx.c`**(或对应语言目录;`Examples/` 整个目录**不参与 sync**,
上游同步不会覆盖本地加的文件 —— 见 `sync.sh` 的 `--exclude 'Examples/'`)。
2. 全用 `vml_ui.h` 的接口:`ui_win_open` → `ui_2d_clear`/`ui_*` 画 → `ui_2d_present`,
输入走 `ui_msg_wait(msg, 0)`(触摸 / 按键 / 定时器**同一个队列**),退出看 `ui_win_is_closed()`。
**不要去碰 500–599 的裸 syscall** —— 那层包装是各语言共用的一份实现。
3. **先开窗还是先排版**:一律 `sw = ui_scr_get_w(); sh = ui_scr_get_h(); ui_win_open(title, sw, sh);`
—— 窗口宽高就是画布的坐标空间,按 395 排版却开 360 宽的窗,右边会被切掉。
4. 在下面的无头模拟器里过一遍(**别为了看一眼效果重打 APK**)。
5. 跑 `scripts/make-vml-lib.sh` 重打资产包 —— 示例是在**打包时**进 `vml_lib.zip` 的,
光把文件放进 `Examples/` 不会自动出现在手机上。
6. 把这个表补一行。
**惯例**(两个游戏都照这个写,新增的也建议照做):
- 布局数值在 `main` 里算**一次**存起来,画和命中判定共用 —— 各算一遍就会出现
「看着在键上、点下去没反应」。
- 每次输入后**先判"画面真的变了吗"再重画**:一帧要把整幅场景光栅化一遍(手机上几百毫秒),
撞墙没动、结束后乱点这类情况就别重画。
- 长按连发用第二个定时器(`ui_timer_set(130, 1)`,用 tag 区分重力);`TOUCHUP` 时 kill 掉。
- 注释里写清**为什么**(哪条约束、哪次实测),不只写"做了什么"。
## 想在桌面看效果
桌面没有 500–599 那套 UI 号段,但仓库里的脚手架 `vmlhost`(`.scratch/vmlhost`)能无头跑:
```bash
# 脚本化触摸 + 重力节拍,并把每一帧走**和手机同一条渲染链**出成 PNG(可以直接看图验收)
dotnet run -c Release --project .scratch/vmlhost -- run Examples/c/tetris.c \
--sim "t;t;87,671;297,639" --screen 395,578 --frames /tmp/out
vmlhost bench # 量每帧的出图成本(这套"场景→DSL→PNG"路线每帧要多久)
vmlhost font # 字体自检:挑中哪个字体、中英推进量、出样张
```
`--sim` 里每个 `x,y` 投一次触摸、`t` 投一次**重力节拍**(定时器消息);
`--screen W,H` 换可用绘图区(手机上实测约 395×578,不是屏幕尺寸);
`--frames` 出帧、`--trace` 打每一次 syscall(定位"卡在哪一条")。