跳到正文
zenn · 游戏开发社区· 来源· boraemon2000·· 2 天前AI 评分64

【每日一次部署】制作了在1000种可能中“必定能锁定到一人”的推理游戏

【1日1デプロイ】1000通りで「必ず1人に絞れる」推理ゲームを作った

正文 · AI 翻译

証拠から森の住人を見つける、カサッ!森の足あと探偵

足あと・持ち物・方角の証拠と3人の候補を並べた実際の画面

本次制作的内容

作为“1天1部署”的新作,我制作了一款短篇推理游戏沙沙!森林足迹侦探,玩家需要阅读证据,从3名候选人中找出1人。

▶ 游玩沙沙!森林足迹侦探

线索有以下3种。

  • 足迹:圆形、细长、分叉
  • 携带物:果实、羽毛、钥匙
  • 方角:川、丘、森

将“脚印细长”“没有朝向河流方向”等2~3张证据,与3位森林居民的特征进行比对。若能在30秒内解决全部3起事件,即完成调查。

即使选错也不会游戏结束。剩余时间会减少3秒,并会收到能够区分所选对象与正确答案者的 追加证据。没有让失败止于对答案,而是将其转化为下一次判断的材料。

玩法

  1. 阅读证据卡
  2. 比较3人的脚印、随身物品、方位
  3. 点击满足所有条件的1个人
  4. 3解决事件

在 PC 上也可以用数字键 1 / 2 / 3 进行选择。结果中会显示解决数、失误数、最大连续答对、剩余时间和等级。

今日的升级1:把证据做成函数而非文章

证据不仅准备为用于显示的文本,还准备为能够判定候选是否满足条件的函数。

const PREDICATES = [
  {
    id: "fp-slender",
    text: "足あとは細長い",
    test: (suspect) => suspect.footprint === "slender",
  },
  {
    id: "not-river",
    text: "川の方へは向かっていない",
    test: (suspect) => suspect.direction !== "river",
  },
];

同一个 test() 会用于事件生成、画面上的正误判定、追加证据的选择和自动测试。

如果分开编写显示用文本和判定逻辑,就会出现这样的偏差:“文本上说的是没有去河边,代码却把去了河边的候选定为正确答案”。通过把证据做成数据与函数的组合,就可以在整个流程中共享同一处的条件。

今日的升级2:完成随机题目后进行检查

如果只是随机选两张证据,可能会出现仍剩下两名候选答案的情况,反过来也可能只看第一张就能知道正确答案。

因此,我们从符合正确答案人物的证据中生成2~3张的排列,并且只采用满足以下所有条件的组合。

function isValidClueSequence(suspects, answer, clues) {
  if (clues.length < 2 || clues.length > 3) return false;
  if (!clues.every((clue) => clue.test(answer))) return false;

  let previousCount = suspects.length;

  for (let i = 0; i < clues.length; i += 1) {
    const matches = matchingSuspects(suspects, clues.slice(0, i + 1));

    if (matches.length > previousCount) return false;
    if (i === 0 && matches.length < 2) return false;

    previousCount = matches.length;
  }

  const final = matchingSuspects(suspects, clues);
  return final.length === 1 && final[0].id === answer.id;
}

重要的是,不要止步于“因为是随机生成的,所以每次玩都会不同”,而是要在生成之后检查它是否是能玩的问题。

  • 所有证据都与正确答案人物一致
  • 仅凭第1张时仍会剩下2名以上候选人
  • 添加证据后候选不会增加
  • 最后只剩下1名答对者
  • 每个答错者都有可区分的追加证据

如果生成失败,就丢弃该事件并创建另一个组合。我们还使用了从相同 seed 会生成相同事件的伪随机数,使其也能复现。

最初的实现是“仅凭第1张就能解开的题目”

步骤1的最初实现中也有唯一解的确认。但是,在从空数组开始添加证据时,只采用了使候选变成1人的证据。

