1. 数独生成算法:先想明白“要什么”再动手
1.1 数独谜题的三条硬性规则
做数独App,第一个绕不开的问题就是:谜题从哪里来?如果只是做Demo,硬编码几张盘面当然能跑,但一个真正能上架的产品,每天要消耗大量新关卡,谜题生成器就是核心引擎。开始写任何生成逻辑之前,我建议你先把数独的规则在代码层面翻译一遍,三句话就能说清:
- 行约束:每一行必须包含1~9,且不能重复。
- 列约束:每一列必须包含1~9,且不能重复。
- 宫约束:每个3x3小宫格内必须包含1~9,且不能重复。
一个完整的数独终盘,就是同时满足这三条约束的9x9矩阵。而数独谜题,本质上是从这样一个终盘里“挖掉”一部分数字,并保证剩下的局面只有唯一解。请注意“唯一解”这三个字,它是谜题生成算法和普通矩阵填充的最大区别。很多人第一次写生成器,只实现了“生成一个合法终盘”,却没有做唯一解校验,导致玩家玩到一半发现有两种填法都能通过,这是产品事故级别的体验缺陷。
关于难度分级,行业里通常用“剩余已知数个数”作为初步指标:简单级一般留38~42个数字,中等难度留30~34个,困难难度留24~28个。但这只是粗粒度分类,真正决定难度的是挖洞位置和求解路径的复杂度,后面我会展开讲。
1.2 生成算法的主流路线对比
数独生成算法从思路上大致可以分成三条路线,每条我在开发中都试过,直接说结论和使用感受。
路线一:纯随机填充 + 回溯求解。 从一个空格子开始,随机尝试填数字,遇到冲突就回溯,填满整个9x9后得到一个随机终盘,再随机挖洞。这种做法的优点是代码量最少,理解起来最直观。缺点是性能和随机性不可控——纯随机回溯最坏情况下可能跑上千次尝试才能填满一行,即使加了一些启发式规则,生成一个终盘的平均耗时也明显偏长。在OpenHarmony的低端开发板上,我实测过纯回溯方案生成一张中等难度盘面平均需要180~300毫秒,虽然单次看起来不多,但批量生成关卡时,体验就比较捉急。
路线二:预置终盘 + 转换打乱。 先内置一个已知合法的标准终盘,然后通过“交换数字符号”“交换行/列”“旋转镜像”等操作,派生出大量不同的合法终盘。然后用求解器反向挖洞。这条路线非常稳定,因为所有变换都保持了解存在的必然性——标准终盘是合法的,变换后的终盘也一定是合法的。性能很好,生成耗时可以降到10毫秒以内。缺点是需要额外维护一组“种子终盘”,而且如果你只用一个种子,打乱后的盘面在高手眼里会有“模式感”,玩家会隐约察觉到难度分布异常。
路线三:基于拉丁方阵的数学构造法。 用组合数学的方式,按规则生成一个3x3的宫格布局,再把每行错位排列,直接构造出合法终盘。这条路线我在论文里见过很多次,数学上很漂亮,生成速度极快。但工程上有两个麻烦:第一,构造算法虽然能保证终盘合法,但挖洞后是否唯一解仍需自行校验;第二,它的随机性分布虽然均匀,但要控制“谜题美感”需要额外写规则。所以实际项目中,用它的人反而不多。
综合对比下来,我最终选择了路线二的变体——内置多个种子终盘,用交换变换打乱,再配合一个高效的求解器做唯一性校验和挖洞。理由很简单:性能快、代码相对可控、后续扩展难度分级也方便。
1.3 预置终盘为什么是工程上的“最优解”
很多刚从算法教程走出来的开发者会纠结:预置终盘是不是不够“原汁原味”?我的看法是,产品要的是体验,不是数学论文。用预置终盘 + 变换,有三个非常现实的好处。
第一个好处是生成耗时可控。数独生成通常发生在关卡切换时,如果卡了300毫秒,玩家能明显感知到卡顿。而预置终盘的变换操作只是数组换位和数值映射,几乎不涉及搜索,耗时可以压到1毫秒级别。
第二个好处是方便做难度曲线。你可以针对每个种子终盘,提前标注好哪些位置的数字在挖洞后才能形成“高难度分支”,这样在生成不同难度关卡时,可以选择性地保留或挖掉这些关键位置,难度控制更精准。纯随机方案很难做到这一点。
第三个好处是可测试性。每次生成都可以基于同一个种子做确定性复现,测试时只要固定随机种子,就能稳定复现同一个Bug。这在纯随机方案里是做不到的,调试成本会高很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 预置终盘 + 变换打乱:一步步写出生成器核心逻辑
2.1 从一个合法终盘出发
先定义一个标准终盘。最简单的方式是直接硬编码一个,我工程里用的种子是这样的:
dart复制const List<List<int>> seedBoard = [
[1, 2, 3, 4, 5, 6, 7, 8, 9],
[4, 5, 6, 7, 8, 9, 1, 2, 3],
[7, 8, 9, 1, 2, 3, 4, 5, 6],
[2, 3, 4, 5, 6, 7, 8, 9, 1],
[5, 6, 7, 8, 9, 1, 2, 3, 4],
[8, 9, 1, 2, 3, 4, 5, 6, 7],
[3, 4, 5, 6, 7, 8, 9, 1, 2],
[6, 7, 8, 9, 1, 2, 3, 4, 5],
[9, 1, 2, 3, 4, 5, 6, 7, 8],
];
这个盘面不是随便写的,它满足一个非常好
