这个游戏从不手工编写谜题。它从一个数字中“长”出一个谜题,然后在你看到之前,先让这个谜题和自己对弈一番,确保它既不过于简单,也并非无解。
本文以英文撰写和编辑。此中文版本由机器翻译生成;凡涉及准确性之处,以英文原文为准。 阅读英文原文 →
Contraption 的关卡并没有存储在任何地方。没有哪个文件列出目标在哪里、平台如何排布。相反,整个关卡——小球的起点、目标的位置、固定墙体的数量与布局、交到你手里的零件清单——都是由一个叫作“种子”的起始数字计算出来的。给生成器同一个种子,你得到的就是逐字节完全相同的谜题。1
之所以能做到这一点,是因为生成器唯一的“随机性”来源是一个小巧的确定性伪随机数生成器。Contraption 使用的是 mulberry32,这是 Tommy Ettinger 编写的一个紧凑的 32 位生成器,你可以凭它的标志性常数 0x6d2b79f5 认出它——每次取数时都会把这个常数加到状态上。2 它速度快,几行代码就能写完,而且关键在于,它根本不是随机的:它是一个固定的数学序列,只是看起来像被打乱了而已。给它一个起点,它就会永远产生同一串数字。生成器随后读取这串数字,来决定每样东西放在哪里。
“每日”(Daily)模式借助这种确定性,做了一件带有社交意味的事。它的种子不是随机的,而是由日历制造出来的。游戏取当天的世界时日期,把它写成类似 "contraption:2026-7-28" 这样的短字符串,再把这段文本送进一个哈希函数,把它压缩成一个 32 位数字。1
这个哈希函数是 FNV-1a,一种著名的非加密混合函数:它从一个固定的偏移量开始——十进制的 2166136261,即 0x811c9dc5——对每个字符先用异或把它并入,再乘以 FNV 素数 16777619。3 得到的数字对某一天是稳定的,而从一天到下一天则天差地别,因此日期中只要改动一个字符,种子就会被彻底打散。由于日历对每个人都一样,种子也一样,谜题也就一样。身处世界两端的两个陌生人打开“每日”模式,面对的是完全相同的场地,而这恰恰是比较分数有意义的原因。
每日挑战之所以公平,并不是因为服务器给每个人发送了同一个关卡,而是因为每个人手中的副本都从同一个日期独立地重新算出了同一个关卡。
随机几何很容易;好的随机几何却不容易。一个只会随手撒下目标和几块平台的生成器,有时会造出这样的关卡:小球从顶部落下,自己就径直掉进了目标。那不是谜题,而是过场动画。因此,Contraption 在接受一个候选世界之前,会悄悄运行一项代码中称为 preSolved 的测试:它在一个零件都不放的情况下模拟该关卡,检查光凭重力,裸球能否到达目标。如果能,这个关卡就被丢弃,生成器重新掷骰。1
这是对我们平常看待物理引擎方式的一种微妙反转。演算你那台机器的同一个 simulate() 函数,在生成阶段被转向内部,用来审核关卡本身的难度。引擎既是你解法的裁判,也是谜题的质检员。
拒绝过于简单的关卡只完成了一半工作。另一个危险是无解的关卡:目标藏在任何零件摆法都够不到的地方。Contraption 用一个名为 plausiblySolvable 的小型暴力求解器来防范这种情况。它并不聪明地思考,它只是逐一尝试。1
具体来说,它把一块斜坡在场地上的一张位置网格中逐点扫过,并在每个位置尝试四种不同的倾斜角度。对于每一个试放位置,它都运行完整的模拟并问:这一块斜坡把球送进目标了吗?只要有任何一个位置成功,这个关卡就被判定为可解,并交到你手上。如果扫完整张网格都没有成功,生成器倾向于丢弃这个关卡,换一个新的种子重试。
| 扫描参数 | 范围 | 含义 |
|---|---|---|
| 斜坡 X | 15 → 85,步长 14 | 横跨场地的各列 |
| 斜坡 Y | 30 → 120,步长 18 | 沿场地向下的各行 |
| 尝试的角度 | 4 | ±0.45 与 ±0.8 弧度 |
| 每次放置的测试 | 1 次完整模拟 | 球是否到达目标? |
值得坦率说明这究竟证明了什么,因为代码本身对此也很坦率:注释称之为“廉价的可行性检查……并非穷举”("cheap plausibility check … not exhaustive")。通过检查的关卡肯定至少有一个粗略的单斜坡解。未通过的关卡却仍可能用风扇、齿轮,或是网格从未试过的更巧妙的斜坡解开,因此生成器并不把失败当作无解的证明。它会把找到的第一个非平凡世界留作后备,如果尝试了四十次仍没有得到一个干净的单零件解,就交出这个后备关卡,而不是无休止地循环下去。结果是一种务实的保证:绝不白送胜利,几乎总有一条真正的解题路径,也绝不会为了“完美”关卡而无限等待。1
退一步看,Contraption 的关卡生成器是一个非常古老、非常通用的问题求解模式的清晰范例:生成与测试。随机提出一个候选;对照你的要求检查它;通过就保留,不通过就丢弃并重新提出。这正是无数游戏中程序化内容背后的循环,也是大量演化计算背后的循环,而若眯起眼睛来看,它甚至就是自然选择本身:突变负责提出,环境负责测试,幸存者得以延续。4
Contraption 的版本之所以优雅,在于它的测试不是可玩性的廉价替代指标,它就是可玩性本身。生成器并不靠数平台或量距离来估计关卡是否公平。它真的把关卡玩了两遍:一遍空着玩,确保它不是白送;一遍放一块试探性的斜坡,确保存在一条路径。将来裁判你解法的那套确定性物理,正是审核过这道谜题的物理。不存在一个可能与真实规则脱节的独立“关卡验证器”,因为规则只有一套,用于一切。
而且由于一切都是确定性的,这项审核在两个方向上都与运气无关。每日种子不可能暗中给某位玩家一块比别人更容易的场地,也不可能给任何人一块不公平的场地,因为在关卡成为挑战之前,它已经经受住了被游戏自己拿来对弈的考验。