也就是说,虽然满足了“最后只剩1个人”,但尽是仅凭第一张就决定答案的问题。并没有成为需要核对多条证据的推理游戏。

测试本身也有问题。

// 修正前
ok("single clue may be ambiguous", matches.length >= 1);

如果是符合正确答案者的证据,那么留下1个以上候选人就是理所当然的。在这个测试中,无法确认“是否在第一张时筛选过度”。

因此将条件改为 matches.length >= 2,并重新改造成调查证据排列的方式。在 seed 1000〜1999 的1000个事件中,验证唯一解、第一张的模糊性、追加证据、相同 seed 的可复现性。

OK: 1000 seeds
OK: capture
OK: penalty / lock / timeout
All tests passed.

测试的成功日志汇总为4行,只有失败时才详细输出 seed 和原因。即使进行大量确认,也做到不增加人需要阅读的信息。

今日的升级3:把时间和输入作为纯粹状态来测试

失误时,将剩余时间减少3秒,并把连续答对次数重置为0。另外,为了防止快速连按同一个候选项时失误被加算两次,在选择处理的最开始就锁定输入。

这些逻辑没有只写在画面操作里,而是拆分到了game-state.js中。

function applyMissPenalty(state) {
  return {
    ...state,
    mistakes: state.mistakes + 1,
    penaltySec: state.penaltySec + 3,
    streak: 0,
  };
}

function remainingSeconds(startMs, nowMs, penaltySec) {
  const raw = 30 - (nowMs - startMs) / 1000 - penaltySec;
  return Math.max(0, Math.ceil(raw));
}

只要传入开始时刻、当前时刻和惩罚值,就能确定剩余时间,因此不必等30秒也能测试时间耗尽。输入锁也作为状态变化来验证,并固定了连打时第二次输入不被接受这一行为。

今天的设计:对齐比较的位置

舞台是放置在黄昏森林中的调查桌。使用了深绿、深棕、米白、红叶色,候选项则被画成博物画风格的森林居民。

优先便于推理,3人卡片的顺序是固定的。

  1. 足迹
  2. 随身物品
  3. 方角

如果每张卡片上信息的位置不同,那么在阅读内容之前,视线就得先寻找位置。通过把相同属性对齐到相同高度,使其可以横向比较。

否定证据不仅显示朱红色条带,还显示“否定”文字;正确与错误也通过“一致”“重新调查”的标记来传达。这是一种即使无法仅凭颜色辨别,也能了解进度的设计。

非工程师初学者用AI制作游戏的技巧

如果只是向AI请求“随机生成推理题”,就很容易混入偶然解不出来的问题或过于简单的问题。

首先,将完成后的题目应满足的 性质 写成文字。

请制作一个从3名候选人中确定1人的推理游戏。证据作为判定候选人的函数来管理。证据为2~3条,仅采用所有证据都与正确答案者一致、第1条证据后仍剩下2名以上候选人、使用全部证据后只留下1名正确答案者的问题。相同 seed 下请生成相同的问题。

此外,不仅要确认若干样本,还要给出批量检查的条件。

请在连续的1000个 seed 中确认:候选人有3名、特征不重复、第1条证据后仍剩下2人以上、最后只剩1名正确答案者、存在能区分各名错误答案者的追加证据。失败时请显示 seed 和原因。

在界面操作中也加入正常流程以外的检查。

不仅要确认正确解答,还要确认以下4条路径:从失误中借助追加证据恢复、对同一候选人高速连点、超时、从结果画面重试。

与其要求 AI“不要有 bug”,具体给出希望成立的条件以及容易出问题的操作,得到的成果物更容易确认。

公開

已 push 到 GitHub 的 main 分支,并通过“ロリポップ!デプロイナウ”公开上线。

▶ 沙沙!森林足迹侦探

与以往以反应速度和时机为主的游戏不同,这次是排列证据、排除条件、逐步逼近答案的游戏。每天都会公开一个小游戏或实用工具。

来源:zenn · 游戏开发社区 · zenn.dev