奶牛摆放谜题生成与唯一解验证
目标
Web 端提供给玩家的局面必须满足:
- 至少有一个合法解
- 只有一个合法解
- 运行时不会把未验证局面交给玩家
- 自动求解接口只返回唯一解;无解或多解都应报错
运行时策略
前端运行时不实时生成谜题。前端只从已入库的验证池中选择局面:
- 按尺寸筛选
verified: true且unique: true的关卡。 - 首次进入时选择该尺寸的一张关卡。
- 点击“重开”时,从同尺寸验证池中选择另一张关卡并清空玩家标记。
这样运行时只做常量级选择,不执行搜索,也不会出现随机生成出无解或多解局面的情况。
生成策略
生成器不使用“完全随机填色 + 大量过滤”的方式。推荐流程是受控构造:
- 先选一个合法解骨架
S[color] = (row, col)。 - 每个颜色区域必须包含自己的解点
S[color]。 - 其他格子作为干扰候选时,应优先分配给会被现有解点阻断的颜色。
- 如果候选局面存在多解,取第二个解作为反例,针对反例里偏离原解的格子做局部改色或域收缩。
- 每次修复后重新做唯一解验证。
一个非解候选格 cell 对颜色 i 越容易被其他解点阻断,就越适合放入颜色 i 的区域:
blockers(cell, i) =
count(j != i where cell 与 S[j] 同行、同列或 8 邻域相邻)
生成时可以使用固定 seed 控制同分候选的排序,但 seed 只用于可复现的局部扰动,不作为唯一解保证来源。
唯一解门禁
唯一解判断使用 CSP 搜索,约束与正式 solver 完全一致:
- 每个颜色区域恰好放 1 头
- 每行每列最多放 1 头
- 任意两头不能在 8 邻域相邻
count_solutions(color_ids, limit=2) 的含义:
0: 无解
1: 唯一解
2: 至少发现 2 个解,因此不是唯一解
这里的 2 不是“精确两个解”,而是为了性能提前停止。找到第二个解以后,已经足够证明该局面不能入库。
入库条件必须是:
count_solutions(color_ids, limit=2) == 1
自动求解接口
/api/solve 也使用唯一解门禁:
0个解:返回 422,无解1个解:返回标注结果和点击步骤>=2个解:返回 422,多解,拒绝给出任意一个解
这样用户上传截图时不会收到不确定的“其中一个答案”。
测试要求
测试需要覆盖:
- 所有前端内置关卡均标记
verified: true和unique: true - 所有关卡
count_solutions(limit=2) == 1 - 示例图经过
parse -> count_solutions后仍是唯一解 - API 对多解局面返回 422,而不是返回第一个解