扫雷:把布雷推迟到第一次点击

第一下就踩雷的扫雷是没写完的扫雷。把布雷推迟到首次点击、并排除点击点周围 3×3,是这类实现最重要的一处设计。

经典实现与随手写的区别

最省事的做法是开局就把雷布好,然后等用户点。问题是第一次点击就有一成以上的概率踩雷(初级 10 雷 / 81 格),而玩家对此没有任何可操作的应对。

正确的做法是把布雷推迟到第一次点击:生成棋盘时只准备空盘,用户点下第一格之后才随机布雷,并且把点击点及其周围 3×3 全部排除在候选之外。这样第一下必定安全,而且点开之后立刻是一片可推演的区域。

代价是雷的分布略有偏置——排除区变小,边缘格子的实际密度会略高。这是体验与统计纯度之间的取舍,我选体验。

洪水填充

翻开一个周围雷数为 0 的格子时,要连带翻开它的邻居,邻居若也是 0 就继续向外扩散。这是标准的泛洪,实现上有两点值得注意:

一是用显式队列或栈,不要递归。高级难度下 30×16 的大片空白足以把调用栈推爆,而这类崩溃只在特定盘面出现,很难复现。

二是扩散的判据是「周围雷数为 0」,而不是「没有雷」。后者会把数字格也一起翻开,游戏就不再需要推理了。

可复现的随机

随机数用 mulberry32 这类带种子的实现,而不是 Math.random()。好处有两个:自检里可以固定种子,断言「这个种子下第 N 次布雷的结果」;调试时也能复现同一张盘面。

值得一提的是,这段 RNG 在 2048 里还有一份几乎相同的副本。原因不是忘了抽公共模块,而是「纯逻辑零 import」这条硬约束:抽出来就得写相对 import,自检会以模块找不到直接挂掉。在这里,重复优于耦合是有意的。

纯函数与难度

棋盘、翻格、插旗、结算全部是纯函数,输入输出都是普通数据结构,不碰 DOM。所以五个游戏的规则都能在自检里被 node 直接跑。三种难度用的是经典比例:9×9 十雷、16×16 四十雷、30×16 九十九雷。

← 返回文章列表

评论

…