做一个”第一遍注定走不完”的迷宫——回响回廊 EchoHalls

本系列:把这两年用 Python 写的十几个小项目挨个开源、挨个说清楚。不讲”我做了什么”,讲”做的过程中被什么打脸、最后怎么想明白的”。 第 4 篇 · 回响回廊 EchoHalls(前三篇:离阵 / 胶囊医生 / 烬塔) 源码:github.com/ghostgorge/EchoHalls 技术栈:Python + pygame,8 章 48 关,关卡程序生成 + 六道筛子验收


一、迷宫游戏最容易犯的错

地图再大再花,玩家沿着墙摸一圈也就出去了。

贴墙法(左手法则)是个几百年前就有的万能解。只要迷宫是单连通的,闭着眼睛贴着一侧墙走,一定能走到出口。你花三天手工雕的 60×60 迷宫,玩家用一条不需要动脑的规则就通关了。

所以这个项目从第一行代码起就奔着一个判据做:

闭着眼睛现场探路的人,走不完。

一句话设计目标:做一个”第一遍注定走不完”的迷宫。

二、三样东西咬在一起

  1. 迷雾——暗层里光只照 2~5 格,你看不到全图。
  2. 步数上限——每关给的步数只比最短解多 10%~50%。
  3. 记忆留存——走过的格子以暗色永久留在图上,同一关重试之间保留。

于是游戏循环变成了:

第一遍 = 侦察    必然超步数,但你摸清了岔路在哪
第二遍 = 执行    照着脑子里(和图上暗色)的路直着走过去

失败不是惩罚,是信息采集。 这就是”回响”的意思——你走过的每一步都在墙上留了回声。

这三样缺一样都不成立:只有迷雾没有步数上限,玩家可以慢慢磨;只有步数上限没有迷雾,第一遍就看得见全图;有前两样但不留记忆,那就是纯粹的折磨,重试跟重来没区别。

一命通关模式「无灯之夜」 把这套循环整个拆掉:不能撤销、地图记忆不保留、48 关连着打,任何一次超步或掉光心就从第 1 关重来。它考的是你真的记住了没有。

三、验收:生成不算数,验收才算数

关卡是程序生成的,但每个候选要连过六道筛子。顺序按”便宜的排前面”——这一条是从上一个项目带过来的教训,贵的筛子放前面会白白烧掉十倍时间:

顺序筛子淘汰什么代价
1A* 求最短解无解的毫秒级
2步数区间太短(没内容)或太长(磨人)的免费
3贪心机器人一根筋直冲出口就能过的
4贴墙法机器人(左右手各一遍)经典万能解能过的
5盲探机器人 ×15 次一次探路就能过的
6道具必需性 / 机关深度道具是摆设的

第 5 道筛子,和我一开始搞错的地方

这是全套里最关键的一个,也是我纠正过的一个错误。

最初我拿”不用道具也能解”当淘汰条件——想法是”如果不用道具就能过,说明道具是白给的”。结果全军覆没,一关都过不了

原因很蠢但很重要:不带道具的完美求解器不是笨办法,它是一个更强的求解器。 任何谜题不在道具上的关卡它都能过。拿它当淘汰条件,等于要求”这关必须只能靠道具解”,那是另一种游戏。

对迷宫来说,真正的笨办法是闭着眼睛现场探路

  • 优先踩没走过的格子
  • 走进死胡同就退回来
  • 绝不主动往尖刺上撞
  • 但会用道具

任何迷宫这么走早晚都出得去——问题是要走多少步。于是判据自然浮现:跑 15 次(随机打破并列),统计”在步数上限内走出去”的比例。

这个比例就是盲探一次通关率。游戏里右侧面板直接把它显示给玩家看。

通用教训:设计”笨办法机器人”时,笨的方向要选对。 削掉能力(不许用道具)得到的是一个残废的强者;要削的是信息(看不见全图)和规划(走一步看一步)。前者测的是关卡的道具依赖,后者才是”这关需不需要动脑”。

步数上限才是真正的难度旋钮——它要卡在”探一遍走不完、背下来才走得完”之间。

四、实测曲线

名字主题最短解步数上限紧度盲探一次通关率
1石廊走路、钥匙、锁门44–5860–820.7337%
2灯下光只照三格50–7868–1020.760%
3尖牙尖刺按拍子、地板会塌64–8181–1010.801%
4推石压板与闸门46–6458–790.800%
5冰室冰上刹不住40–6651–820.811%
6深坑羽毛跳、钩爪56–7671–920.821%
7暗记全黑 + 三次回想79–14395–1610.852%
8回响全都来一遍68–10276–1090.900%

