跳到正文
zenn · 游戏开发社区· 来源· ランウィズ·· 2 天前AI 评分62

在发布前让计算机解完100个下落式益智关卡的故事

落ち物パズルの100面を、出す前に全部コンピューターに解かせた話

正文 · 原文

はじめまして。愛知県の小さな会社、ランウィズの佐藤と申します。
先日、木のブロックの落ち物パズルを Android とブラウザーで出しました。この記事では、そのステージ100面と問題50問を「人の手で作らず、出す前に全部コンピューターに解かせて確かめた」やり方を書きます。

なお、この記事のコードは AI のコーディングの道具(Claude Code)が書き、私は何を作るかと、出してよいかを決めました。記事の下書きも AI が書き、私が読んで直しています。

ゲームの決まり(短く)

  • 盤は横18×縦30。最初からほぼ埋まっていて、底まで細い割れ目が1本通っている
  • 落ちてくる駒を置いて、そろった列が消えると、上のブロックが一番下まで落ちる
  • 落ちた結果また列がそろえば、続けて消える(連鎖)

この「どこまで落ちるか」が盤ごとに違うので、良い盤と悪い盤の差がとても大きくなります。

面は「種」の数から作る

1面ごとに盤を手で描く代わりに、面の番号 n と試しの番号 k から種の数を作り、その種で盤を作ります。

function candidateSeed(n, k) {
  return (Math.imul(n * 7919 + k * 104729 + 1, 2654435761) ^ 0x5eed1e55) >>> 0 || 1;
}

同じ種なら、いつ遊んでも同じ盤から始まります。

コンピューターの遊び手

遊び手は欲ばりな作りです。今の駒の置ける所を全部試し、「連鎖が多い → 消える列が多い → 低い所」の順で一番良い所に置きます。

function bestPlacement(b, piece) {
  let best = null;
  for (const p of placements(b, piece)) {
    const { res } = tryPlacement(b, p);
    const v = res.chain * 1000 + res.rows * 10 + p.y;
    if (!best || v > best.v) best = Object.assign({ v, chain: res.chain, rows: res.rows, points: res.points }, p);
  }
  return best;
}

先読みはしません。「人より弱い遊び手でもクリアできる盤」なら、人がクリアできないことはまずない、という考えです。

面として残す条件

面 n ごとに k = 0, 1, 2 … と種を試し、次の2つを満たした最初の種を表に書きます。

  1. 最初の駒(まっすぐの棒)で 4 連鎖以上を起こせる
  2. 時計なしで、遊び手が決まった数の駒でクリアできる(面が進むほど、使える駒の数を増やす)

1つ目は「始めた瞬間に気持ちよく崩れる」ため、2つ目は「詰まない」ためです。見つかった種の表はゲームの本体のファイルに書きこみ、テストでもう一度全部の面を解き直しています。

問題50問は「全部の置き方」を数える

連鎖パズル(駒が1〜3個だけ)は、置き方が少ないので全部試せます。全部の置き方のうち、目標(何連鎖、または何列)に届く割合を数え、1問目は届く置き方が多く、50問目はほんのわずかしか届かないように目標を決め、易しい順に並べました。答えの1つを表に残し、テストでその答えを実際に打って解けることを確かめています。

やってみて分かったこと

  • 盤を作る決まりを変えるたびに、100面を作り直してすぐに全部確かめられるのが一番の得でした
  • 「最初の1手で4連鎖以上」の条件を入れる前は、始めてすぐ何も起きない面が混ざっていました
  • 遊び手が欲ばりなので、「人ならもっとうまく解ける」面が多めです

作ったゲーム:Timber Drop(無料)

最後まで読んでいただき、ありがとうございました。

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