无猜扫雷:怎么保证每一局都能靠推理走完
扫雷最烦的不是踩雷,是踩到一颗只能靠运气的雷。这篇讲生成器怎么用「先解一遍再发牌」保证整局可推理,以及为什么这件事比看起来贵。
扫雷输掉的原因有两种,性质完全不同:
- 看漏了。这是我的问题,重开一局就好。
- 必须猜。局面到了某一步,所有确定推理都用完了,剩下的只能掷骰子。
第二种让人不想再玩。所以「无猜」不是一个难度选项,它是这个游戏能不能被当成技术游戏来玩的前提。
无猜的判据
定义要精确,否则实现会跑偏:
一局是无猜的,当且仅当存在一条从开局到通关的路径,路径上每一步都只使用确定推理。
注意是「存在一条」,不是「所有分支都不需要猜」。后者太强,会筛掉大量本来很好的局面。
还有一条更隐蔽的约束:求解器只能使用玩家能看到的信息。如果它偷看答案来判断「这格是雷」,那生成出来的局面是「上帝视角可解、玩家不可解」—— 看起来满足条件,实际毫无价值。
最直白的做法:生成、求解、不行就重来
loop:
随机布雷
挖开一个安全起点
if 求解器能推完: 返回这一局
else: 重来
这就是拒绝采样。它正确、简单,而且在小棋盘上完全够用 —— 低雷密度时第一次就通过的概率很高。
代价是它的尾部很难看。棋盘一大、雷密度一高,命中率会掉得很快,偶尔要重试几十上百次。如果布雷本身是 O(n),最坏情况就是 O(n × 重试次数),而重试次数没有上界。
更省的做法:边生成边修
与其整局重来,不如把「必须猜」的那个位置直接改掉:
- 生成一局,求解
- 求解卡住时,找出当前所有未知格里无法被确定的那些
- 从里面挑一个,把雷挪走或挖开(改完之后它周围的开局信息也变了)
- 重新求解;反复几次仍不行,才整局重来
改一格通常比重新生成一整局便宜得多。实践中大多数“卡住”只需要挪一两颗雷。
求解器要覆盖到哪一层
这是整件事里唯一需要拿捏的地方。确定推理有三个层级:
| 层级 | 手法 | 覆盖 |
|---|---|---|
| 一 | 直接数数:某格周围剩余雷数等于未知邻居数 → 全是雷;等于 0 → 全安全 | 大部分 |
| 二 | 子集差集:A 的未知集是 B 的子集 → 差集里全是雷或全安全 | 一大批 |
| 三 | 局部枚举:把边界上的未知格枚举到一定规模,找在所有解里取值相同的格 | 剩下的多数 |
第二层是「1-2-1」「1-2-2-1」这些经典模式的通用化 —— 那些模式不是规则,是这一层的特例。
第三层是分界线。它在数学上仍然是确定性的(某格在所有解里都是雷,那它必然是雷),但计算量上去了,而且“枚举规模”是个需要调的旋钮:放得太松,生成变慢;收得太紧,又会有局面被判成“需要猜”。
我的取舍是算到第二层 + 小规模枚举。理由是前两层覆盖了绝大多数局面,枚举吃掉剩下的绝大部分,而再往上投入的算力换来的通过率提升非常小。
开局第一步
一个容易被忽略但很关键的点:第一步必须安全。
标准做法是首次点击之后再布雷,或者先把首点及其八邻域挖空。它一次解决两件事:
- 玩家不会在第一步就死 —— 这纯粹是体验问题
- 开局立刻有一片数字,也就是立刻有信息。否则第一手是纯随机的,无猜的前提从一开始就不成立
无猜到底改变了什么
它没有让游戏变简单,难度仍然由棋盘大小和雷数决定。
它改变的是随机性作用的位置:从「运气决定胜负」移到「局面形状」。玩家仍然会输,但输在“这里我没看出来”,而不是“这里谁也看不出来”。
对我来说这才是关键 —— 一个游戏值不值得反复玩,取决于失败能不能被归因到自己的判断上。

评论
…