第 1 章是教学,故意留着 37%——前三关允许无脑走过去,玩家得先相信自己会走路。从第 2 章起压到 0~2%。

紧度(最短解 / 步数上限)从 0.73 爬到 0.90:第 8 章的步数余量只有 10%,几乎不允许任何一次走错回头。

五、机关:每一个都对应图论里的一个操作

塞尔达谱系的那套元素,我全部按”它在数学上是什么”来选:

元素它在数学上是什么
锁门 / 钥匙图上的强制拓扑顺序(门放割点,钥匙放门这一侧)
裂墙 / 炸弹同上,但资源是消耗品
压板 / 石块 / 闸门不可逆动作——推错方向就回不来
火把 / 灯笼光照资源
尖刺时间维度:按 (步数 + 相位) % 周期 < 占空 起落,原地”等”一步也是解法
碎地板单向边——走过就没了
深坑需要羽毛(跨一格)或钩爪(拉一串)
羽毛跳跳过一个点(2 步)
钩爪加一条长边——朝一个方向找铆点,把自己拉过去,中间可以是坑
冰面走一步 = 走一段,路径不再由你逐格控制
石块推进坑用一个不可逆动作换一条边
回想把”信息”标价成”步数”

最后一条是整个设计的支点。 暗层里你随时可以花 3 步买一次全图闪现,所以”记不住”永远有解,只是要付钱。第 7 章只给 3 次,第 8 章只给 2 次。

这解决了记忆类玩法最大的隐患:记忆力差不该等于玩不了。 把信息明码标价之后,记性好的人省下步数用来试错,记性差的人花步数买确定性——两条路都通,只是紧度不同。

六、工程:关卡不存地图,只存种子

core/rules.py     规则内核。状态压成不可变元组,UI 和求解器共用同一份规则
core/solver.py    A* 求最短解 + 四种笨办法机器人
core/gen.py       迷宫骨架(递归回溯 + 编织)+ 母题布点 + 章节配置
core/baked.py     自动生成:48 关的种子表(不要手改)
core/session.py   一局的运行时:迷雾、记忆、回想、撤销
tools/bake.py     扫种子、验收、写 baked.py

生成器是确定性的,同一个种子永远长出同一座迷宫,所以 48 关只存种子 + 章节配置。改生成器就得重烤(python tools/bake.py,约 5 分钟)。

A* 的启发式:状态空间爆炸先加启发式,再谈上限

一开始用纯 Dijkstra,推石关(两个石块可自由移动)状态直接炸到 6×10⁷,20 万节点的上限根本不够看。

解法是加一张**”把门、坑、闸全当通路,只留墙”的到出口距离表**当下界。它永远不会高估真实代价,所以是合法的 A* 启发式。同样的关卡从”跑不出来”变成毫秒级。

另一个几乎免费的优化:没有尖刺的关卡把周期设成 1,直接省掉状态里一个白白翻四倍的维度。

母题布点要反着定位

三个坑,都是”顺着放”必踩的:

  • 压板必须放在闸门的同一侧——否则要先过闸门才能开闸门,80% 的候选直接无解。
  • 推石要反着定位:先定压板,再沿某个方向往回数 k 格放石块,并确认石块背后还有一格能站人。随机撒点会大量产出推不动的死局。
  • 深坑两端都要能挂钩爪——只在一端放铆点,过去了就回不来。

共同点:先定”目标位置”,再往回推”起始位置”。 顺着随机撒点然后祈祷有解,命中率低一个量级。

七、成就:刻意不做重复劳动型

28 项,分四类:进度(每章一枚 + 全通)、精度(最短步数通关 1/10/30 关、一遍过 1/10 关)、自律(不用撤销、不用回想、毫发无伤)、硬核(无灯之夜活到第 3/6 章 + 隐藏的全通)。

刻意没有做任何”重复劳动型”成就——走 N 步那一枚是唯一例外,且门槛很低。

理由很简单:这个游戏的乐趣在于把一条路走短,不在于时长。 加一个”累计行走 10000 步”的成就,等于鼓励玩家做与核心乐趣完全相反的事。

尾巴

这个项目留下的最有价值的一条,是关于怎么设计验收机器人

削能力得到的是残废的强者,削信息才得到真正的笨办法。

判断”这关需不需要动脑”,不要问”少了某个工具还能不能过”,要问”看不见全图、走一步看一步的人,能不能在预算内出去“。

以及那条一以贯之的:筛子的顺序值十倍时间,便宜的必须排前面。

📦 源码https://github.com/ghostgorge/EchoHalls


下一篇:FANGCUN——回复「继续」。