このゲームは、パズルを手作業で作ることは決してありません。たった1つの数からパズルを育て、そしてあなたの目に触れる前に、そのパズルを自分自身を相手にプレイして、簡単すぎもせず不可能でもないことを確かめるのです。
英語で執筆・編集されました。この日本語版は機械翻訳によって作成されたものです。正確さが重要な箇所については、英語の原文が正式なものです。 英語の原文を読む →
Contraptionのレベルは、どこにも保存されていません。ゴールがどこにあるか、足場がどう並んでいるかを記したファイルは存在しないのです。その代わり、レベル全体、すなわちボールの出発点、ゴールの位置、固定された壁の数と配置、あなたに手渡されるパーツの一覧は、シードと呼ばれるたった1つの開始数値から計算されます。ジェネレータに同じシードを与えれば、1バイトたがわず同じパズルが得られます。1
それが可能なのは、ジェネレータが持つ唯一の「ランダムさ」の源が、小さな決定論的疑似乱数生成器だからです。Contraptionが使っているのはmulberry32という、トミー・エッティンガー(Tommy Ettinger)が作ったコンパクトな32ビット生成器で、値を取り出すたびに状態へ加算される特徴的な定数0x6d2b79f5で見分けがつきます。2 高速で、数行に収まり、そして肝心なことに、まったくランダムではありません。それは、ただシャッフルされているように見えるだけの、固定された数学的な数列なのです。開始点を与えれば、いつまでも同じ数の流れを生み出します。ジェネレータはその流れを読み取って、すべてをどこに置くかを決めていきます。
デイリーモード(Daily)は、この決定論を頼りにして、社会的なことを実現しています。そのシードはランダムではなく、カレンダーから作られます。ゲームは協定世界時での今日の日付を取り、それを"contraption:2026-7-28"のような短い文字列に書き込み、そのテキストをハッシュ関数に通して1つの32ビットの数に煮詰めます。1
このハッシュはFNV-1aという、よく知られた非暗号学的な攪拌関数です。固定のオフセット、すなわち10進数で2166136261、16進数で0x811c9dc5から始め、1文字ごとにその文字を排他的論理和で混ぜ込んでから、FNV素数16777619を掛けます。3 その結果として残るのは、ある日のうちは安定していて、翌日にはまったく違う数です。日付の1文字の変化がシードを完全に散らしてしまうのです。カレンダーは誰にとっても同じですから、シードも同じで、したがってパズルも同じです。地球の反対側にいる2人の見知らぬ人がDailyを開けば、まったく同一のフィールドと向き合うことになります。これこそが、スコアの比較を意味あるものにしているのです。
デイリーチャレンジが公平なのは、サーバーが全員に同じレベルを送るからではなく、全員の手元のコピーが、同じ日付から同じレベルを独立に再計算するからです。
ランダムな形状を作るのは簡単ですが、良いランダムな形状を作るのは簡単ではありません。ゴールと足場をいくつか散らすだけのジェネレータは、上から落としたボールがひとりでにゴールへまっすぐ落ちていくレベルを生むことがあります。それはパズルではなく、カットシーンです。そこでContraptionは、候補となる世界を受け入れる前に、コードがpreSolvedと呼ぶ静かなテストを実行します。パーツを1つも置かない状態でレベルをシミュレートし、何も加えられていないボールが重力だけでゴールに届くかどうかを調べるのです。届いてしまうなら、そのレベルは捨てられ、ジェネレータはもう一度振り直します。1
これは、物理エンジンについての通常の考え方を巧妙にひっくり返したものです。あなたのマシンを動かすのと同じsimulate()関数が、生成の時点で内側に向けられ、レベル自身の難易度を監査しているのです。エンジンは、あなたの解答の審判であると同時に、パズルの品質検査官でもあります。
簡単すぎるレベルをはねつけるのは、仕事の半分にすぎません。もう1つの危険は不可能なレベル、つまりパーツをどう配置しても届かない場所にゴールが押し込まれたレベルです。Contraptionはこれに対して、plausiblySolvableという名の小さな総当たりソルバーで備えています。それは賢く考えることはしません。ただ試すだけです。1
具体的には、フィールド上のグリッド状の位置に沿って1つのランプを走査させ、それぞれの位置で4通りの傾きを試します。そうした試し置きの一つひとつについて完全なシミュレーションを実行し、こう問いかけます。そのランプ1つでボールはゴールまで導かれたか? どれか1つの配置が成功した瞬間に、そのレベルは解けると宣言され、あなたのもとへ届けられます。走査をすべて終えてもどれも成功しなければ、ジェネレータはそのレベルを捨てて新しいシードを試すほうを選びます。
| 走査パラメータ | 範囲 | 意味 |
|---|---|---|
| ランプX | 15 → 85、刻み14 | フィールドを横切る列 |
| ランプY | 30 → 120、刻み18 | フィールドを下る行 |
| 試す角度 | 4 | ±0.45と±0.8ラジアン |
| 配置ごとのテスト | 完全なシミュレーション1回 | ボールはゴールに届くか? |
これが何を証明しているかについては正直であるべきです。コード自身もそれについて正直で、コメントはこれを「安価な妥当性チェック … 網羅的ではない」と呼んでいます。合格したレベルには、間違いなく少なくとも1つ、ランプ1つによる粗い解があります。不合格のレベルも、ファンやギア、あるいはグリッドが試さなかったもっと巧妙なランプを使えば解けるかもしれないので、ジェネレータは不合格を不可能の証明とは見なしません。最初に見つけた自明でない世界を予備として取っておき、40回試みてもきれいなパーツ1つの解が得られなければ、永遠にループし続けるのではなく、その予備を届けます。その結果が、実用的な保証です。ただで勝てることは決してなく、ほぼ常に本物の道筋があり、「完璧な」レベルを無限に待たされることも決してありません。1
一歩引いて見れば、Contraptionのレベル生成器は、非常に古く非常に一般的な問題解決パターンである生成とテストの明快な一例です。候補をランダムに提案し、要件と照らし合わせて確認し、合格すれば残し、不合格なら捨てて提案し直す。これは、数え切れないゲームのプロシージャル生成コンテンツの背後にあり、進化計算の多くの背後にあり、そして目を細めて見れば、自然選択そのものの背後にあるループです。突然変異が提案し、環境がテストし、生き残ったものが存続するのです。4
Contraption版が優雅なのは、そのテストがプレイ可能性の安価な代用品ではなく、プレイ可能性そのものである点です。ジェネレータは、足場を数えたり距離を測ったりしてレベルが公平かどうかを見積もったりはしません。文字どおりレベルを2回プレイするのです。1回は空の状態で、ただの贈り物になっていないことを確かめるために。もう1回は探りのランプを置いて、道筋が存在することを確かめるために。あなたの解答を裁くことになる決定論的な物理は、パズルを審査したのと同じ物理です。本物のルールとずれていきかねない別個の「レベル検証器」は存在しません。ルールはただ1組しかなく、それがすべてに使われているからです。
そしてすべてが決定論的であるからこそ、この監査にはどちらの方向にも運の入り込む余地がありません。デイリーのシードが結託して、あるプレイヤーに他の人より簡単なフィールドを渡すことはできませんし、誰かに不公平なフィールドを渡すこともできません。なぜなら、レベルがチャレンジになる前に、それはすでにゲーム自身によって、自分自身を相手にプレイされ、生き延びてきたからです。