初始提交:三玩法 Web 原型与设计文档
- 玩法一 数字一笔:规则库、求解器、26 关、竖屏触摸 Web 原型、测试 - 玩法二 连连气球:连消畅玩版核心、Web 原型、Cocos 球槽组件与导出脚本 - 玩法三 六边一笔:规则库、求解器、10 关 + 转墙实验包、Web 原型、测试 - 产品文档、UI 设计规范、各玩法 GDD / HANDOFF - 文档移除全部外部参考作引用;删除玩法二 v0 旧稿
This commit is contained in:
@@ -0,0 +1,464 @@
|
||||
# 玩法一 · 数字一笔
|
||||
|
||||
本文只描述**玩法一**。玩法二(连号1234)、玩法三(六边一笔)有自己的目录和 GDD,见仓库 [`docs/PRODUCT.md`](../../../docs/PRODUCT.md)。玩法三虽然也是顺序一笔画,几何和墙都独立,不要把墙或六边形补进本玩法。
|
||||
|
||||
用一次滑动,按数字规定的顺序,把盘面上每一块都走遍。规则、交互、关卡、竖直切面都以能直接实现为准。关卡、资源、文案全部原创。
|
||||
|
||||
---
|
||||
|
||||
## 1. 一句话
|
||||
|
||||
在不规则拼板上画一条哈密顿路径:格子可大可小,大格只算一步;盘上预先写好的数字,必须刚好在那一步走到。
|
||||
|
||||
## 2. 产品定位
|
||||
|
||||
| 项 | 决定 |
|
||||
|---|---|
|
||||
| 类型 | 单机、离线、无体力、无计时的逻辑 puzzle |
|
||||
| 平台 | 手机竖屏。本玩法 V1 先在 `modes/01-number-stroke/web/` 可玩,再进 Cocos 本玩法场景 |
|
||||
| 单局 | 15–90 秒想,3–8 秒画完 |
|
||||
| 会话 | 通勤 3 关,睡前 15 关 |
|
||||
| 对象 | 喜欢 Flow Free、Numberlink、一笔画、数回的人;不靠剧情、不靠养成 |
|
||||
| 付费 | 竖直切面免费无广告。上线后再加去广告 + 提示视频 |
|
||||
|
||||
卖点不是“又一个一笔画”,而是:**数字把无限路径收成唯一解,大格子把几何邻接和步数拆开。**
|
||||
|
||||
没有数字时,一笔画路径往往很多。加上两三个序号,解立刻变成一条。难度结构就是这三条:数字少、解唯一、横长/纵长格让难度陡起来。
|
||||
|
||||
## 3. 核心循环
|
||||
|
||||
1. 看盘:哪些格写了数字,哪些是大格,洞在哪。
|
||||
2. 推:从已知序号往两边挤,排除走不通的岔路。
|
||||
3. 画:手指从当前格滑到邻格,沿途自动填序号。
|
||||
4. 错了就沿原路退,或一键重开。不会死局锁死。
|
||||
5. 全填满且数字全部对上 → 过关,下一题。
|
||||
|
||||
没有分数、连击、限时。爽感来自“这条路只能是这样”和滑过时格子亮起来。
|
||||
|
||||
---
|
||||
|
||||
## 4. 规则(权威)
|
||||
|
||||
### 4.1 盘面
|
||||
|
||||
- 盘面落在整数网格上。每个**格**是轴对齐矩形,占 `w×h` 个单位格,`w,h ≥ 1`。
|
||||
- V1 允许的尺寸:`1×1`、`1×2`、`2×1`、`2×2`。V1.1 追加 `3×3` 大连块。
|
||||
- **V1.1 起追加异形格**(俄罗斯方块式拼块):2~4 个单位格四向连通,目录为 I/L/S/T/O(含旋转与镜像),JSON 里用 `shape` 偏移列表表达。规则与矩形完全一致:占多个单位格、仍只计 1 步、按单位边算邻接。
|
||||
- 每个格无论面积,都是图上的**一个节点**,路径里只计 **1 步**。
|
||||
- 单位格要么属于恰好一个格,要么是空洞(不可走、不显示底板)。
|
||||
- 盘面可以不规则,可以有洞。
|
||||
|
||||
### 4.2 邻接
|
||||
|
||||
两个格相邻,当且仅当它们共享至少 1 个单位长度的边(异形格按它占的每个单位格逐对判断)。只认四向,不认角对角。
|
||||
|
||||
推论:`2×2` 可以同时和它四边贴着的多个 `1×1` 相邻。这是难度的主开关——步数是 1,出口却有 4~8 个。异形格的凹口让「贴着几个格」更不规则,是 V1.1 的难度来源之一。
|
||||
|
||||
### 4.3 路径
|
||||
|
||||
目标:在格图上找一条**哈密顿路径**。
|
||||
|
||||
- 每个格恰好经过一次。
|
||||
- 只能走到相邻且尚未走过的格。
|
||||
- 经过第 `k` 个格时,该格显示数字 `k`(从 1 起)。
|
||||
- 路径是一条线,不是树,不能分叉,不能跳。
|
||||
|
||||
### 4.4 线索格
|
||||
|
||||
部分格预先写了正整数 `c`,带描边,开局就看得见。
|
||||
|
||||
- 路径走到这个格时,步号必须等于 `c`;反过来,第 `c` 步也必须走到这个格。
|
||||
- 盘上可以没有 `1`:起点未给定,玩家自己判断从哪起手。这是中后期难度,不进前 12 关。
|
||||
- 线索可以在路径中段,不必是起点或终点。
|
||||
- 同一盘线索互不相同,且 `1 ≤ c ≤ N`(`N` 为格数)。
|
||||
- 线索可以不包含 `N`(终点未给定)。
|
||||
|
||||
**V1.2 奇偶线索**:格子也可以写「奇」或「偶」,代替确定的数字。
|
||||
|
||||
- 「奇」要求该格的路径步号是 `1、3、5…`;「偶」要求 `2、4、6…`。按整格计步,和占用面积无关,支持 `3×3` 与异形拼块。
|
||||
- 同一格只填一种线索:数字或奇偶;同一盘允许多个「奇」和「偶」,并可与数字线索混用。
|
||||
- 未走时居中显示「奇/偶」和整格描边。走对后显示实际步号,在下方保留小字「奇/偶」;走错时红底显示实际步号,角标保留「奇/偶」要求。
|
||||
- 起点规则不变:有数字 `1` 时只能从它起手;没有 `1` 时可从任意格尝试,从「偶」起手会产生冲突。
|
||||
- 第三包「奇与偶」先提供 6 关体验(`pack-3-parity.json`);每条奇偶线索都必须参与唯一性,不能只是普通方格盘上天然成立的奇偶条件。
|
||||
|
||||
### 4.5 对错
|
||||
|
||||
走的过程允许“暂时错”:
|
||||
|
||||
- 踩到线索格但当前步号不满足数字或奇偶要求:该格变冲突态(红底),数字显示当前步号,角标显示原线索,避免只靠颜色。
|
||||
- 当第 `c` 步走到预定数字 `c` 所在格以外的任意格,也立即冲突。错误格红底显示当前步号并轻晃,不显示叉号;无需等到踩上预定格再判错。
|
||||
- 原始预定数字与「补数字」新增的数字都适用;补数后即时检查现有路径。奇偶要求只约束本格,不占用其他同类步号。
|
||||
- 错误格单独轻晃 280ms,连接线保持紫色。补数字造成已有路径冲突时也轻晃;退回 / 重开立即停止。减弱动态效果开启时仅标红。
|
||||
- 冲突不打断手势,可以继续画,也可以退回。
|
||||
- **过关条件(同时满足)**:所有格都走过,且全部数字线索和奇偶线索都满足。
|
||||
- 未过关时没有失败弹窗。死路的反馈是:没格可走了,但还没填满或还在冲突。
|
||||
|
||||
### 4.6 内容铁律
|
||||
|
||||
每一关在发布前必须通过求解器验证:
|
||||
|
||||
1. 至少 1 条合法哈密顿路径满足全部线索。
|
||||
2. 恰好 1 条。
|
||||
3. 线索数量尽量少:在仍唯一的前提下,优先少线索、优先去掉 `1` 和 `N` 这类“端点暗示”。
|
||||
4. 有奇偶线索时,逐条删除后都应出现多解;`validate` 会拒绝冗余奇偶线索。
|
||||
|
||||
前 8 关可以故意多给线索,用来教学,不追求最简线索。
|
||||
|
||||
---
|
||||
|
||||
## 5. 交互
|
||||
|
||||
### 5.1 手势
|
||||
|
||||
单指。
|
||||
|
||||
| 手势 | 行为 |
|
||||
|---|---|
|
||||
| 点在合法起点 | 该格变为路径第 1 步。若该格是线索且 `c ≠ 1`,进入冲突态,但仍算第 1 步 |
|
||||
| 按住滑向相邻未走格 | 追加一步,画连接短边,格子填色并写步号 |
|
||||
| 点按或滑到任意已走格 | 保留该格及之前的路径,一次撤掉后面的填色、短边、步号与冲突;不松手即可换方向继续画 |
|
||||
| 滑到不相邻的未走格 | 忽略;前进仍只允许四向邻接 |
|
||||
| 在空白处松手 | 路径保留,下次从末端继续,或点更早的已走格直接回退 |
|
||||
| 点末端旁的未走格 | 追加一步,兼容点按操作 |
|
||||
|
||||
触屏命中死区为格四周 8%,鼠标为 14%。快速滑动插值补采样;实际触点落到历史格时优先回退,避免跳跃采样误追加途中的格。仅跟踪一个主指针。
|
||||
|
||||
合法起点:
|
||||
|
||||
- 若存在线索 `1`:只能从该格起手。点别的格无效果,线索 `1` 轻抖一次。
|
||||
- 若不存在线索 `1`:任意非空洞格都可起手。
|
||||
|
||||
### 5.2 按钮
|
||||
|
||||
盘面下方三个按钮:
|
||||
|
||||
1. **重开**:清空路径,本局已补出的数字保留。
|
||||
2. **补数字**:在当前待解区间补一个中间数字,让玩家继续推路线。规则见 §10。
|
||||
3. **指路**:指出下一步或应退回的位置,保留原来的提示方案。
|
||||
|
||||
顶部:`{pack}-{index}`、剩余未走格 `□ × n`、返回选关。音效 / 振动在选关页设置;正式壳接管通用设置。
|
||||
|
||||
没有倒计时,没有步数惩罚。
|
||||
|
||||
### 5.3 输入状态机
|
||||
|
||||
```
|
||||
Empty
|
||||
→ 合法起点按下 → Drawing
|
||||
Drawing
|
||||
→ 滑到相邻未走格 → Drawing(N 增加)
|
||||
→ 滑到任意已走格 → Drawing(截短路径,保留目标格)
|
||||
→ 松手 → Idle(保留路径)
|
||||
Idle
|
||||
→ 从末端按下 → Drawing
|
||||
→ 点更早的已走格 → Drawing(截短路径,可继续滑动)
|
||||
→ 重开 → Empty
|
||||
→ 全填满且无冲突 → Cleared
|
||||
Cleared
|
||||
→ 短庆祝后自动进下一关,或点下一关
|
||||
```
|
||||
|
||||
任何时候点重开都回到 Empty。Cleared 后本关路径锁住,不能再改。
|
||||
|
||||
---
|
||||
|
||||
## 6. 反馈与手感
|
||||
|
||||
逻辑 puzzle 的板要干净,动效只服务三件事:我走到哪了、哪错了、过没过。
|
||||
|
||||
| 事件 | 反馈 |
|
||||
|---|---|
|
||||
| 追加一步 | 格 80ms 弹性放大回 1.0;相邻短边弹出;步号 fade-in;轻振动 |
|
||||
| 撤销一步 | 格褪回未走色;短边收回;无振动 |
|
||||
| 踩中线索且步号正确 | 描边闪白一次。这是“推对了”的主奖励 |
|
||||
| 冲突 | 单格变红并轻晃,连接线保持紫色;短振动,不弹窗 |
|
||||
| 死路未过关 | 当前格轻轻抖。不弹“失败” |
|
||||
| 过关 | 全盘路径色提到更亮,短边顺次扫过 200–300ms,然后切下一关。不要大烟花、不要全屏撒纸片 |
|
||||
| 非法起手 | 线索 `1` 抖动 |
|
||||
|
||||
视觉与动效的权威规格已迁到仓库 [`docs/design.md`](../../../docs/design.md)(令牌、字阶、组件、动效四档、过关时间线)。下面的配色与反馈表保留为规则口径,数值以设计规范为准。
|
||||
|
||||
配色 V1(浅色主题):
|
||||
|
||||
- 未走:中灰
|
||||
- 已走:单一主色(蓝紫)
|
||||
- 线索描边:未走线索 / 奇偶格与线索数字同色(墨 `#23243A`);起点和已走线索为灰白 `#DFE1EC`
|
||||
- 起点数字 `1`:开局蓝紫底、白字、灰白描边;数字放大为单位格的 62%(原为 42%),描边收细至 4.5%;只是视觉提示,按下后才计入路径。重开和撤销至空盘时恢复此样式。
|
||||
- 冲突:红
|
||||
- 背景:白
|
||||
- 数字:已走格用深色;未走线索格用白字
|
||||
|
||||
不要每关换主题色。识别数字和冲突优先于换皮。
|
||||
|
||||
音效:落子短音(音高随步号轻微上升,到 12 后循环)、冲突闷音、过关和弦。可关。
|
||||
|
||||
---
|
||||
|
||||
## 7. 关卡语言
|
||||
|
||||
难度不靠计时,靠这五个旋钮。一次只加一个。
|
||||
|
||||
| 旋钮 | 作用 | 何时引入 |
|
||||
|---|---|---|
|
||||
| 格数 `N` | 8–12 教学,12–16 常规,17–22 后期 | 全程 |
|
||||
| 大格 | `2×2`/`3×3` 出口多、只计 1 步;`1×2`/`2×1` 让难度突然抬升 | 第 4 关起 `2×2`,第 9 关起长条,第二包起 `3×3` |
|
||||
| 异形格 | L/I/S/T 拼块:邻接更不规则,凹口改变分叉结构(V1.1) | 第 11 关起(第二包) |
|
||||
| 空洞 | 切断看似相邻的格,强迫绕路 | 第 6 关 |
|
||||
| 线索位置 | 中段线索比起点线索更要推理 | 第 5 关 |
|
||||
| **线索步号差** | **难度的主旋钮**:相邻线索差得越远越难。开局 5~7 步内给一个近距引导线索,玩家就有起手方向;难度靠后面拉开差距 | 全程(V1.1 起作为生成约束) |
|
||||
| 无线索 `1` | 起点未知 | 第 13 关之后 |
|
||||
| 奇偶线索 | 只约束步号奇偶,结合大块改变走法;需证明参与唯一性 | V1.2 第三包 |
|
||||
|
||||
不做的旋钮(V1):斜走、传送、单向边、冰格、颜色门、限时。先把主规则做到手熟。
|
||||
|
||||
1-1 到 1-5 的坐标已经写在 `levels/pack-1-tutorial.json`。实现时直接读这个文件。
|
||||
|
||||
### 7.1 教学 12 关(必须手做,不生成)
|
||||
|
||||
| 关 | 教什么 | 盘面约束 |
|
||||
|---|---|---|
|
||||
| 1-1 | 滑动、自动出数字 | 3 个 `1×1` 一横,只标 `1` |
|
||||
| 1-2 | 必须走完所有格 | L 形 4 格,标 `1` 和 `4` |
|
||||
| 1-3 | 可以退回 | 小岔路,标 `1`。第一次死路用抖提示退 |
|
||||
| 1-4 | 大格 = 1 步 | 一个 `2×2` + 三个 `1×1`,标 `1` |
|
||||
| 1-5 | 中段线索 | 约 7 格,标 `1` 和 `4` |
|
||||
| 1-6 | 空洞 | 明显的缺口,看起来近其实不相邻 |
|
||||
| 1-7 | 先推再画 | ~10 格,两个中段线索。正解需要先排除一条岔路 |
|
||||
| 1-8 | `2×2` 当枢纽 | 一个 `2×2` 连接三个方向 |
|
||||
| 1-9 | 横长条 | 第一次 `1×2`,出口在长边上 |
|
||||
| 1-10 | 竖长条 | `2×1` |
|
||||
| 1-11 | 两个大格 | `2×2` + `1×2` |
|
||||
| 1-12 | 综合小考 | ~12 格,3 个线索,唯一解,不过分绕 |
|
||||
|
||||
1-4 是关键:玩家必须理解“这块很大,但数字只加 1”。过不了这关,后面全废。
|
||||
|
||||
### 7.2 结构
|
||||
|
||||
- 一包 8 关。包号就是顶部的 `pack`。
|
||||
- 包内由易到难,最后一关是本包小 Boss。
|
||||
- V1 内容量:教学 12 关 + 4 包 × 8 = **44 关**。够验证循环,不够上架。上架目标另定 200+。
|
||||
- 正常版本选关按通关进度线性解锁;独立 HTML 试玩版全部 26 关开放,可直接选择任意关卡,不需要通关前置关。
|
||||
- 没有每日挑战、没有联机(V1)。
|
||||
|
||||
### 7.3 规则样例盘(只用于讲规则,不上架)
|
||||
|
||||
一块 15 格的原创样例,用来对齐邻接和大格,不进资源表。`tests/solver.test.ts` 用同一块盘做求解器回归。
|
||||
|
||||
单位网格 5×5,行从上到下 0–4,列从左到右 0–4:
|
||||
|
||||
```
|
||||
A A a b c
|
||||
A A . d e
|
||||
f g h B B
|
||||
. i j B B
|
||||
1 l m . .
|
||||
```
|
||||
|
||||
- `A` = `2×2` 于 (0,0)
|
||||
- `B` = `2×2` 于 (3,2),带线索 `6`
|
||||
- `1` 是线索,位于 (0,4)
|
||||
- 其余字母是 `1×1`
|
||||
- `.` 是空洞
|
||||
- `N = 15`
|
||||
|
||||
邻接检查:`A` 贴 `a`(右边)、`f`、`g`(底边),不贴 `h`(只碰角)。`B` 贴 `d`、`e`(顶边)、`h`、`j`(左边),底边全是空洞。`1` 只贴 `l`,度为 1,所以必是路径端点。
|
||||
|
||||
唯一解:`1 → l → m → j → h → B → d → e → c → b → a → A → f → g → i`。`B` 面积 4 格,只占第 6 步。
|
||||
|
||||
错误走法演示:`1 → l → m → j` 之后往左进 `i` 看起来顺手,但那样 `B` 最早也要到第 8 步,踩上去显示红 `8`:这是冲突态的标准演示。正确走法是 `j` 之后先进 `h` 当第 5 步,再进 `B` 当第 6 步。
|
||||
|
||||
---
|
||||
|
||||
## 8. 生成器与求解器
|
||||
|
||||
手做教学关。第 13 关起允许生成,但每关必须人过一眼:解是否“漂亮”(没有无意义绕圈、线索是否读得清)。
|
||||
|
||||
### 8.1 求解器
|
||||
|
||||
输入:格列表(矩形 / 异形 + 可选数字或奇偶线索)。输出:满足全部线索的哈密顿路径。
|
||||
|
||||
- `N ≤ 22`,回溯即可。先走度小的节点,线索格在步号到达前剪枝。
|
||||
- 线索格 `c`:若当前步号已超过 `c` 而该格未走,剪枝;若走到时步号 ≠ `c`,剪枝。
|
||||
- 奇偶格:每次尝试进入(包括起点)都按实际步号检查奇偶,不符则剪枝。`ignoreClues` 同时忽略数字与奇偶条件。
|
||||
- 发布流水线:`paths.length === 1` 才写入关卡表。
|
||||
|
||||
### 8.2 生成流程
|
||||
|
||||
1. 随机或用模板铺格 + 空洞,保证格图连通。V1.1 起模板含异形拼块(I/L/S/T/O,随机旋转镜像),并保证每个多单位格都被作者解真正穿过(不当端点)。
|
||||
2. 随机找一条哈密顿路径作为“作者解”。
|
||||
3. 从路径上抽 2~4 个位置当线索(偏中段,少用 `1`/`N`)。V1.1 起带线索 `1` 的关先在 `1` 之后 4~7 步放一个**引导线索**(开局近距指引),再用其余线索拉开步号差上难度。
|
||||
4. 跑求解器。若解 > 1,加线索或换位置;若解 = 0,丢弃。
|
||||
5. 尝试删线索,能删到仍唯一就删。
|
||||
6. 导出 JSON。
|
||||
|
||||
### 8.3 关卡文件
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "2-3",
|
||||
"pack": 2,
|
||||
"index": 3,
|
||||
"cellSize": 64,
|
||||
"cells": [
|
||||
{ "id": "a", "x": 0, "y": 0, "w": 1, "h": 1, "clue": 1 },
|
||||
{ "id": "b", "x": 1, "y": 0, "w": 2, "h": 2 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `x,y` 是单位格坐标,原点左上。
|
||||
- V1.2 可写 `"parity": "odd"` 或 `"parity": "even"`,与 `clue` 互斥;省略表示没有奇偶要求。例:`{ "id": "big", "x": 1, "y": 1, "w": 3, "h": 3, "parity": "odd" }`。
|
||||
- 不存解。解由求解器在编辑器里验证,运行时只做规则模拟。
|
||||
- 提示用的“下一步”在编辑器烘焙进可选字段 `hintPath`,避免端上枚举。若缺失,运行时现算(N 小,可接受)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 界面与流程
|
||||
|
||||
```
|
||||
(壳:选玩法)→ 本玩法选关(默认停在第一关未通) → 对局 → 过关 → 下一关
|
||||
↘ 菜单:本玩法关卡列表 / 返回壳
|
||||
```
|
||||
|
||||
设置、广告、返回首页属于壳,见 `shared/`。本玩法对局页不实现去广告购买。
|
||||
|
||||
对局层从上到下:
|
||||
|
||||
1. 安全区
|
||||
2. 顶栏:关号、剩余格、汉堡菜单
|
||||
3. 盘面(水平垂直居中,按短边缩放,四周留指宽)
|
||||
4. 手势说明「按住滑动连线 · 点已走格回退」;底栏:重开、补数字、指路
|
||||
5. 底 banner 广告位:竖直切面放占位,正式包未去广告时才显示
|
||||
|
||||
过关不进独立结算页。短动画后自动下一关。最后一关回选关。
|
||||
|
||||
文案尽量少。规则用 1-1~1-4 教完,不写长教程。1-1 顶部一行:“从一个数字滑到邻格”。1-4 一行:“大格也只算一步”。之后无文案。
|
||||
|
||||
---
|
||||
|
||||
## 10. 提示、存档、设置
|
||||
|
||||
**补数字**
|
||||
|
||||
- 从唯一解中取当前尚未解开的数字区间,选择靠近中点的一个步号。例如已有 `1` 与 `9`,补出 `5`;再请求可继续缩小区间。
|
||||
- 正确前缀已走过的区域不再补数;走错时仍按真实解补数,不沿错误路线给数字。
|
||||
- 每次只增加一个固定数字,不自动追加路径。该数字参与冲突与通关判定;若补在奇偶格,数字与奇偶要求同时保留。
|
||||
- 补出的数字先用琥珀外圈提示位置,之后以普通数字线索显示。重开保留;退出重进、刷新或切关恢复原始关卡。补数不写回关卡文件,也不存档。
|
||||
- 没有可补数字时提示尝试「指路」。
|
||||
|
||||
**指路**
|
||||
|
||||
- 当前无路径:指出合法起点(有 `1` 就指它;否则指解的真实起点)。
|
||||
- 当前有路径且与唯一解的前缀一致:指出下一步。
|
||||
- 当前有路径但已偏离唯一解:高亮最后一个正确格,压暗错段;点高亮格即可一次退回。若起点就错了,提示重开。
|
||||
- 竖直切面:无限次、无广告。
|
||||
- 上线:每天 1 次免费,之后激励视频。
|
||||
|
||||
**存档**
|
||||
|
||||
本地,键前缀 `mode:01-number-stroke`。存 `clearedId[]`、`currentId`。不存半途路径。不得读写其他玩法的桶。
|
||||
|
||||
高对比(冲突改成红底+斜纹)读壳的设置项,本玩法视图遵守即可。
|
||||
|
||||
---
|
||||
|
||||
## 11. 设计边界
|
||||
|
||||
V1 只做主规则(§4)。明确不做,留给后续版本或其它玩法:
|
||||
|
||||
- 对角邻接、斜走、传送、单向边
|
||||
- 用户造关、关卡分享
|
||||
- 计时、体力、分数
|
||||
|
||||
关卡、命名、视觉、音效全部原创,不使用第三方题库或素材。教学关按教学节拍手做(§7.1)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 技术
|
||||
|
||||
全部落在 `modes/01-number-stroke/` 内。不要把本玩法的 Board / 邻接 / 求解器放到 `shared/` 或其它 `modes/`。
|
||||
|
||||
| 项 | 决定 |
|
||||
|---|---|
|
||||
| 竖直切面 | `web/` 单页,手机浏览器可玩,不经过壳首页 |
|
||||
| 正式工程 | Cocos Creator 3.x,场景与脚本在 `assets/modes/01-number-stroke/` |
|
||||
| 逻辑 | 纯 TypeScript,与渲染解耦。同一套规则跑在 `web/` 和 Cocos |
|
||||
| 求解器 | `tools/solver.ts`,只服务本玩法 |
|
||||
| 关卡 | `levels/*.json`,见 §8.3 |
|
||||
|
||||
核心代码模块(`src/`):
|
||||
|
||||
- `model`:Cell、Board、Path、clue/conflict
|
||||
- `rules`:邻接、合法下一步、过关判定
|
||||
- `input`:手势 → 命令(Start / Append / Rewind / Reset)
|
||||
- `solver`:枚举唯一解
|
||||
- `clue-hint`:从唯一解选择待解区间的中间数字
|
||||
- `view`:格、短边、数字、冲突、过关动画
|
||||
|
||||
对壳只暴露玩法入口 `id + mount/unmount`,见产品文档。
|
||||
|
||||
---
|
||||
|
||||
## 13. 竖直切面(做完才能说“玩法成立”)
|
||||
|
||||
可玩标准,全部要在真机或浏览器里点过:
|
||||
|
||||
1. 1-1 到 1-4 能独立教会滑动、走完、撤销、大格计 1 步。
|
||||
2. 1-5 到 1-8 需要先想再画,不是闭眼滑。
|
||||
3. 冲突态肉眼可辨,撤销后恢复。
|
||||
4. 有线索 `1` 时,点其他格起手无效。
|
||||
5. 无线索 `1` 的样例关至少 1 个(可放在 VS 附录关,不进前 12)。
|
||||
6. 过关自动进下一关,进度刷新后还在。
|
||||
7. 求解器对 VS 全部关断言 `paths.length === 1`。
|
||||
8. 提示在偏离解时高亮分叉,不继续给错下一步。
|
||||
|
||||
VS 不做:广告、IAP、每日、动画角色、换皮、关卡编辑器 UI。
|
||||
|
||||
---
|
||||
|
||||
## 14. 里程碑
|
||||
|
||||
| 阶段 | 交付 |
|
||||
|---|---|
|
||||
| M0 | 本 GDD 锁定 |
|
||||
| M1 | 规则库 + 求解器 + 1-1~1-4 可玩 |
|
||||
| M2 | 1-1~1-12 + 输入手感 + 存档 |
|
||||
| M3 | 再 32 关(生成+人工验收)+ 选关 |
|
||||
| M4 | 迁 Cocos 本玩法场景,真机跑 VS 清单。不负责接其它玩法 |
|
||||
|
||||
M1 结束时如果 1-4 的“大格只计一步”教不会,先改教学盘,再加内容。
|
||||
|
||||
---
|
||||
|
||||
## 15. 关键决定
|
||||
|
||||
1. **关卡与资源全原创。** 不使用第三方题库或素材。
|
||||
2. **大格是一等公民,不是皮肤。** 面积不影响步数,只影响邻接。这是和普通一笔画的分界。
|
||||
3. **解必须唯一。** 休闲 puzzle 靠“想明白就过”,不靠多种走法碰运气。
|
||||
4. **允许画错,不允许锁死。** 冲突标红,撤销和重开永远在。
|
||||
5. **无计时、无分、无体力。** 会话长度由关卡大小控制。
|
||||
6. **先 Web 规则原型,再 Cocos。** 玩法成立之前不上引擎装饰。
|
||||
7. **V1 不加第二套机构。** 斜走、传送、单向边全部推迟。把 44 关的主规则打满。
|
||||
|
||||
---
|
||||
|
||||
## 16. 验收标准(玩法)
|
||||
|
||||
- 没玩过的人,不看文字,能在 1-4 结束前理解全部 V1 规则。
|
||||
- 1-7 开始出现“停下来看”的瞬间,而不是滑完才发现错。
|
||||
- 任意关在提示关闭时仍可人解,不依赖穷举。
|
||||
- 同一关重开 10 次,手势与判定稳定,无幽灵邻接、无双触追加两步。
|
||||
|
||||
---
|
||||
|
||||
## 17. 已关闭的问题
|
||||
|
||||
- 要不要做成 2048 / 合成数字?不要。本玩法就是顺序一笔画。其它机制留给玩法二、三。
|
||||
- 要不要 V1 就上差异化机构?不要。先证明主规则。
|
||||
|
||||
## 18. 留给实现的缺口
|
||||
|
||||
- 本玩法对外名称是否就用「数字一笔 / Number Stroke」,上架前再定。产品总名称另定。
|
||||
- 主色的最终值(蓝紫只是 VS 默认)。
|
||||
- M3 的 32 关是全手做还是生成后人工改,看 M2 手感再定。
|
||||
- 本玩法的 `src/`、`tools/`、`web/` 在 M1 才建。Cocos 场景在 M4 才建。
|
||||
@@ -0,0 +1,536 @@
|
||||
# 玩法一 · 数字一笔 — 交接文档
|
||||
|
||||
> 面向接手的人。读完这份文档 + 跑一遍 `npm run check`,应该能不问人就继续往下做。
|
||||
> 规格权威是 [`GDD.md`](GDD.md),目录/运行方式的速查在 [`../README.md`](../README.md),本文讲“做了什么、怎么做的、为什么、还差什么”。
|
||||
>
|
||||
> 交接日期:2026-09-16。状态:M1/M2 达成(Web 原型可玩、三包 26 关唯一解、奇偶线索/存档/提示/音效/振动齐全),未进 Cocos。
|
||||
|
||||
---
|
||||
|
||||
## 0. 一分钟上手
|
||||
|
||||
```bash
|
||||
cd modes/01-number-stroke
|
||||
npm install
|
||||
npm run dev # 起服务 + 监听改动。终端打印手机可访问的局域网地址(默认从 8000 起找空闲端口)
|
||||
npm run check # 类型检查(含 src/ 无 DOM 校验)+ 68 个单元测试(含设计令牌一致性)+ 26 关唯一解校验,全绿才算没改坏
|
||||
```
|
||||
|
||||
浏览器打开 `http://localhost:8000/?dev=1`:`?dev=1` 解锁全部关卡并暴露 `window.__numberStroke` 调试句柄(见 §3.6)。去掉参数就是正常的线性解锁。
|
||||
|
||||
`npm run build` 会把打包产物写到 `web/dist/app.js`(IIFE),之后直接双击 `web/index.html` 也能玩。
|
||||
|
||||
---
|
||||
|
||||
## 0.1 本次接手后的最新状态(2026-09-15)
|
||||
|
||||
已从 Paseo Agent `62da92b3-72b1-42e9-bf69-229171bc31f1` 的近期会话接手。原会话因 Kimi 周额度耗尽停在用户的新需求:“格子可填奇或偶,要求当前步数符合奇偶”。此前已落地的 3×3、大格权重、异形格、第二包保留。
|
||||
|
||||
本次完成:
|
||||
|
||||
- `CellDef.parity?: 'odd' | 'even'`,与同格数字 `clue` 互斥。`clue.ts` 统一判定,`Game` 和 `solve` 共用;不符时允许继续但不算通关,撤销/重开恢复,提示基于含奇偶的唯一解。
|
||||
- 「奇与偶」6 关,登记到 Web 第三包。每关唯一,且删掉任意一条奇偶线索都会出现多解;第一、二包数据未改。3-2 为新的 3×3 教学盘,其余复用旧几何重配线索,具体来源见 `teach`。
|
||||
- 未走格显示「奇/偶」,走对后显示步号并保留小字要求,走错显示红底及要求角标。异形线索描边改为外/内剪影形成整块轮廓,继续使用原来的四种 Painter 原语。修复大矩形冲突角标索引越界。
|
||||
- `validate` 增加奇偶冗余校验;`design --minimize` 可检查两类线索,重建候选时保留异形形状和奇偶字段。普通随机生成器仍只生成数字关。
|
||||
- 修复最后一关回选关时引用不存在的 `pack-progress` 节点:提示条由选关/对局共用。
|
||||
- 试玩入口:`http://localhost:8000/?dev=1&level=3-1`;指定 `level` 仅在开发模式生效,不清存档。调试状态新增 `conflicts: string[]`。
|
||||
- 起点样式按后续反馈调整:数字 `1` 开局为蓝紫底、白字、灰白描边(`#DFE1EC`),字号从单位格 42% 调至 62%;普通单字数字 52%、两位数 48%,描边从 6% 收到 4.5%。矩形和异形统一用内外剪影,修掉圆角残色。已检查 390×844 与 320×640 的显示;没有提前计步,撤销/重开为空盘时保持该样式。
|
||||
|
||||
验证:`npm run check` 为 46 测试、26/26 唯一解;新增测试用独立枚举对照 81 种奇偶分布(各含有/无数字 1 两种情况),并覆盖起手、冲突、撤销、重开、大块、异形、提示与过关。浏览器 390×844 触摸仿真点通全部 6 个新关卡;3-1 另验证滑错 → 红底与「偶」角标 → 提示退回 → 滑回撤销 → 走通。已目视检查 3×3 已走标记、异形未走及冲突轮廓。
|
||||
|
||||
下一步:用户试玩这 6 关后调难度;按需扩充奇偶生成器;Cocos/真机验收仍未做。下文第一包的关卡图与早期架构说明继续保留;关卡总数、异形和奇偶规则以本节与 README/GDD 为准。
|
||||
|
||||
---
|
||||
|
||||
## 0.2 触屏回退与双提示(2026-09-16,当前实现)
|
||||
|
||||
- 点按或滑到任意已走格,`rewind` 一次移除其后缀;保留目标格,不松手即可接着画。触屏命中死区 8%,鼠标 14%;Web 插值前优先检查实际触点是否是历史格,防止跳跃回退误加格。
|
||||
- 底栏为「重开 / 补数字 / 指路」,并加手势说明。补数字由 `src/clue-hint.ts` 选当前尚未解开的数字区间中点:1~9 补 5。已正确走过的区域不再补。
|
||||
- `Game` 的 `extraClues` 保存本次对局补数;`clueAt()` 合并数字与原有奇偶条件,冲突和通关都用有效线索。重开保留补数,重新进入关卡或刷新不保留;不修改关卡数据、求解器或存档。
|
||||
- `reveal` 的数字来自唯一解;渲染时轻弹并用琥珀外圈定位,之后是普通线索。补在奇偶格时保留小字奇偶要求。
|
||||
- 原 `solver.hint()` 由「指路」调用;偏离时高亮最后一个正确格,可点高亮位置直接回退。调试 `debugState()` 增加 `revealed: {cell, step}[]`。
|
||||
- 类型检查、60 个测试、26/26 关唯一解通过。浏览器以原生 CDP touch 事件验证:连续画 6 格、点第 3 格一次退 3 步并不松手转向、跨格回退、快速滑动补采样;验证 1~9 补 5、重开保留、旧指路入口及奇偶补数。390×844 与 320×640 检查布局。真机手感仍待试玩。
|
||||
- `npm run build:standalone` 导出根目录 `releases/数字一笔-试玩版.html`,内含两种提示和触屏操作,可直接发给朋友用浏览器打开。
|
||||
|
||||
上一节为接手历史;当前手势和提示以本节及 GDD §5 / §10 为准,视觉以根目录 design.md / design.html v1.3 为准。
|
||||
|
||||
---
|
||||
|
||||
## 0.3 固定数字步号的双向校验(2026-09-16)
|
||||
|
||||
- `Game.isConflict()` 既检查本格数字 / 奇偶要求,也检查当前步号是否被另一个固定数字格占用。例如预定格为 5,而玩家第 5 步落在其他格,则该步立即标错,无需等踩到预定格。
|
||||
- `push()` 的冲突事件、`clueHit`、盘面渲染与通关统一采用此判断。奇偶命中不能覆盖数字位置错误。原始数字与 `extraClues` 都生效;补数后已有路线也立即重新判断。
|
||||
- 错位的普通格或满足自身奇偶要求的格显示红底 +「×」角标;自身线索也不符时继续显示原要求。撤销 / 回退后清除对应冲突。
|
||||
- 新增 6 项回归测试,覆盖即时错位、奇偶命中反馈、撤回重走、补数与重开、奇偶不占号、红格 / 红桥 / 角标。完整检查为 66 测试、26/26 关通过。独立 HTML 同步更新。
|
||||
|
||||
---
|
||||
|
||||
## 0.4 错格反馈简化、独立试玩版全开放(2026-09-16,当前状态)
|
||||
|
||||
- 不再绘制占号冲突的「×」;只有错误格变红并横向轻晃 280ms,进出连接线始终使用路径色。本格数字 / 奇偶要求不符时仍保留原要求角标。
|
||||
- 新数字让已有路线出错时,同样晃动受影响格;撤销 / 重开立即清除晃动。系统减弱动态时仅保留红色状态。
|
||||
- `scripts/standalone.mjs` 为导出 HTML 加 `data-unlock-all="true"`,选关不再要求前置通关;无需 `?dev`,不会把关卡伪标记为已通关,也不默认暴露调试对象。正常 Web 的进度规则保持原样。
|
||||
- 当前完整检查为 68 测试、26/26 关通过;最新交付为根目录 `releases/数字一笔-试玩版.html`。视觉规范为 design.md / design.html v1.5;此前版本的红桥 / × 描述仅作历史记录。
|
||||
|
||||
---
|
||||
|
||||
## 1. 实现了什么
|
||||
|
||||
### 1.1 交付清单(对照 GDD)
|
||||
|
||||
| GDD 条目 | 状态 | 备注 |
|
||||
|---|---|---|
|
||||
| §4 规则:矩形格、四向邻接、大格计 1 步、线索步号、冲突态、过关判定 | 完成 | `src/board.ts` `src/game.ts`,有单测 |
|
||||
| §5.1 手势全部行为(含“有 1 只能从 1 起手”) | 完成 | `src/input.ts`,有单测;真实触摸事件跑通 10 关 |
|
||||
| §5.2 按钮:重开 / 补数字 / 指路 + 返回选关 | 完成 | 菜单做成左上返回箭头,因为壳还没有 |
|
||||
| §5.3 输入状态机 Empty/Drawing/Idle/Cleared | 完成 | `Game.status` |
|
||||
| §6 反馈:落子弹性、桥弹出、步号淡入、线索命中闪白、冲突红、死路抖、非法起手抖、过关扫光 | 完成 | `src/view.ts`;音效 `web/audio.ts`;振动 `navigator.vibrate` |
|
||||
| §7.1 教学关 | 完成 10 关(用户要求 10,见 §3.2) | `levels/pack-1-tutorial.json` |
|
||||
| §8.1 求解器 | 完成 | `src/solver.ts`,N ≤ 22 毫秒级 |
|
||||
| §8.2 生成器 | 已实现数字关生成 | `tools/generate.ts` 支持大块/异形与难度筛选;奇偶包当前为手配线索 + 求解器验证 |
|
||||
| §8.3 关卡文件格式 | 完成 | 未使用可选字段 `hintPath`,运行时现算(N 小) |
|
||||
| §9 界面流程:选关 → 对局 → 过关自动下一关 → 最后一关回选关 | 完成 | `web/main.ts` |
|
||||
| §10 补数字 | 完成 | `clue-hint.ts` + `Game.reveal`,重开保留 |
|
||||
| §10 指路三态(起点 / 下一步 / 退回分叉)+ 起手错 | 完成 | `solver.hint()`,无限次无广告 |
|
||||
| §10 存档 `mode:01-number-stroke` | 完成 | `src/progress.ts`,`clearedId[]` + `currentId` + 偏好 |
|
||||
| §10 高对比(冲突加斜纹) | **未做** | 冲突已有非颜色标记(角标),斜纹等壳的设置项 |
|
||||
| §12 技术:纯 TS 逻辑与渲染解耦 | 完成 | `tsconfig.src.json` 用 `lib: ["ES2020"]` 强制 `src/` 不碰 DOM |
|
||||
| §13 竖直切面清单 | 见下表 | |
|
||||
|
||||
GDD §13 竖直切面 8 条:
|
||||
|
||||
| # | 条目 | 状态 |
|
||||
|---|---|---|
|
||||
| 1 | 1-1~1-4 能教会滑动、走完、撤销、大格计 1 步 | 关卡与字幕已做;**没有找真人试玩** |
|
||||
| 2 | 1-5~1-8 需要先想再画 | 求解器层面成立(无线索路径 2~124 条,加线索后唯一);**没有试玩数据** |
|
||||
| 3 | 冲突态肉眼可辨,撤销后恢复 | 完成(红底 + 白步号 + 深色角标显示应有步号) |
|
||||
| 4 | 有线索 1 时点其他格无效 | 完成,有单测,浏览器验证过(1 会抖一下) |
|
||||
| 5 | 无线索 1 的样例关至少 1 个 | 已有 2-8(第二包);奇偶无起点场景另有单测 |
|
||||
| 6 | 过关自动进下一关,进度刷新后还在 | 完成,浏览器验证过 localStorage 内容 |
|
||||
| 7 | 求解器对全部关断言 `paths.length === 1` | 完成:`npm run validate` + `tests/solver.test.ts` 双重断言 |
|
||||
| 8 | 提示在偏离解时高亮分叉,不继续给错下一步 | 完成,浏览器验证过 |
|
||||
|
||||
### 1.2 玩家能做什么(交互行为,已实现)
|
||||
|
||||
- 有线索 `1`:只能从它起手,点别处 `1` 抖一下 + 轻振动 + 短音。无线索 `1`:任意格起手。
|
||||
- 按住滑到相邻未走格 → 追加一步(格弹一下、桥长出来、步号淡入、音高随步号上升到 12 后循环)。
|
||||
- 点按或滑到任意已走格 → 一次撤掉后面的路径,目标格轻弹,多步回退轻振动。
|
||||
- 滑到不相邻的空格 → 忽略,逻辑指针停在末端。
|
||||
- 松手 → 路径保留(`idle`)。再从末端按下 → 继续画。点任意已走格 → 回退到它并进入画的状态。点末端旁边的空格 → 直接追加(点着走)。
|
||||
- 踩到线索但步号不对 → 该格红底,步号照显示,右上角深色圆角标写应有的数字;不中断手势。
|
||||
- 无格可走但未过关 → 当前格抖一下,不弹窗。
|
||||
- 全走完且线索全对 → 路径提亮、按步序扫光,约 1 秒后(扫光 → 停留 → 淡出)自动进下一关;包末衔接下一包,全序列最后一关回选关并显示完成提示。过关后本关锁定。
|
||||
- 重开:清空路径但保留补数。补数字 / 指路:见 §0.2。
|
||||
- 选关页:10 格瓦片(已通关紫色带点、当前关描边、未解锁灰锁),“继续 · 1-x”大按钮,音效/振动开关,重置进度(有 confirm)。
|
||||
- 手势健壮性:只跟踪一个指针(`pointerId`),`setPointerCapture`,两帧之间按 1/4 格插值采样防跳格,触屏每格四周 8%、鼠标 14% 死区防误触。
|
||||
|
||||
### 1.3 关卡:10 关
|
||||
|
||||
坐标原点左上,`.` 是空洞,`·` 是大格的延伸部分,`[n]` 是线索。右边是唯一解的步号图。全部由 `npm run validate` 断言唯一。
|
||||
|
||||
**1-1** 滑动,邻格自动出步号(字幕:从 1 开始,滑到旁边的格)
|
||||
```
|
||||
[1] b c 1 2 3
|
||||
```
|
||||
|
||||
**1-2** 必须走完所有格(字幕:走完每一格,数字要对上)
|
||||
```
|
||||
[1] . 1 .
|
||||
b . 2 .
|
||||
c [4] 3 4
|
||||
```
|
||||
|
||||
**1-3** 走错可以退。先往下会错过 3(字幕:走错了,沿原路滑回去)
|
||||
```
|
||||
[1] a [3] 1 2 3
|
||||
. d c . 5 4
|
||||
```
|
||||
|
||||
**1-4** 大格只算一步(字幕:大格也只算一步)。`start` 与 2×2 只碰角,不相邻——这是理解邻接规则的关键盘。
|
||||
```
|
||||
[1] a b 1 2 4
|
||||
. [3] · . 3 ·
|
||||
. · · . · ·
|
||||
```
|
||||
|
||||
**1-5** 中段线索。必须先走完顶行,4 才落在右上角。
|
||||
```
|
||||
[1] a b [4] 1 2 3 4
|
||||
. . d e . . 6 5
|
||||
. . f . . . 7 .
|
||||
```
|
||||
|
||||
**1-6** 空洞。中间缺一格,`a` 与 `f` 隔洞不相邻;`9` 在左边,必须绕环一圈最后到它。陷阱:先往下走会把 `9` 提前踩成红 `3`。`9` 对唯一性是冗余的(只留 `1` 也唯一),故意留着教“先看终点再起手”。
|
||||
```
|
||||
. [1] a b . 1 2 3
|
||||
[9] c . d 9 8 . 4
|
||||
. e f g . 7 6 5
|
||||
```
|
||||
|
||||
**1-7** 先推再画。3×4 满盘,无线索路径 124 条。三处分叉:`b` 处要走 `f` 才能让 `e=4`;`k` 处要走 `g` 才能让 `g=8`;`g` 处要先取 `c`,否则 `l` 被孤立。
|
||||
```
|
||||
[1] b c d 1 2 9 10
|
||||
[4] f [8] h 4 3 8 11
|
||||
i j k l 5 6 7 12
|
||||
```
|
||||
|
||||
**1-8** 2×2 当枢纽。`B` 有 6 个出口但只能进出各一次:从 `B` 出去必须挑 `c` 或 `d`(远角),挑 `e/f/h/i` 都会孤立一侧。`h=8` 决定顺时针。
|
||||
```
|
||||
. [1] . . . 1 . .
|
||||
. b . . . 2 . .
|
||||
c B · d 11 3 · 4
|
||||
e · · f 10 · · 5
|
||||
g [8] i j 9 8 7 6
|
||||
```
|
||||
|
||||
**1-9** 横长条,出口在长边上。`b` 紧挨着 `1` 却要第 6 步才到:只能从长条 `H` 的下边进、上边出(`h→H→b`),所以先绕左下角。无线索路径 112 条,两个线索即唯一。
|
||||
```
|
||||
[1] [6] c d 1 6 7 8
|
||||
e H · f 2 5 · 9
|
||||
g h i j 3 4 11 10
|
||||
```
|
||||
|
||||
**1-10** 小考:2×2 + 竖长条 + 空洞。顶行直走到 `e` 只有 5 步,`e=6` 要求多垫一步——从 `b` 下到 `B` 再回 `c`(`a,b,B,c,d,e`)。之后绕右下回到左侧,`V` 收尾。陷阱:`a,V,B,c` 也能让 `e=6`,但 `b` 会被孤立。14 格只有 2 个线索。
|
||||
```
|
||||
[1] b c d [6] 1 2 4 5 6
|
||||
V B · . f 14 3 · . 7
|
||||
· · · g h · · · 9 8
|
||||
i j k l . 13 12 11 10 .
|
||||
```
|
||||
|
||||
每关 JSON 里的 `teach` 字段是设计备注(含剧透),**永远不要渲染到界面**;只有 `caption` 会显示在顶部。
|
||||
|
||||
难度旋钮引入顺序:格数 → 大格(1-4) → 中段线索(1-5) → 空洞(1-6) → 推理(1-7) → 2×2 枢纽(1-8) → 横条(1-9) → 竖条+综合(1-10)。GDD 的 12 关梯度压成 10 关:竖长条和“两个大格”并进 1-10;无线索 `1` 按 GDD 留到第 13 关之后。
|
||||
|
||||
### 1.4 视觉
|
||||
|
||||
完整规范见仓库 [`docs/design.md`](../../../docs/design.md)(2026-09-15 起):色彩 / 字阶 / 间距 / 动效四档令牌,CSS `:root` 与 `src/theme.ts`(`THEME` / `BOARD_TYPE` / `MOTION`)由 `tests/theme.test.ts` 校验一致。顶栏含关号、「包名 · 第 n 关」副标题、本包进度条与剩余格胶囊;盘面有错落进场与过关后淡出。
|
||||
|
||||
浅色单主题(`src/theme.ts`):背景白、未走格 `#E6E7EC`、已走蓝紫 `#6558F5`(过关提亮 `#7D72FF`)、未走线索描边与数字同为近黑 `#23243A`、冲突红 `#E5484D`、提示琥珀 `#F5A524`。桌面浏览器上以 480px 手机框居中显示,方便调试。
|
||||
|
||||
---
|
||||
|
||||
## 2. 如何实现
|
||||
|
||||
### 2.1 技术选型与理由
|
||||
|
||||
目标是“之后迁 Cocos Creator 3.x 不重写”。由此推出的决策:
|
||||
|
||||
| 决策 | 理由 |
|
||||
|---|---|
|
||||
| `src/` 纯 TypeScript,零 DOM、零 Cocos 依赖 | Cocos 脚本就是 TS;规则/求解器/动效状态/存档逻辑直接拷过去。用 `tsconfig.src.json`(`lib: ["ES2020"]`, `types: []`)编译一遍,谁往 `src/` 里写 `document`/`window` 就报错 |
|
||||
| import 全部**无后缀**(`./board` 而不是 `./board.ts`) | Cocos 模块规范明确不允许 `import './foo.ts'`(会找不到模块)。Node 原生 type-stripping 反而要求带 `.ts` 后缀,二者冲突,所以放弃 Node 直跑,改用 esbuild 打包 + tsx 跑工具 |
|
||||
| 渲染走 `Painter` 接口(4 个原语) | Canvas 2D 与 Cocos `Graphics`/`Label` 都能实现;`BoardView.draw()` 是即时模式,每帧全量重画(N ≤ 22 格无压力) |
|
||||
| 输入层只认“指针现在压在哪个格” | `PointerInput.down/move/up(cellIndex)` 不知道 PointerEvent 或 TOUCH 事件的存在;坐标 → 格由 `layout.hitTest()` 算,Cocos 只需换坐标来源 |
|
||||
| 存档走 `KeyValueStore` 接口 | Web 用 `localStorage`,Cocos 用 `sys.localStorage`,测试用 `MemoryStore` |
|
||||
| esbuild 单文件 IIFE 输出 | 不需要 dev server 也能 `file://` 打开;手机测试时 `npm run dev` 走局域网 |
|
||||
| 关卡 JSON 打进 bundle | esbuild `loader: { '.json': 'json' }`;Cocos 侧放 `resources/` 用 `JsonAsset` 加载,解析函数相同 |
|
||||
|
||||
不选的东西:Vite/React(多余的运行时和 DOM 心智)、SVG/DOM 渲染(迁 Cocos 全废)、Node 原生 `--experimental-strip-types`(后缀冲突)。
|
||||
|
||||
### 2.2 目录与模块职责
|
||||
|
||||
```
|
||||
modes/01-number-stroke/
|
||||
package.json tsconfig.json tsconfig.src.json .gitignore
|
||||
scripts/build.mjs esbuild 打包/监听/服务
|
||||
levels/pack-1-tutorial.json
|
||||
src/ ── 纯逻辑层,Web/Cocos 共用 ──
|
||||
types.ts CellDef / LevelDef / PackDef(关卡 JSON 的类型)
|
||||
board.ts Board:单位网格、四向邻接、共享边、连通性;rectsShareEdge()
|
||||
game.ts Game:Command → GameEvent[] 的状态机
|
||||
input.ts PointerInput:手势语义 → Command
|
||||
solver.ts solve() / uniqueSolution() / hint()
|
||||
layout.ts fitBoard() / cellRect() / cellBounds() / bridgeRect() / hitTest()
|
||||
view.ts BoardView:动效状态 + draw(painter)
|
||||
painter.ts Painter 接口
|
||||
theme.ts 配色常量
|
||||
progress.ts Progress + KeyValueStore + MemoryStore
|
||||
levels.ts parsePack() / loadPack()
|
||||
web/ ── 浏览器粘合层 ──
|
||||
index.html style.css 两屏 DOM(选关 / 对局)
|
||||
main.ts App:屏幕切换、HUD、PointerEvent → PointerInput、rAF 循环、存档、调试句柄
|
||||
canvas-painter.ts Painter 的 Canvas 2D 实现
|
||||
audio.ts WebAudio 合成音效(step/conflict/cleared/reject)
|
||||
dist/app.js 打包产物(gitignore)
|
||||
tools/ ── Node 命令行 ──
|
||||
validate.ts 全包校验:连通 + 恰好 1 解;-- <id> 打印盘面与解
|
||||
design.ts 设计辅助:枚举无线索路径、--minimize、--suggest k --path ...
|
||||
ascii.ts 终端字符盘面
|
||||
tests/*.test.ts node:test,30 个用例
|
||||
docs/GDD.md docs/HANDOFF.md(本文)
|
||||
```
|
||||
|
||||
### 2.3 数据流:一次触摸从事件到画面
|
||||
|
||||
```
|
||||
PointerEvent (web/main.ts)
|
||||
│ getBoundingClientRect → 容器像素坐标;pointermove 时按 unit/4 插值采样
|
||||
▼
|
||||
layout.hitTest(board, layout, x, y) → cellIndex | -1(缝/死区/空洞/越界)
|
||||
▼
|
||||
PointerInput.down/move/up(cellIndex) → 根据路径状态决定 Command
|
||||
▼
|
||||
Game.apply(Command) → GameEvent[] (start/append/undo/rewind/reveal/release/resume/reset)
|
||||
▼
|
||||
handleEvents(events):
|
||||
├─ BoardView.onEvent(e) 置动效状态(弹簧脉冲、桥进度、闪白、抖动、过关扫光)
|
||||
├─ Sfx / vibrate step(n)、conflict、reject、cleared
|
||||
├─ HUD 剩余格数
|
||||
└─ cleared → Progress.markCleared → setTimeout(clearedDuration) → 下一关
|
||||
每帧 (rAF):view.update(dt);若 view.isAnimating 则 clearRect + view.draw(painter)
|
||||
```
|
||||
|
||||
Cocos 里只换最上面一行(TOUCH 事件 → 节点局部坐标)和 `Painter` 实现,中间不动。
|
||||
|
||||
### 2.4 各模块要点
|
||||
|
||||
**board.ts**
|
||||
- `Board.fromCells(defs)`:校验尺寸只允许 1×1/2×1/1×2/2×2(`ALLOWED_SIZES`)、id 不重复、线索 ∈ [1, N] 且不重复、单位格不重叠;把最小 x/y 平移到 0。
|
||||
- 邻接:两矩形共享 ≥ 1 单位长度的边(`rectsShareEdge`),只认四向。2×2 与只碰角的 1×1 不相邻(1-4 就靠这个)。
|
||||
- `unitMap: Int16Array` 做单位格 → 格下标的 O(1) 查询,空洞为 -1。
|
||||
- `sharedEdge(a,b)` 返回共享线段和方向,画桥用。
|
||||
- 运行时统一用**格下标**(`index`)引用格;`id` 只在 JSON 和工具输出里用。
|
||||
|
||||
**game.ts**
|
||||
- 状态 `empty | drawing | idle | cleared`;`stepOfCell: Int16Array`(0 = 未走)。
|
||||
- `start`:路径非空或已过关 → 无事件;不是合法起点 → `rejectStart{cell, clueCell}`。
|
||||
- `append`:必须 `canAppend`(相邻且未走且未过关)。每次 `push` 后:若 `isSolved()`(填满且无冲突)→ `cleared` 并锁定;否则若 `legalMoves()` 为空 → 附带 `deadEnd`。
|
||||
- 冲突判定是派生的:`isConflict(cell) = clue !== null && step !== 0 && step !== clue`,撤销即自动恢复。
|
||||
- `reset` 在空盘上不产生事件(避免视图无谓刷新)。
|
||||
|
||||
**input.ts**
|
||||
- 内部只有两个字段:`drawing`(手指是否处于画的状态)和 `hoverCell`(去抖,同一格不重复触发)。
|
||||
- `down` 的分支顺序:路径空 → start;末端 → resume;任意更早历史格 → rewind + resume;可追加 → append;否则忽略。
|
||||
- `move` 只在 `drawing` 时生效;`up` → release;`cancel()` 给重开按钮同步状态用。
|
||||
- 空白 `-1` 会被记为 hover,但不改路径。所以手指在缝里滑不会触发任何事。
|
||||
|
||||
**solver.ts**
|
||||
- 回溯 DFS。下一步若有线索格 → 只能走它(且必须相邻);否则候选 = 相邻、未走、**不是线索格**(线索格只能在它的步号进入)。
|
||||
- 剪枝:每个节点做一次 BFS(只经未走格)——可达未走格数 < 剩余数 → 剪;每个未来线索 `s` 的 BFS 距离 > `s - step` → 剪。
|
||||
- Warnsdorff 排序(空邻居少的先走)让 `limit: 2` 的唯一性检查很快停。
|
||||
- 起点:有线索 1 只从它起;否则全部格(跳过 clue ≠ 1 的格)。`opts.starts` 可指定,`opts.ignoreClues` 给设计工具枚举全部哈密顿路径。
|
||||
- 全部 10 关求解 < 0.2ms;GDD §7.3 的 15 格样例盘也毫秒级。上限 N ≤ 22 是 GDD 定的,没测过更大。
|
||||
- `hint(solution, path)`:算最长公共前缀 L。`path` 空 → `start`;L = path 长度 → `next`(或 `none`);L = 0 → `restart`(只在无线索 1 的盘可能发生);否则 `backtrack{cell: path[L-1], undoCount}`。
|
||||
|
||||
**layout.ts**
|
||||
- `fitBoard(board, availW, availH, {padding, maxUnit, gapRatio=0.1, radiusRatio=0.18})`:`unit = min(maxUnit, (W-2p)/cols, (H-2p)/rows)`,居中。`web/main.ts` 传 `padding: 10`,`maxUnit` 在 `cols*rows ≤ 6` 时 96 否则 110(防止 3 格的教学关铺满屏)。
|
||||
- `cellRect` 是扣掉缝的绘制矩形,`cellBounds` 是不扣缝的命中矩形。
|
||||
- `bridgeRect(a, b, thickness=0.46)`:横跨缝(`gap + 2px` 防抗锯齿露缝),沿共享边中点居中,宽 0.46 unit。与格同色即成连续路径。
|
||||
- `hitTest(..., inset=0.14)`:先按单位格查,再检查死区,是则返回 -1。Web 触屏传 0.08,鼠标传 0.14。
|
||||
|
||||
**view.ts**
|
||||
- 每格一个 `CellAnim {scale, scaleVel, flash, shake, numberAlpha, sweep}`,桥一个 `bridges[i]` 进度数组。
|
||||
- 常量:`POP_IMPULSE 2.4 / SPRING_K 260 / SPRING_DAMP 18`(落子弹簧),`FLASH_DECAY 3.2/s`,`SHAKE_TIME 0.28s`,`NUMBER_FADE 8/s`,`BRIDGE_GROW 10/s`,`HINT_TIME 2.4s`,`CLEARED_SWEEP_TIME 0.32s`,`clearedDuration = 0.32 + 0.55 s`(宿主据此跳关)。
|
||||
- `draw` 四遍:底板 → 桥 → 线索描边/数字/冲突角标 → 提示(琥珀外框脉冲;`backtrack` 时把要退的段用背景色压暗 45%)。
|
||||
- `update(dt)` 把 dt 截到 50ms 防切后台回来爆炸;`isAnimating` 为假时宿主不重绘。
|
||||
- `onEvent` 会清掉当前提示(任何一步操作都让提示消失)。
|
||||
|
||||
**progress.ts**
|
||||
- 键 `mode:01-number-stroke`,值 `{"cleared":[...],"currentId":"1-3","prefs":{"sound":true,"haptics":true}}`。
|
||||
- 线性解锁 `isUnlocked(ids, i) = i === 0 || cleared.has(ids[i-1])`;`firstUncleared` 用于“继续”按钮。
|
||||
- 读档 try/catch,坏 JSON 当没有。写档失败(私密模式)静默。
|
||||
- 没有 schema 版本号(见 §4.1)。
|
||||
|
||||
**web/main.ts**
|
||||
- `App` 单例。`Session` 持有 `level/game/view/input/solution/layout`,每进一关重建。
|
||||
- `resize()`:`ResizeObserver` 盯 `#board-wrap`,canvas 按 `devicePixelRatio`(上限 3)设物理尺寸并 `setTransform`。
|
||||
- 音频在第一次 `pointerdown` / 按钮点击时 `sfx.unlock()`(iOS 需要用户手势)。
|
||||
- 过关后禁用重开/提示按钮,`advanceTimer` 到点跳关;返回按钮会 `clearTimeout`。
|
||||
- 提示的 `restart` 态会让重开按钮加 `.pulse` 动画。
|
||||
|
||||
**web/style.css**
|
||||
- `100dvh`(带 `100vh` 兜底)、`env(safe-area-inset-*)`、`touch-action: none`(canvas)/`manipulation`(body 防双击缩放)、`user-select: none`。≥ 520px 宽时显示手机框。
|
||||
|
||||
### 2.5 关卡格式与校验流水线
|
||||
|
||||
```json
|
||||
{ "id": "1-9", "teach": "设计备注,不显示", "caption": "可选,显示在顶部",
|
||||
"cells": [ { "id": "a", "x": 0, "y": 0, "w": 1, "h": 1, "clue": 1 }, ... ] }
|
||||
```
|
||||
|
||||
改关卡的标准流程:
|
||||
|
||||
1. 编辑 `levels/pack-1-tutorial.json`(或新建包文件,`validate` 会扫 `levels/*.json`)。
|
||||
2. `npm run validate -- 1-9` 看盘面、解、无线索路径数。多解/无解会打 ✗ 并退出码 1。
|
||||
3. `npm run design -- 1-9 --minimize` 看哪些线索可删;`--suggest 3 --path a,e,g,...` 让工具给一条作者解找最少线索(排序偏好:少用 1/N 端点、线索分布均匀)。
|
||||
4. `npm run dev` + `?dev=1` 实际滑一遍。
|
||||
5. `npm run check`。
|
||||
|
||||
`tests/solver.test.ts` 断言第一、二包各 10 关、第三包 6 关且全部唯一解;增删关卡记得同步包内数量。
|
||||
|
||||
### 2.6 构建与脚本
|
||||
|
||||
| 脚本 | 做什么 |
|
||||
|---|---|
|
||||
| `npm run dev` | `node scripts/build.mjs --serve`:esbuild context,watch + serve `web/`。`--port N` 或 `PORT=N` 指定端口,否则从 8000 起找空闲 |
|
||||
| `npm run build` | 同一入口,minify,输出 `web/dist/app.js` |
|
||||
| `npm run typecheck` | `tsc -p tsconfig.json`(全项目,含 DOM)+ `tsc -p tsconfig.src.json`(仅 src,无 DOM) |
|
||||
| `npm run test` | `node --import tsx --test tests/*.test.ts` |
|
||||
| `npm run validate` / `npm run design` | 同上,`node --import tsx tools/xxx.ts` |
|
||||
| `npm run check` | 以上三项串起来 |
|
||||
|
||||
依赖只有 4 个 devDependencies:`esbuild`、`tsx`、`typescript`、`@types/node`。Node 24 开发,Node ≥ 20 应该都行。
|
||||
|
||||
为什么是 `node --import tsx` 而不是 `npx tsx`:tsx 的 CLI 会在系统临时目录建一个 IPC 管道,在受限(沙箱)环境里会 EPERM;`--import tsx` 只挂 loader,不建管道。正常机器上 `npx tsx file.ts` 也能用。
|
||||
|
||||
### 2.7 测试
|
||||
|
||||
**单元测试(46 个,`tests/`;奇偶覆盖见 §0.1)**
|
||||
- `board`:四向邻接、角对角不相邻、2×2 多邻接、长条长边邻接、空洞、共享边、非法盘抛错。
|
||||
- `game`:起手合法性(有/无线索 1)、追加限制、冲突不中断、死路事件、过关锁定、release/resume/reset 流转、撤销到空。
|
||||
- `input`:滑动追加/滑回撤销/松手保留、从末端继续、点前一格撤销、点着走、非法起手、忽略不相邻、过关后失效。
|
||||
- `solver`:10 关唯一解、GDD §7.3 样例盘、无线索 1 枚举起点、多解/无解、提示四态。
|
||||
- `layout`:适配与 maxUnit、命中/死区/空洞、桥矩形位置。
|
||||
- `progress`:线性解锁、存档恢复与偏好、坏存档不炸。
|
||||
|
||||
**真实触摸自动化(已验证但脚本没入库)**
|
||||
用带 CDP 的 Chromium(我用的是本机 ego-browser,Playwright/Puppeteer 同理)在 390×844、触摸仿真下把 10 关全部用 `Input.dispatchTouchEvent` 滑通,检查 `debugState().status === 'cleared'`、路径与解一致、localStorage 内容正确;另测了 1-6 的故意走错 → 冲突 → 提示回退 → 滑回 → 正解。核心片段(可直接搬进任何 CDP 环境):
|
||||
|
||||
```js
|
||||
// 视口 + 触摸仿真
|
||||
await cdp("Emulation.setDeviceMetricsOverride", { width: 390, height: 844, deviceScaleFactor: 2, mobile: true });
|
||||
await cdp("Emulation.setTouchEmulationEnabled", { enabled: true, maxTouchPoints: 1 });
|
||||
// 进关:打开 /?dev=1,点第 n 个 .level-tile
|
||||
// 拿格中心(视口坐标)与状态
|
||||
const pts = await evaluate((ids) => ids.map((id) => window.__numberStroke.debugCellCenter(id)), ids);
|
||||
const st = await evaluate(() => window.__numberStroke.debugState()); // {id, path, status, remaining, solution}
|
||||
// 沿格中心序列滑动,两格之间插 4 个点
|
||||
const touch = (type, x, y) => cdp("Input.dispatchTouchEvent", { type,
|
||||
touchPoints: type === "touchEnd" ? [] : [{ x, y, radiusX: 8, radiusY: 8, force: 1, id: 1 }] });
|
||||
await touch("touchStart", pts[0].x, pts[0].y);
|
||||
for (let i = 1; i < pts.length; i++) for (let k = 1; k <= 4; k++) {
|
||||
const t = k / 4, a = pts[i - 1], b = pts[i];
|
||||
await touch("touchMove", a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
|
||||
}
|
||||
await touch("touchEnd", 0, 0);
|
||||
```
|
||||
|
||||
建议把它做成 `tests/e2e/` 下的 Playwright 脚本入库(见 §4.2)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 与这次实现相关的其他内容
|
||||
|
||||
### 3.1 本次改动的仓库文件
|
||||
|
||||
- 新建:`modes/01-number-stroke/` 下除 `docs/GDD.md` 以外的全部文件。
|
||||
- 重写:`levels/pack-1-tutorial.json`(原 5 关 → 10 关;1-1~1-5 坐标未动,只给 1-2/1-3 补了 `caption`,包标题改“入门”)。
|
||||
- 重写:`modes/01-number-stroke/README.md`(目录、运行、关卡表、交互清单、Cocos 移植 8 步)。
|
||||
- 改一行:根 `README.md` 与 `docs/PRODUCT.md` 里玩法一的状态 → “Web 原型可玩,10 关”。
|
||||
- **未改** `docs/GDD.md`。
|
||||
|
||||
### 3.2 与 GDD 的偏差与补充(接手人需要知道的口径)
|
||||
|
||||
| 项 | GDD | 实际 | 原因 |
|
||||
|---|---|---|---|
|
||||
| 包大小 | 一包 8 关,教学 12 关 | 第一包 10 关 | 用户要求“自己设计 10 关”。GDD 文本未同步,是否改由负责人定 |
|
||||
| 字幕 | 只有 1-1、1-4 有一行文案 | 1-1~1-4 都有 | 帮第一次玩的人;仍在“1-1~1-4 教完规则”的窗口内 |
|
||||
| 未走线索格样式 | “未走线索格用白字” | 灰底 + 近黑粗描边 + 近黑数字 | 白字在灰底上对比度不够;已走后变紫底白字白描边 |
|
||||
| 冲突“小标记” | 叠一个小标记 | 右上角深色圆角标,写应有步号 | 顺便告诉玩家“应该是几” |
|
||||
| 点末端旁的空格 | 未规定 | 追加一步 | 让“点着走”成立,降低滑动门槛 |
|
||||
| 菜单按钮 | 汉堡菜单:关卡列表 / 返回壳 | 左上返回箭头回选关 | 壳不存在 |
|
||||
| 音效/振动开关 | 属于壳 | 暂放在选关页底部 | 原型没壳;进壳后删掉这两个 chip,从 `ShellContext` 读 |
|
||||
| `hintPath` | 可在编辑器烘焙 | 未用,进关时 `uniqueSolution()` 现算 | 毫秒级,没必要 |
|
||||
| 输入细节 | 未规定 | 插值采样、触屏 8% / 鼠标 14% 死区、单指针 | 手感需要;参数见 §2.4 |
|
||||
| 无线索 1 的样例关 | VS 第 5 条要至少 1 个 | 没出 | 见 §4.2 |
|
||||
|
||||
### 3.3 GDD 里发现的问题(建议负责人修文)
|
||||
|
||||
- §7.1/7.2 的 12/8 关与当前 10 关不一致(见上表)。
|
||||
|
||||
### 3.4 环境坑
|
||||
|
||||
- **Cocos 不允许 `import './x.ts'`**。`src/` 里任何新文件都保持无后缀 import,否则迁移时全要改。
|
||||
- 本机 5173 被别的服务占着,所以 dev server 默认自动找端口;看终端打印的地址。
|
||||
- `os.networkInterfaces()` 在受限环境会抛错,`build.mjs` 已 try/catch,只是打不出局域网地址。
|
||||
- Cursor 内置浏览器打不开 `file://`;要么走 dev server,要么用系统浏览器双击 `web/index.html`。
|
||||
- 想杀 dev server 用 PID,不要 `pkill -f "build.mjs --serve"`——如果你所在的 shell 命令行本身包含这个字符串,会把自己一起杀掉(我踩过)。
|
||||
- `.gitignore` 排除了 `node_modules/` 和 `web/dist/`。仓库目前**不是 git 仓库**,需要的话在根目录 `git init`。
|
||||
- iOS Safari 没有 `navigator.vibrate`,振动开关在 iOS 上无效属正常;Android Chrome 有。
|
||||
- iOS 音频必须在用户手势里 `AudioContext.resume()`,已在首次 `pointerdown` 处理;静音键打开时 WebAudio 也会被静音,这是系统行为。
|
||||
|
||||
### 3.5 存档格式
|
||||
|
||||
`localStorage["mode:01-number-stroke"]`:
|
||||
|
||||
```json
|
||||
{ "cleared": ["1-1", "1-2"], "currentId": "1-3", "prefs": { "sound": true, "haptics": true } }
|
||||
```
|
||||
|
||||
只存 id,不存半途路径(GDD §10)。不得读写其他玩法的桶。
|
||||
|
||||
### 3.6 调试句柄(仅 `?dev=1`)
|
||||
|
||||
- `window.__numberStroke.debugCellCenter("B")` → 该格中心的 `clientX/Y`,自动化滑动用。
|
||||
- `window.__numberStroke.debugState()` → `{ id, path: string[], status, remaining, solution: string[] | null }`。
|
||||
- 选关页所有关解锁(不写存档,只是渲染时放行)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 可优化的点,以及接下来要做的事
|
||||
|
||||
### 4.1 已知不足 / 风险(按我认为的优先级)
|
||||
|
||||
1. **没有真机、真人数据。** 所有手势验证来自触摸仿真。触屏死区 8% / 鼠标 14%、插值步长 unit/4、`maxUnit` 96/110、过关 0.87s 跳关,这些数字都要在真机上摸过再定。GDD §16 的验收(不看字在 1-4 前学会、1-7 出现停顿)需要 3~5 个没玩过的人。
|
||||
2. **无线索 1 已有 2-8**;后续可补奇偶与未知起点组合的正式关卡。
|
||||
3. **存档没有版本号。** 上线前加 `"v": 1`,`Progress.load()` 里按版本迁移。
|
||||
4. **多包已完成。** 选关按包分组,包间线性解锁,目前三包 26 关。
|
||||
5. **奇偶生成器待补。** 现有 `generate.ts` 已产出第二包数字关;奇偶条件当前经人工选择和求解器验证。
|
||||
6. **视觉细节**:线索命中的闪白描边会溢出格外一点(已收窄到 1.6 倍线宽,仍可再调);过关扫光是“白色 sin 脉冲”,比较朴素;数字字体依赖系统字体栈,不同机器观感不一。
|
||||
7. **BoardView 是即时模式全量重画。** Web 上无压力;到 Cocos 若用 `Graphics` 每帧 clear + 重画也能接受(≤ 22 格 + ≤ 21 桥),但 `Label` 节点需要池化复用,不能每帧新建。若性能不理想,改成“每格一个节点、事件驱动更新”的保留模式——那时 `BoardView` 的动效状态仍可复用,只是 `draw()` 换成 `sync()`。
|
||||
8. **hint 是运行时求解。** N 一旦上到 22 以上或做用户造关,改成烘焙 `hintPath` 到 JSON(GDD 给了字段)。
|
||||
9. **提示无限次无广告**是竖直切面的设定;上线要接“每天 1 次免费 + 激励视频”,位置在 `App.hint()` 入口。
|
||||
10. **文案全是硬编码中文**(`index.html`、`main.ts` 的 toast)。要多语言就抽成表。
|
||||
11. **高对比模式**(冲突加斜纹)没做;`Painter` 需要加一个斜纹填充原语,`THEME` 加高对比变体,开关从壳读。
|
||||
12. **选关页在超高屏上中部留白大**,可以把“继续”按钮上移或加规则三行说明;对局页盘面严格垂直居中,拇指够不到顶部大盘时可考虑整体上移 5~8%。
|
||||
|
||||
### 4.2 接下来要做(建议顺序)
|
||||
|
||||
**A. 收尾 M2(1~2 天)**
|
||||
- 真机跑一遍(iPhone Safari、Android Chrome 各一台):调 §4.1 第 1 条的参数,看 `100dvh`/安全区/音频解锁/振动。
|
||||
- 在已有 2-8 上验证未知起点的提示体验,再考虑加入奇偶组合。
|
||||
- 把 §2.7 的触摸脚本落成 `tests/e2e/play-all.spec.ts`(Playwright),CI 里跑。
|
||||
- 找 3~5 人试玩,按 GDD §16 记录:在哪一关卡住、是否理解大格、1-10 是否用了提示。只改关卡数据,不改规则。
|
||||
- 决定 GDD §7.1/7.2/7.3 的文本要不要同步(§3.3)。
|
||||
|
||||
**B. M3 内容(32 关)**
|
||||
- 写 `tools/generate.ts`:随机铺 1×1/2×1/1×2/2×2 + 空洞(保证连通)→ `solve(board, {ignoreClues: true, limit: 1})` 随机取一条作者解(给 DFS 加随机排序)→ 复用 `design.ts` 的 suggest/minimize 挑 2~4 个中段线索 → 导出 JSON。
|
||||
- “漂亮度”过滤:无意义绕圈(连续 U 形折返次数)、线索读得清(不挤在一起)、大格至少被路径真正利用一次。
|
||||
- 每关人过一眼(GDD §8 要求),`validate` 全绿。
|
||||
- 多包 UI 与包间解锁已完成,新增包只需登记并校验。
|
||||
|
||||
**C. 壳接入(等第二个玩法有原型后)**
|
||||
- 把 `web/main.ts` 的 `App` 改造成 `PuzzleMode { id: '01-number-stroke', title, mount(root, ctx), unmount() }`(`docs/PRODUCT.md` 的接口)。`ctx` 提供存档、音效、振动、返回首页——对应现在的 `LocalStore`、`WebAudioSfx`、`vibrate()`、返回按钮。
|
||||
- 删掉选关页的音效/振动 chip,读壳设置;高对比也从这里来。
|
||||
- 广告位占位(底 banner)按 GDD §9 加在对局页底部。
|
||||
|
||||
**D. M4 迁 Cocos Creator 3.x**(详见 README“移植到 Cocos Creator”,这里补执行顺序)
|
||||
1. 新建 Cocos 工程,`assets/modes/01-number-stroke/scripts/` ← 拷 `src/` 全部文件,不改一行。先让工程编译过(`tsconfig.src.json` 已保证无 DOM)。
|
||||
2. 关卡 JSON 放 `assets/modes/01-number-stroke/resources/levels/`,`resources.load('levels/pack-1-tutorial', JsonAsset)` → `parsePack/loadPack`。
|
||||
3. 写 `GraphicsPainter implements Painter`:一个 `Graphics` 组件画矩形/圆(`roundRect` + `fill/stroke`,注意 Cocos y 轴向上,在 Painter 内 `y' = H - y - h`),一个 `Label` 对象池写数字(每帧先全部隐藏再按需取用)。
|
||||
4. 写 `BoardController`(组件):`onLoad` 建 `Game/BoardView/PointerInput`,`update(dt)` 调 `view.update(dt)`,`isAnimating` 时 `graphics.clear()` + `view.draw(painter)`;`Node.on(TOUCH_START/MOVE/END/CANCEL)` → `UITransform.convertToNodeSpaceAR` → `hitTest` → `PointerInput`。TOUCH_MOVE 里照搬 `web/main.ts` 的插值采样。
|
||||
5. `Progress(new CocosStore())`,`CocosStore` 包 `sys.localStorage`。
|
||||
6. HUD、选关、字幕、toast 用 UI 节点复刻 `index.html` 的结构;音效换 `AudioSource` 四段短音。
|
||||
7. 用 §2.7 的路径数据在真机上把 10 关滑一遍。
|
||||
8. 不搬:`web/`、`tools/`、`tests/`(测试继续在 Node 跑,只依赖 `src/`)。
|
||||
|
||||
### 4.3 可选的产品/内容方向(不阻塞上面任何一项)
|
||||
|
||||
- 每关记录用时与提示次数(本地),给 M3 调难度用。
|
||||
- 过关动画换成“路径按顺序亮一遍 + 终点格弹一下”,比现在的扫光更有“一笔画完”的感觉。
|
||||
- 撤销加一个极短的褪色(现在是瞬时),看真机手感再定。
|
||||
- 关卡编辑器 UI(GDD 明确 VS 不做;`design.ts` 已够用一阵)。
|
||||
|
||||
---
|
||||
|
||||
## 附:常用命令速查
|
||||
|
||||
```bash
|
||||
npm run dev # 起服务(改动自动重打包)
|
||||
npm run check # 全部检查
|
||||
npm run validate -- 1-10 # 看某关盘面/解
|
||||
npm run design -- 1-10 --minimize # 线索能不能再删
|
||||
npm run design -- 1-10 --suggest 2 --path a,b,B,c,d,e,f,h,g,l,k,j,i,V
|
||||
npm run design -- /path/to/draft.json --suggest 3 --path ... # 临时关卡文件也行
|
||||
```
|
||||
Reference in New Issue
Block a user