本系列:把这两年用 Python 写的十几个小项目挨个开源、挨个说清楚。不讲”我做了什么”,讲”做的过程中被什么打脸、最后怎么想明白的”。 第 7 篇 · 方寸 FANGCUN(前几篇:离阵 / 胶囊医生 / 烬塔 / 回响回廊 / 砂漏 / 纯粹数独) 源码:github.com/ghostgorge/FANGCUN 一句话:经典推箱子的机制重构。80 关全部由求解器验证,每一关的 par 都是移动步数最优解
一、先说那个最重要的设计决定
推箱子这个玩法有一个几十年没解决的老问题:它的挫败感和它的深度是绑在一起的。
你玩了二十步,突然发现第 3 步就已经把路堵死了。传统解法有三种,全都不好:限制撤销次数(那就是逼你重来)、不给撤销(更狠)、给无限撤销但不给任何反馈(那玩家就无脑试错了)。
方寸的做法是:
撤销无限、免费、不消耗任何东西。惩罚从”损失时间”改成”损失评级”。
| 评级 | 条件 |
|---|---|
| ★★★ | 步数 ≤ par |
| ★★ | 步数 ≤ par × 1.3 |
| ★ | 通关 |
而 par 是求解器算出来的移动步数最优解,不是拍脑袋定的。
于是:你可以随便试错,但想拿三星必须真的想明白。 挫败感消失了,深度一点没少。
提示同理,压评级上限而不是收费:一级 → 最高 ★★,二级 → 最高 ★,三级(播放完整解法)→ 不计评级。作弊通关同样不计评级。
这条设计的通用形式我觉得值得记:当一个惩罚同时打击”试错”和”想不明白”两件事时,把它拆开,只惩罚后者。
二、招牌机制:时间回响
第 7 章的机制,也是整个项目里我最想做的那个。
踩上回响点 → 开始录制 → 你继续走,每一步都被记录 → 按 T 结束 → 场景回退到你踩上去的那一刻,同时出现一个重放你刚才动作的半透明分身。之后你每走一步,它同步重放一步。
它能推箱子、能踩压力板、会挡路,而且序列走完后驻留原地不消失——这一条是必须的,否则它永远压不住压力板,”回响 + 压力板”这个组合就废了。
于是思考方式整个变了:
从”我怎么走”,变成”我先怎么走给未来的自己铺路,再怎么配合过去的自己“。
十章机制表:
| 章 | 机制 | 说明 |
|---|---|---|
| 1 | 基础推箱 | |
| 2 | 坑 / 填坑 | 箱子推进坑 → 永久消失,坑变成路。资源管理:谁是牺牲品? |
| 3 | 冰面 | 滑到撞墙才停。停位由障碍布局决定,不由推力决定 |
| 4 | 压力板 + 门 | 箱子既是货物又是钥匙 |
| 5 | 传送门 | 人和箱子都能过,冰上滑行穿过后保持滑行 |
| 6 | 拉钩 + 炸药 + 锚点 | 数量由关卡固定给定,用完没有 |
| 7 | 时间回响 | 见上 |
| 8 | 深坑 | 要连填两个箱子才平 |
| 9–10 | 纯组合 | 无新机制,极限难度 |
三、难度不是手感,是入库前算出来的数字
这是本项目和普通推箱子最大的区别。每一关进入关卡库之前,必须依次通过四道闸门:
- 可解——A* 求出移动步数最优解,这个长度就是 par
- 机制必需——把本章新机制禁用后重解一次,必须解不出来
- 不无脑——三个自动打手跑一遍,通过率超过本章上限就丢弃
- 预见深度——F 落在本章区间内
闸门 2:绝大多数”机制关卡”里的机制其实是装饰
这道闸门是我最推荐给任何做机制类关卡的人的。
判据极简:把这一章的新机制关掉,再解一次。如果还能解出来,说明这个机制是装饰品——”不用冰也能通关的冰关卡”,冰就是画上去的。直接丢弃。
实测被这道闸门拦下来的量:
| 章 | 因”机制非必需”丢弃 |
|---|---|
| 3 冰面 | 83 个 |
| 4 压力板 | 51 个 |
| 5 传送门 | 66 个 |
| 7 回响 | 327 个 |
回响这一章,生成 327 个”回响关卡”,里面的回响全都是可有可无的。 如果没有这道闸门,我会自信满满地把它们发出去,然后玩家会觉得”这个机制好像没什么用”——而我永远不知道为什么。
闸门 3:打手的智力等于一个走神的玩家
三个自动打手(贪心 / 就近 / 扫描)都不做搜索。它们会规避明显死锁(真人也不会主动把箱子往角落推)、有随机探索、会重试——它们的智力就等于一个凭手感推的走神玩家,而那恰恰是要防的失败模式。
第 9、10 章的上限是 0%:只要有任何一个打手通过任何一次,那关就删掉重生成,不接受人工豁免。
闸门 4:预见深度 F
F 衡量的是:做出错误选择,到能察觉自己错了,中间隔了多少步。
F = 1:把箱子推进角落,立刻看出死了F = 15:舒舒服服玩十五步,然后发现第 3 步就已经把路堵死了
长度不等于难度。20 个箱子的关卡只是烦,玩家不用在脑子里存任何东西;6 个箱子但第 3 步悄悄断送第 25 步的关卡才是难的。
出厂关卡库实测:
| 章 | 关数 | par 范围 | 无脑打手最高通过率 |
|---|---|---|---|
| 1 | 8 | 5–13 | 1.00 |
| 2 | 8 | 9–23 | 0.50 |
| 3 | 8 | 14–26 | 0.50 |
| 4 | 8 | 16–31 | 0.33 |
| 5 | 8 | 12–21 | 0.33 |
| 6 | 8 | 9–29 | 0.00 |
| 7 | 8 | 17–33 | 0.00 |
| 8 | 8 | 14–24 | 0.08 |
| 9 | 8 | 12–45 | 0.00 |
| 10 | 8 | 14–30 | 0.00 |
四、教学关全局只有两关,且没有一个字的说明
1-1 除了推过去别无他法。 1-2 故意让你把箱子推进角落死一次,然后你自己会去按 Z。
就这样。后续每一章的新机制,全靠该章第一关的极简结构自解释——一个只有冰面和一堵墙的房间,你推一次就懂了。
这是从上一个项目学来的:教学关多了,前面几章就全是”看懂就能过”,玩家在真正的难度开始之前已经走了。
五、一条架构硬约束,和我自己破戒的后果
所有移动都必须走
rules.try_move()/step_game()。 玩家、被推的箱子、回响体、冰上滑行、传送门出口——全部。
理由是组合爆炸:冰 × 传送门 × 坑 × 门 × 回响 = 32 种交互。
走单一入口,它们是一个函数里的 32 条分支;各机制自己写移动代码,它们就是 32 个让求解器和渲染器对同一件事产生分歧的机会——而这类 bug 一旦有 80 关建立在它之上,基本没法调。
开发中我自己破过一次这条规矩:solver/search.py 的 successors() 图省事直接调了 try_move,绕过了回响推进。
后果是整个第 7 章看起来全部无解。
那一刻我第一反应是”回响这个机制设计得不对,状态空间太大了”——差点就把招牌机制砍掉了。真因是求解器走了旁门,它眼里的世界和玩家眼里的不是同一个。
六、已知限制(照实写)
- 第 6 章的 F 值不可信(全部顶在上限 24.0)。有拉钩/炸药时死锁检测必须整体关闭——拉钩能把箱子拖出角落、炸药能改拓扑,任何死锁判定都不再成立。于是 F 的探测器永远探不到”全死层”,直接撞上限。这一章的难度实际由打手通过率 0.00 保证,不由 F 保证。
- 含冰面/传送门的关卡 F 偏高,同理。F 只在同章内可比,跨章比较无意义。
- 求解器不支持长回响序列。回响宏动作枚举上限是 4 步(4+16+64+256 = 340 个分支),再长状态空间就失控。第 7 章的
echo上限因此设为 3——是求解器的能力边界决定了游戏的机制上限,不是反过来。 - 关卡编辑器没做(列为 v1.1);音效没做。
七、验证与上手
python -m fangcun.tools.test_rules # 33 项规则单测
python -m fangcun.tools.verify_library # 重解全部 80 关,校验 par 与打手上限
verify_library 有一个细节值得说:它是从实际出厂的数据文件重新解析求解的,不是从生成时的中间结果——所以它验的就是发出去的那个东西。生成流程和验证流程之间任何一处不一致,都会在这里暴露。
pip install pygame && python main.py # 直接跑
cd build && build_exe.bat # 出 build\dist\FANGCUN.exe
打包脚本会自动建干净 venv(不用 Anaconda)、装依赖、跑规则测试和关卡库验证,任何一步失败就中止。
exe 双击没反应时的分流手段:跑 fangcun_debug.spec(onedir + console)。能跑而单文件版不能 → 问题在解压到 %TEMP%(杀软 / AppLocker);调试版也挂 → 控制台直接给 traceback。另外启动日志始终写在 ~/.fangcun/startup.log。
存档是明文 JSON(~/.fangcun/save.json),可以自己改。
Z 撤销 · R 重开 · T 结束回响录制 · 1/2 拉钩/炸药 · 3 锚点 · H 提示 · Shift ×2 作弊窗口
尾巴
三条带得走的:
一、当一个惩罚同时打击”试错”和”想不明白”,把它拆开,只惩罚后者。 撤销免费 + par 定星,是我做过的最划算的一个设计决定。
二、机制必需性要用”禁用后重解”来验,不能靠眼看。 327 个回响关卡里的回响全是装饰——这个数字靠自己审关卡永远发现不了。
三、多机制交互必须走单一裁决入口。 5 个机制就是 32 种交互,让求解器和渲染器各写一份,等于给自己埋 32 颗雷。
📦 源码:https://github.com/ghostgorge/FANGCUN
下一篇:立方之蛇 CubeSerpent——三维贪吃蛇,一个被实机反馈直接推翻、然后整个重写的项目。回复「继续」。
