跳转至

奶牛摆放谜题生成与唯一解验证

目标

Web 端提供给玩家的局面必须满足:

  • 至少有一个合法解
  • 只有一个合法解
  • 运行时不会把未验证局面交给玩家
  • 自动求解接口只返回唯一解;无解或多解都应报错

运行时策略

前端运行时不实时生成谜题。前端只从已入库的验证池中选择局面:

  1. 按尺寸筛选 verified: trueunique: true 的关卡。
  2. 首次进入时选择该尺寸的一张关卡。
  3. 点击“重开”时,从同尺寸验证池中选择另一张关卡并清空玩家标记。

这样运行时只做常量级选择,不执行搜索,也不会出现随机生成出无解或多解局面的情况。

生成策略

生成器不使用“完全随机填色 + 大量过滤”的方式。推荐流程是受控构造:

  1. 先选一个合法解骨架 S[color] = (row, col)
  2. 每个颜色区域必须包含自己的解点 S[color]
  3. 其他格子作为干扰候选时,应优先分配给会被现有解点阻断的颜色。
  4. 如果候选局面存在多解,取第二个解作为反例,针对反例里偏离原解的格子做局部改色或域收缩。
  5. 每次修复后重新做唯一解验证。

一个非解候选格 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: trueunique: true
  • 所有关卡 count_solutions(limit=2) == 1
  • 示例图经过 parse -> count_solutions 后仍是唯一解
  • API 对多解局面返回 422,而不是返回第一个解