Files
NumberSwipe_New/modes/01-number-stroke/README.md
T
cabinw 8026e4628d 初始提交:三玩法 Web 原型与设计文档
- 玩法一 数字一笔:规则库、求解器、26 关、竖屏触摸 Web 原型、测试
- 玩法二 连连气球:连消畅玩版核心、Web 原型、Cocos 球槽组件与导出脚本
- 玩法三 六边一笔:规则库、求解器、10 关 + 转墙实验包、Web 原型、测试
- 产品文档、UI 设计规范、各玩法 GDD / HANDOFF
- 文档移除全部外部参考作引用;删除玩法二 v0 旧稿
2026-09-16 14:30:59 +08:00

160 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 玩法一 · 数字一笔
按数字顺序,一笔画走完盘面上每一格。大格只计一步。
本目录自包含:规格、关卡、规则代码、求解器和 Web 原型。不要依赖 `modes/02` 或 `modes/03`。壳需要的东西通过 `shared/` 的玩法入口对接,见 [`docs/PRODUCT.md`](../../docs/PRODUCT.md)。
关卡、资源、文案全部原创,不使用第三方题库或素材。
## 状态
M1/M2 已达:竖屏触摸 Web 原型可玩,三包 26 关全部经求解器断言唯一解,含 6 关「奇与偶」体验包;存档、补数字 / 指路双提示、音效、振动齐全。68 个单元测试通过。下一步为试玩调校与 M4 迁 Cocos。
## 目录
| 路径 | 用途 |
|---|---|
| [`docs/GDD.md`](docs/GDD.md) | 本玩法规格(权威) |
| [`docs/HANDOFF.md`](docs/HANDOFF.md) | 交接文档:实现了什么、怎么实现、与 GDD 的偏差、待办与 Cocos 迁移顺序 |
| [`../../docs/design.md`](../../docs/design.md) | UI 设计规范(色彩 / 字阶 / 组件 / 动效)。`web/style.css :root` 与 `src/theme.ts` 由 `tests/theme.test.ts` 校验一致 |
| [`levels/pack-1-tutorial.json`](levels/pack-1-tutorial.json) | 第一包 10 关(教学) |
| [`levels/pack-2-advanced.json`](levels/pack-2-advanced.json) | 第二包 10 关(进阶,生成器产出 + 人工过目) |
| [`levels/pack-3-parity.json`](levels/pack-3-parity.json) | 第三包 6 关(奇与偶,每条奇偶线索都参与唯一性) |
| [`src/`](src/) | **纯 TS 规则层,零 DOM / 零 Cocos 依赖**。Web 与 Cocos 共用 |
| [`web/`](web/) | 浏览器原型:DOM / Canvas / PointerEvent / localStorage 粘合层 |
| [`tools/`](tools/) | 求解器命令行:全关卡校验、关卡设计辅助 |
| [`tests/`](tests/) | 规则层单元测试(node:test) |
`src/` 分层:
```
types.ts 关卡 JSON 结构
board.ts Cell / Board:单位网格、四向邻接、共享边
clue.ts 数字 / 奇偶线索统一判定与显示文字
game.ts 对局状态机:Command → GameEvent(含 rewind 多步回退、reveal 补数字)
input.ts 手势语义:按下/移动/松手 + “当前压在哪个格” → Command
solver.ts 枚举满足线索的哈密顿路径、唯一解、提示(下一步 / 退回分叉)
clue-hint.ts 在尚未解开的数字区间补中间数,不替玩家画路线
layout.ts 盘面适配、格矩形、桥矩形、命中测试(带死区)
view.ts BoardView:格/桥/数字/冲突/提示/过关动效,只调用 Painter
painter.ts 绘制原语接口(圆角矩形、圆、居中文字)
theme.ts 配色
progress.ts 存档(KeyValueStore 抽象,桶 mode:01-number-stroke)
levels.ts 关卡包解析
```
## 运行
```bash
cd modes/01-number-stroke
npm install
npm run dev # 起本地服务 + 监听改动,终端会打印手机可访问的局域网地址
npm run build # 打包到 web/dist/app.js,之后可直接双击 web/index.html
npm run build:standalone # 导出项目根目录 releases/数字一笔-试玩版.html
npm run check # 类型检查(含 src/ 无 DOM 校验)+ 单元测试 + 全关卡唯一解校验
```
`?dev=1` 解锁全部关卡并暴露 `window.__numberStroke` 调试句柄(自动化手势测试用)。
直接体验奇偶:`http://localhost:8000/?dev=1&level=3-1`。`level` 跳关参数仅在开发模式下生效;不清空已有存档。
**发给朋友测试**:运行 `npm run build:standalone`,发送生成的 [`数字一笔-试玩版.html`](../../releases/数字一笔-试玩版.html) 一个文件即可。脚本、样式、26 关数据与合成音效全部内置,无需安装或启动服务,可离线用浏览器打开。全部 26 关直接可选,无需解锁或添加参数;通关记录仍保存在试玩者的浏览器中。
关卡工具:
```bash
npm run validate # 每关:连通、恰好 1 条满足线索的哈密顿路径
npm run validate -- 1-7 # 只看某关,并打印解
npm run design -- 1-7 --minimize # 当前线索里哪些可删
npm run design -- 1-7 --suggest 3 --path a,b,c,... # 给作者解找最少线索
npm run generate -- --seed 3 --out levels/pack-2-advanced.json # 随机铺盘+作者解+挑线索,批量产关
```
选关页按包分区渲染,跨包线性解锁(2-1 需先过 1-10);`web/main.ts` 顶部的 `packs` 数组登记包文件,加新包时往数组里加 import 即可。
## 第一包 · 10 关
每关只加一个旋钮(GDD §7)。线索数已按 §4.6 收到教学所需的最少。
| 关 | N | 教什么 | 大格 | 线索 |
|---|---|---|---|---|
| 1-1 | 3 | 滑动,邻格自动出步号 | – | 1 |
| 1-2 | 4 | 必须走完所有格 | – | 1, 4 |
| 1-3 | 5 | 走错可以退(第一次死路) | – | 1, 3 |
| 1-4 | 4 | 大格只算一步 | 2×2 | 1, 3 |
| 1-5 | 7 | 中段线索 | – | 1, 4 |
| 1-6 | 9 | 空洞:隔洞不相邻,先看终点再起手 | – | 1, 9 |
| 1-7 | 12 | 先推再画:三处分叉各需一次推理 | – | 1, 4, 8 |
| 1-8 | 11 | 2×2 当枢纽:6 个出口只能选远角 | 2×2 | 1, 8 |
| 1-9 | 11 | 横长条:b 紧挨 1 却是第 6 步,只能从长条下边进上边出 | 2×1 | 1, 6 |
| 1-10 | 14 | 小考:顶行直走只有 5 步,要借 2×2 垫一步;竖长条收尾 | 2×2 + 1×2 | 1, 6 |
GDD §7.1 的 12 关教学梯度压缩到 10 关:竖长条与“两个大格”并入 1-10;无线索 `1` 的关按 §7 留在第 13 关之后,本包不含。
## 第二包 · 10 关(进阶)
`tools/generate.ts` 自动生成(seed=43)+ 人工过目。面向已经会玩的人,三个设计原则(来自试玩反馈,已写进 GDD §4/§7/§8.2):
- **难度的主旋钮是相邻线索的步号差**。每关开局固定「线索 1 + 4~7 步内的引导线索」,玩家先有起手方向;难度靠后面的差距拉开(包内最大差从 8 升到 16)。
- **大连块高频出现**:每关 2~3 个多单位格,`2×2` 在 7 关出现,`3×3` 在 2-2、2-4 出现。大连块只计 1 步但出口多,是推理的主战场。
- **格子可以是俄罗斯方块式异形**(L/I/S/T,随机旋转镜像)。异形格占 2~4 个单位格、仍只计 1 步,凹口让邻接关系更不规则;渲染上拼块连成一整块(单位矩形 + 延展接缝),与 `2×2` 观感一致。
| 关 | N | 大连块与异形 | 空洞 | 线索 | 步号差 |
|---|---|---|---|---|---|
| 2-1 | 16 | 异形×2 + 2×2 | – | 1, 7, 14 | 6/7/2 |
| 2-2 | 14 | **3×3** + 异形 | – | 1, 4, 12 | 3/8/2 |
| 2-3 | 18 | 异形×2 | 1 | 1, 4, 8, 16 | 3/4/8/2 |
| 2-4 | 15 | **3×3** | 1 | 1, 6, 15 | 5/9/0 |
| 2-5 | 16 | 2×2×2 + 异形 | – | 1, 6, 15 | 5/9/1 |
| 2-6 | 18 | 2×2×2 | 1 | 1, 4, 8 | 3/4/10 |
| 2-7 | 21 | 2×2 + 条 | – | 1, 7, 17 | 6/10/4 |
| 2-8 | 19 | 异形×2 | – | 3, 5, 15, 17 | 2/10/2/2 |
| 2-9 | 21 | 2×2 + 条 | – | 1, 7, 20 | 6/13/1 |
| 2-10 | 21 | 2×2 + 条 | – | 1, 5, 21 | 4/16/0 |
2-8 **无线索 1**(全游戏第一关):起点也要自己找。
每关 JSON 里的 `teach` 字段存了生成参数与作者解(剧透,永不渲染)。想换一批:`npm run generate -- --seed <新种子> --out levels/pack-2-advanced.json`,然后 `npm run check`。
## 第三包 · 6 关(奇与偶)
「奇」要求走到该格时的步数为 1、3、5…,「偶」要求 2、4、6…。整块仍只计一步,数字与奇偶条件要同时满足。走过后显示实际步号并保留奇偶标记;走错可撤回,提示也考虑奇偶要求。
| 关 | 格数 | 内容 |
|---|---|---|
| 3-1 | 4 | 「偶」入门,借 2×2 调整步数 |
| 3-2 | 6 | 「奇」写在 3×3 上,整块只计一步 |
| 3-3 | 11 | 用奇数条件决定绕行方向 |
| 3-4 | 16 | 异形格上的「偶」+「奇」+数字线索 |
| 3-5 | 18 | 双异形、奇偶与中段数字共同推理 |
| 3-6 | 18 | 两个 2×2、两个「奇」,仅固定起点 1 |
3-2 为新盘,其余复用既有关卡的几何并重新设计线索(来源及作者解记在 `teach`)。每关唯一解;逐条删去任意奇偶条件都会出现多解,确保新规则有实际作用。此包先用于体验规则,难度手感仍待真人试玩。
关卡中写 `"parity": "odd"` / `"parity": "even"`,与同格 `clue` 互斥;可与 `shape` 或 `3×3` 共用。`npm run design -- 3-4 --minimize` 会同时检查数字和奇偶线索。现有随机生成器仍产出数字关,奇偶包采用人工配线索并用求解器筛选。
## 交互(已实现,对齐 GDD §5)
- 有线索 `1` 时只能从它起手,点别处 `1` 抖一下
- 按住滑到相邻未走格追加;点按或滑到任意已走格,直接撤掉该格之后的路径,不松手就能换方向继续画
- 松手保留路径;从末端按下继续;点末端旁的空格仍可追加;不相邻的空格忽略
- 快速滑动在两帧之间插值采样,避免漏掉中间格;直接落到历史格时优先回退。触屏死区收至 8%,鼠标维持 14%;只跟踪一个指针
- 踩错线索,或第 n 步走到预定数字 n 所在格以外的位置,立即让该格变红并轻晃,连接线保持紫色,不加叉号;本格要求不符时仍显示原数字 / 奇偶要求。原始数字与补出的数字都生效;退回后清除冲突。死路当前格抖动,不弹失败
- 补数字:优先当前尚未解开的数字区间,1~9 补 5;每次只给一个确定数字,遵守原来的奇偶规则。重开保留补数,退出重进则恢复原关卡
- 指路:无路径指起点;是解的前缀给下一步;已偏离则高亮分叉格并压暗错段,点高亮格即可直接回退
- 过关:路径提亮、按步序扫光,约 0.9s 后自动下一关;最后一关回选关
## 移植到 Cocos Creator
技术选型就是为这一步做的:
1. **`src/` 原样拷进 `assets/modes/01-number-stroke/scripts/`。** 它用 `tsconfig.src.json`(`lib: ["ES2020"]`,无 DOM)编译通过;import 全部无后缀,符合 Cocos 模块规范(Cocos 不允许 `import './foo.ts'`)。
2. **Painter 换实现。** `web/canvas-painter.ts` 对应一个 `GraphicsPainter`:`fillRoundRect/strokeRoundRect/fillCircle` 用 `Graphics`,`text` 用一组按需复用的 `Label` 节点。`BoardView.draw()` 不用改。Cocos 的 y 轴向上,在 Painter 里翻一次 y 即可。异形格不用新原语:`layout.cellShapeRects()` 把拼块拆成单位矩形 + 接缝矩形,复用同一套 `fillRoundRect`。
3. **输入换事件源。** `Node.on(TOUCH_START/MOVE/END)` 把触点转成 Canvas 节点局部坐标 → `layout.hitTest()` → `PointerInput.down/move/up()`。插值采样那几行照搬 `web/main.ts`。
4. **存档换 store。** `Progress` 接收 `KeyValueStore`,Cocos 用 `sys.localStorage` 包一层。
5. **关卡 JSON 放 `resources/`**,`resources.load` 成 `JsonAsset` 后走同一个 `parsePack/loadPack`。
6. **HUD / 选关是 UI 节点。** 对应 `web/index.html` 里那几个元素:顶栏(返回、关号、剩余格)、字幕、底栏(重开、补数字、指路)、选关网格。
7. **音效**:`web/audio.ts` 是 WebAudio 合成;Cocos 用 `AudioSource` 播 4 段短音(step / conflict / cleared / reject),接口一致。
8. 每帧:`view.update(dt)` 放 `update(dt)`,`view.draw(painter)` 在 `isAnimating` 为真时清空 Graphics 重画。
不搬进 Cocos 的东西:`web/`、`tools/`、`tests/`(测试可以继续在 Node 跑,它们只依赖 `src/`)。