ドルアーガの塔のマップ生成アルゴリズムはすごくかっこいいです。
ドルアーガの塔のマップ生成アルゴリズムがすごくかっこいいので、javascriptで再現してみました。
ページ下部のコントローラーで
を変更すると再現可能なフロアマップが生成されます。
本記事はドルアーガの塔のフロアマップ生成アルゴリズムについての解説となります。 マップ生成アルゴリズムは詳細な解説記事があります。
本記事はこの解説をベースに再現と解説を行っています。
ドルアーガの塔のフロアマップは、以下の前提条件を元に生成されています。
実機では、フロアマップはなんと8ビットの整数値から生成されています。 各フロアの階数をシード値とするとフロアが生成されます。60階(最終フロア)のみ255をシードとして使用しています。同じフロアサイズとシード値を与えれば、必ず同じマップが生成されます。 生成されたフロア上にキャラクターを配置するとダンジョンが完成します。
広い部屋を許さないのは、当時はハードウェアの性能的にフロアの広さが制限されていたためです。 長い通路を作り出さないと、ユーザーがあっという間にフロアを横断できてしまいます。
マップ生成アルゴリズムにはシード付き擬似乱数が必要となります。
今回はこの記事を参考にXorshiftアルゴリズムで乱数を生成しています。
ここからは、実際にどのような手順でマップが生成されているかの解説となります。
ドルアーガの塔のフロアマップは、3つの要素から構成されています。
外壁はフロア全体を囲んでいます。 床はキャラクターが自由に行き来できるマップです。 柱は等間隔に並び、ここから壁が生えています。
ドルアーガの塔のマップ生成アルゴリズムは、棒倒し法をベースにしています。 しかし、棒倒し法は単純な直線ルートが生成されやすいという問題があります。 そのため、以下のようにアレンジされています。
この処理を以下では基本手順と呼びます。
この基本手順をひたすら繰り返すことで、柱から壁が伸びてマップが生成されます。 柱からスタートし、外壁に到達すると壁の生成が終了するため、フロアを縦横に分断してしまう壁は生成されません。 他の壁に合流して手順が終了した場合も、必ず外壁に届いて壁の生成が止まることになります。
基本手順に従い壁を作り続けると
の2つの場合に閉じた空間が発生します。 この2つの問題を解決する必要があります。
基本手順にしたがって壁を伸ばし続けた場合、生成中のルートに壁がぶつかると閉じた空間ができます。 数字の6のような形を想像してください。
● ← ●
↓
● ← ●
↓ ↑
● → ●
上記のような場合、生成中のルートにぶつかって円ができています。 この問題を回避するため、壁を伸ばす先が生成中のルート上でないかを確認します。 ぶつかっている場合は最後の一手の壁生成を取り消して再抽選を行います。
生成中のルートにぶつかる問題を解決しましたが、次のような場合の問題は回避できません。 渦巻きの形を想像してください。
● ← ● ← ●
↓
● ● ← ●
↓ ↑
● → ● → ●
この場合、現在生成中のルートに四方を囲まれてしまっています。 最後の一手をいくら再抽選しても生成中のルートにぶつかることを回避できません。
このようなループが発生した場合は、生成中のルートの壁をすべて破棄して最初から再抽選を行います。 これで壁生成のアルゴリズムができました。
GitHubに置いているソースは、マップ生成アルゴリズムがどのような挙動をしているかの確認用です。 javascriptのトランスパイルを行っても、基本的にはDemoページとまったく同じものが出力されます。
トランスパイルの手順は以下の通りです。
npm install
する。npm run publish
する。Demoページのデフォルトのマップサイズはドルアーガの塔のサイズを基準にしています。 フォームで入力するフロアサイズは柱の本数です。
マップ生成アルゴリズムはフロアサイズに影響されません。 サイズを拡大しても問題なく動作します。 レイアウトが破綻するためhtmlのinput側でフロアのWidthを40までに制限していますが、それ以上のサイズでも動作します。 (dungeonGenerator.jsのgenerate関数に1以上のサイズを与えれば動きます。)
文字列で出力を行っている関係上、スマホで表示するとレイアウトが崩れます。 マップチップを用意してcanvasに出力するとビジュアルの再現もできるはずです。
以上、ありがとうございました。かっこいいです。