マインスイーパー:地雷の配置を最初のクリックまで遅らせる

最初のクリックで負けうるマインスイーパーは未完成です。配置を遅らせ、クリック位置の周囲 3×3 を除外することが、ここで最も重要な設計判断です。

本物の実装と即席の実装の差

手を抜く方法は、最初に地雷を配置してクリックを待つことです。初級(81 マスに 10 個)では最初のクリックが 1 割以上の確率で負けになります。しかもプレイヤーに打てる手がありません。

正しい方法は配置を最初のクリックまで遅らせることです。空の盤面だけを用意し、プレイヤーが最初のマスを押した後にランダム配置を行い、そのマスと周囲 3×3 を候補から除外します。これで最初のクリックは必ず安全になり、開いた先はすぐ推論できる領域になります。

代償は分布のわずかな偏りです。除外領域のぶん盤面が狭まり、端のマスの実効密度が上がります。体感と統計的な純粋さの交換で、私は体感を取ります。

フラッドフィル

周囲の地雷数が 0 のマスを開いたときは隣も開き、隣も 0 ならさらに広がります。実装で重要な点は二つです。

第一に、再帰ではなく明示的なスタックかキューを使います。上級の 30×16 では広い空白が呼び出しスタックを壊すのに十分で、しかもその崩壊は特定の盤面でしか起きないため再現が困難です。

第二に、拡散の条件は「周囲の地雷数が 0」であって「地雷が無い」ではありません。後者では数字のマスまで開いてしまい、推理の必要がなくなります。

再現可能な乱数

乱数は Math.random() ではなく、mulberry32 のような種を持つ実装を使います。利点は二つあります。自検で種を固定し、N 個目の配置をそのまま断言できます。デバッグでも同じ盤面を再現できます。

なお、この RNG とほぼ同じ複製が 2048 にも存在します。共通化を怠ったのではなく、「純粋ロジックは import ゼロ」という制約の結果です。共通モジュールにすれば相対 import が必要になり、自検はモジュール未検出で落ちます。ここでは重複が結合に勝ちます。

純粋関数と難易度

盤面生成、開放、旗立て、決着はすべて、DOM に触れない素のデータ構造上の純粋関数です。だから 5 つのゲームのルールを自検で node から直接実行できます。難易度は古典的な比率で、9×9 に 10 個、16×16 に 40 個、30×16 に 99 個です。

← 記事一覧に戻る

コメント

…