アレフガルド as Code(in Minecraft)

# はじめに
　この記事は、[NSSOL Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nssol)の2日目の記事です。
　今年の3月くらいから、「アレフガルド in マインクラフト」というタイトルで、Minecraft Education EditionにバンドルされているCodeBuilderを使って、アレフガルドをJavascriptのコードで生成するという試みを続けてきました。紆余曲折あり、時に回り道もしながらも、なんとかアレフガルドの全体像をマインクラフトに生成するところまでたどり着きました。今回のアドベントカレンダーでは、その足跡を2つの記事にまとめようと思います。一つ目は、取り組み自体の紹介（カレンダー2日目）、二つ目は取り組みを通じて得られた知見のまとめ（カレンダー3日目）です。

 # アレフガルドをマインクラフトで再現するには
　マインクラフトは、レゴのようにさまざまな種類のブロックを組み合わせて、3Dの世界に色々なものを表現することができます。アレフガルドをマインクラフトの世界で表現しようとする場合、オリジナルのゲームのマップ構造（2D）に、高さ方向の情報を加える必要があります。
　ドラゴンクエストの世界を3Dで表現した作品としては、ドラゴンクエストビルダーズがリリースされています。本家のドラゴンクエストから分岐・派生した世界で、本来地続きであったアレフガルドがいくつかのエリアに分割され、地形も一部変わってしまっていますが、高さの情報を含むアレフガルドが表現されている公式作品という意味で、マインクラフト上にアレフガルドを再現するための貴重な情報元となります。
　最初のアプローチとしては、ビルダーズのアレフガルドを測量し、その3Dデータをマインクラフトに再現することを試みます。ビルダーズのマップは、アレフガルドが複数に分割されてしまっていますが、間を埋めつつ繋ぎ合わせることで、本来のアレフガルドを再現することができるのではないか、という算段です。ビルダーズは、メルキド地方からスタートするので、メルキドからドムドーラといったアレフガルド南西地方をこの方法で再現しました。再現方法に関する具体的な説明は本投稿では省略します。ご興味のある方は[アレフガルド in マインクラフト Part1 マインクラフトでの地形生成](https://qiita.com/yamamorisoba/items/a8736bfe8acb802dd2ae)をご覧ください。

 　実際に、マインクラフト上に、ビルダーズのメルキド周辺やドムドーラ周辺の地形を再現したものが、以下の図となります。
![スクリーンショット 2024-07-31 194322.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/038951ec-889d-5475-0fcd-6942732fdc19.png)
![スクリーンショット 2024-06-18 220803.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/5033ae36-0556-adbe-963a-34b450507021.png)

　このアプローチは、個々のエリアを再現する分にはある程度有効だったのですが、複数のエリアをつなぎ合わせる段階で、ある問題が顕在化しました。実は、ドラゴンクエストビルダーズは、メルキド周辺のエリアと、それ以外のエリアとで、本家のアレフガルドのマップに対する縮尺が異なっており、エリア同士とつなぎ合わせようとすると、スケールが合わないのです。ビルダーズを実測して用意したアレフガルド南西地方の地形と、岩山の洞窟周辺の地形をつなぎ合わせてみたものが以下の図です。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/e3ab7cbe-b998-edd9-813d-95e5ec04dab8.png)
　白抜き部分が岩山の洞窟周辺エリアなのですが、そのままつなぎ合わせると大きくはみ出してしまうことがわかります。縮尺の違いを計算してみると、岩山の洞窟周辺のサイズを100とした場合、アレフガルド南西地方は東西方向・南北方向とも75程度に地形が間引きされていることがわかりました。スケールが合うようにエリア同士がつながるようにするには、75%に間引かれた地形を100%に拡大する必要がありますが、これはなかなか手間がかかりそうなので、別のアプローチでアレフガルドの再現を試みることにします。

# FC版ドラゴンクエストのマップからの再現
　前節で利用したドラゴンクエストビルダーズの地形は、FC版ドラゴンクエストのワールドマップ1マスを、16ブロック×16ブロックの区画で表現しているようでした。そこで、FC版ドラゴンクエストのマップを縦横にそれぞれ16倍して平坦な地形データを生成し、細かな海岸線などの境界構造や、各地の高さに関する情報をドラゴンクエストビルダーズから補完することで、アレフガルド全体を再現するというアプローチを試みます。

## FC版ドラゴンクエスト マップデータの作成
　ドラゴンクエスト関連の攻略サイトを巡れば、ワールドマップの情報はあるのですが、今回は一次情報（FC版ドラゴンクエスト[^1]）から改めてマップデータを作成しました。

 [^1]:Wiiでリリースされたファミコン・スーパーファミコン ドラゴンクエストⅠ・Ⅱ・Ⅲを使用しています。
 
![FC版アレフガルド.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/1b9c27b3-d889-d18b-f9ae-02cf80eb08ae.jpeg)

　ドラゴンクエスト3がHD-2Dでリメイクされる時代に、FC版のアレフガルドをくまなくマッピングすることになるとは思いませんでしたが、久しぶりに隅々まで散策しました。以下のスクリーンショットが、アレフガルドのどのあたりか、お分かりになりますか。
 
![スクリーンショット 2024-11-09 230220.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/70af49b9-937c-2eb1-5505-e64d36d862ef.png)
この段だけ「おおさそり」や「メイジドラキー」が出る初期のレベリングの穴場

![スクリーンショット 2024-11-09 230349.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/ca4a9cad-c58c-30ee-e888-9fb6efa59a16.png)
右端の３マスだけ「よろいのきし」など各段に強いモンスターが出る危険地帯

![スクリーンショット 2024-11-09 230458.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/dcc68a70-80d2-3e11-421b-49d7aa2790bc.png)
攻略上、来る意味は特にないので、マッピングのために何十年ぶりかにやってきた場所

## 16倍の地形データの作成
　FC版のマップ1マスを16ブロック×16ブロックに拡大した地形データを作成します。元のマップは128×128の広さとなっており、手作業で16倍データを作るのは大変なので、Edge Copilotの支援を受けつつ16倍データを生成するPowerShellスクリプトを作成しました。
　Copilotとのやり取りはこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/1cf39be2-ed3d-6ac6-a55d-247c4898a795.png)
``` Powershell
# Excelアプリケーションを起動
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true

# 新しいワークブックを追加
$workbook = $excel.Workbooks.Add()
$worksheet = $workbook.Worksheets.Item(1)

# コピー元のセルを指定（例：A1セル）
$sourceCell = $worksheet.Cells.Item(1, 1)
$sourceCell.Value2 = "コピー元のデータ"

# コピー先の範囲を指定（例：A1セルを16x16セルに拡大コピー）
for ($row = 1; $row -le 16; $row++) {
    for ($col = 1; $col -le 16; $col++) {
        $worksheet.Cells.Item($row, $col).Value2 = $sourceCell.Value2
    }
}

# 保存してExcelを閉じる（必要に応じて）
#$workbook.SaveAs("C:\path\to\your\file.xlsx")
#$excel.Quit()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/47040a3f-2123-3873-907d-39e6c1b70fef.png)
``` PowerShell
param (
    [string]$sourceRangeAddress = "A1:C5",  # コピー元の範囲
    [int]$expandFactor = 16                # 拡大する倍率
)

# Excelアプリケーションを起動
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true

# 新しいワークブックを追加
$workbook = $excel.Workbooks.Add()
$worksheet = $workbook.Worksheets.Item(1)

# コピー元の範囲を指定
$sourceRange = $worksheet.Range($sourceRangeAddress)

# コピー元の範囲の行数と列数を取得
$sourceRows = $sourceRange.Rows.Count
$sourceCols = $sourceRange.Columns.Count

# コピー先の範囲を指定（例：A1から指定範囲を拡大コピー）
for ($row = 1; $row -le $sourceRows * $expandFactor; $row++) {
    for ($col = 1; $col -le $sourceCols * $expandFactor; $col++) {
        $sourceRow = [math]::Ceiling($row / $expandFactor)
        $sourceCol = [math]::Ceiling($col / $expandFactor)
        $worksheet.Cells.Item($row, $col).Value2 = $sourceRange.Cells.Item($sourceRow, $sourceCol).Value2
    }
}

# 保存してExcelを閉じる（必要に応じて）
#$workbook.SaveAs("C:\path\to\your\file.xlsx")
#$excel.Quit()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/bb0f71b7-5e56-207e-8f35-788a2d1be5de.png)
```Powershell
param (
    [string]$sourceRangeAddress = "A1:C5",  # コピー元の範囲
    [int]$expandFactor = 16                # 拡大する倍率
)

# Excelアプリケーションを起動
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true

# 新しいワークブックを追加
$workbook = $excel.Workbooks.Add()
$worksheet = $workbook.Worksheets.Item(1)

# コピー元の範囲を指定
$sourceRange = $worksheet.Range($sourceRangeAddress)

# コピー元の範囲の行数と列数を取得
$sourceRows = $sourceRange.Rows.Count
$sourceCols = $sourceRange.Columns.Count

# コピー先の範囲を指定（例：A1から指定範囲を拡大コピー）
for ($row = 1; $row -le $sourceRows * $expandFactor; $row++) {
    for ($col = 1; $col -le $sourceCols * $expandFactor; $col++) {
        $sourceRow = [math]::Ceiling($row / $expandFactor)
        $sourceCol = [math]::Ceiling($col / $expandFactor)
        $worksheet.Cells.Item($row, $col).Value2 = $sourceRange.Cells.Item($sourceRow, $sourceCol).Value2
    }
}

# 保存してExcelを閉じる（必要に応じて）
#$workbook.SaveAs("C:\path\to\your\file.xlsx")
#$excel.Quit()
```


とまあ、こんな感じで、Copilotと対話しつつ、FC版のマップを16倍に拡大するスクリプトをお手軽に用意することができたわけです。このスクリプトを使って生成した16倍のマップは以下のようになりました。2048×2048の広さとなっているため、Excelの表示倍率を10%にしても、ほんの一部分（下図の場合、ガライの町周辺）しか画面には収まらなくなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/b113b0a5-3693-690a-a20b-4da74c18fdc6.png)

# マインクラフト上での地形生成
　16倍のマップデータが出来上がったので、いよいよマインクラフト上にこの地形を生成します。セルの色に応じて設置するブロックの種類を変え、平坦な地形を生成します。ただし、岩山部分は歩いて通行できないように、少しだけ高くなるようにしました。地形の生成は、CodeBuilderで自動実行できるよう、JavaScriptで実装します。今回の地形生成に用いたコードの一部を掲載します。コードの各行は、特定範囲を指定したブロックで埋める`/fill`コマンドの実行に相当しています。
```Javascript
blocks.fill(DIAMOND_BLOCK,world(336, -40, -941),world(351, -34, -941),FillOperation.Replace)
blocks.fill(GRASS,world(352, -40, -941),world(367, -34, -941),FillOperation.Replace)
blocks.fill(WATER,world(368, -40, -941),world(511, -34, -941),FillOperation.Replace)
blocks.fill(STONE,world(512, -40, -941),world(623, -31, -941),FillOperation.Replace)
blocks.fill(COARSE_DIRT,world(624, -40, -941),world(703, -33, -941),FillOperation.Replace)
blocks.fill(GRASS,world(704, -40, -941),world(815, -34, -941),FillOperation.Replace)
blocks.fill(WATER,world(816, -40, -941),world(1023, -34, -941),FillOperation.Replace)
```
 
　実際に生成された地形を、上空から俯瞰したスクリーンショットが以下の図です。高さ1000位のところから見下ろしているので小さく見えますが、地形を構成している正方形の1マスは16ブロック×16ブロック（マインクラフトの1チャンク相当）です。生成された地形の範囲が広すぎて、描画範囲を最大設定の96チャンクとしても、外周部分（マイラの村やガライの町）は視界に収まりきらなくなってしまいました。
 ![Minecraft Education 2024_11_11 21_41_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/204aedfe-8a93-d7ca-e59e-8c864500f19b.png)

# ビルダーズの情報を付加した地形データの精緻化
　FC版のマップデータには高さの情報がないので、ビルダーズの地形から、海岸線などの詳細構造や高さの情報を反映させます。アレフガルド全土にわたって反映させるのは時間的に無理だったので、今回は竜王の城がある「魔の島」の一部を対象としています。対比のために、FC版を16倍に拡大したマップと、ビルダーズの地形情報を反映させたマップを並べてみます。
 ![compare.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/e60c3e57-b55b-64fe-733c-fc35d4e57513.jpeg)
 　ビルダーズ版は、竜王の城を囲っている毒の沼地がオリジナルよりも広がっていますが、海岸線の構造はおおむねFC版を踏襲しているように見えます。

　この情報から、地形生成のJavaScriptのコードを生成し、最初に生成した平らな地形を更新します。実際に地形が作り変えられる様子を10倍速の動画にまとめました。手作りすると相当な労力がかかりそうな地形ですが、コード化することで、短時間で生成することができています。また、うっかり一部を破壊してしまっても、コードから再生成できるので、生成した地形に色々手を加えることも比較的気軽に実施できます。
 <iframe width="560" height="315" src="https://www.youtube.com/embed/xDZk8P1PK6k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

　続いて、毒の沼地の中央に竜王の城を建てます。今回は城の外観のみを用意することにします。これもJavaSciprtでコードを用意し、CodeBuilderで実行することで、一括生成することができます。今回は以下のコードを用いて簡易な城を建造しました。

 ```Javascript
player.onChat("run", function () {
    blocks.fill(BLACKSTONE,world(-189,-32,-208),world(-173,-29,-192),FillOperation.Replace)
    blocks.fill(BLACKSTONE,world(-146,-32,-208),world(-130,-29,-192),FillOperation.Replace)
    blocks.fill(BLACKSTONE,world(-189,-32,-165),world(-173,-29,-149),FillOperation.Replace)
    blocks.fill(BLACKSTONE,world(-146,-32,-165),world(-130,-29,-149),FillOperation.Replace)
    blocks.fill(BLACKSTONE,world(-187,-32,-206),world(-132,-29,-151),FillOperation.Replace)

    blocks.fill(NETHER_BRICK,world(-188,-28,-207),world(-174,-19,-193),FillOperation.Hollow)
    blocks.fill(NETHER_BRICK,world(-145,-28,-207),world(-131,-19,-193),FillOperation.Hollow)
    blocks.fill(NETHER_BRICK,world(-188,-28,-164),world(-174,-19,-150),FillOperation.Hollow)
    blocks.fill(NETHER_BRICK,world(-145,-28,-164),world(-131,-19,-150),FillOperation.Hollow)

    blocks.fill(NETHER_BRICK,world(-189,-21,-208),world(-173,-17,-192),FillOperation.Replace)
    blocks.fill(AIR,world(-188,-17,-207),world(-175,-17,-194),FillOperation.Replace)
    blocks.fill(NETHER_BRICK,world(-146,-21,-208),world(-130,-17,-192),FillOperation.Replace)
    blocks.fill(AIR,world(-145,-17,-207),world(-132,-17,-194),FillOperation.Replace)
    blocks.fill(NETHER_BRICK,world(-189,-21,-165),world(-173,-17,-149),FillOperation.Replace)
    blocks.fill(AIR,world(-188,-17,-164),world(-175,-17,-151),FillOperation.Replace)
    blocks.fill(NETHER_BRICK,world(-146,-21,-165),world(-130,-17,-149),FillOperation.Replace)
    blocks.fill(AIR,world(-145,-17,-164),world(-132,-17,-151),FillOperation.Replace)

    blocks.fill(NETHER_BRICK,world(-187,-28,-206),world(-132,-19,-151),FillOperation.Replace)
    blocks.fill(NETHER_BRICK,world(-188,-21,-207),world(-131,-19,-150),FillOperation.Replace)
    blocks.fill(AIR,world(-172,-20,-205),world(-147,-19,-151),FillOperation.Replace)
    blocks.fill(AIR,world(-186,-20,-191),world(-132,-19,-166),FillOperation.Replace)

    blocks.fill(NETHER_BRICK,world(-170,-20,-189),world(-149,-12,-168),FillOperation.Replace)
    blocks.fill(NETHER_BRICK,world(-171,-12,-190),world(-148,-11,-167),FillOperation.Keep)
    blocks.fill(AIR,world(-170,-12,-189),world(-149,-11,-168),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-169,-12,-188),world(-150,-12,-169),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-168,-11,-187),world(-151,-11,-170),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-167,-10,-186),world(-152,-10,-171),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-166,-9,-185),world(-153,-9,-172),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-165,-8,-184),world(-154,-8,-173),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-164,-7,-183),world(-155,-7,-174),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-163,-6,-182),world(-156,-6,-175),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-162,-5,-181),world(-157,-5,-176),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-161,-4,-180),world(-158,-4,-177),FillOperation.Replace)
    blocks.fill(RED_NETHER_BRICK,world(-160,-3,-179),world(-159,-3,-178),FillOperation.Replace)
})
 ```
　このコードを実行して城が建てられる様子を撮影した動画が以下となります。こちらは等倍速の動画ですが、CodeBuilderでコマンド群を一括実行することで、あっという間に構造物が造られる様子を見て取れると思います。

<iframe width="560" height="315" src="https://www.youtube.com/embed/hYkvtbdC5Gc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

　コードを追加・手直しすることで、もう少し細かい部分を作りこむこともできますが、そのあたりの完成度を上げていくのは、他の地方の地形生成が進んでから着手しようと思います。

　ここまでの作業で生成された魔の島を、ラダトーム城の方から眺めた景色が以下の画像です。晴天では爽やかな感じになってしまうので、天候を雨、時間を夜（night）に設定し、暗くよどんだ雰囲気にしてみました。
 ![ラダトームから見た竜王の城.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297527/ef156ea5-037f-0804-83f1-f6448e0c2e71.png)

　先日発売されたドラゴンクエスト3 HD-2D版は、フィールドマップの地形にも起伏が表現されているので、アレフガルドまで進めることができれば、魔の島の地形・起伏も確認できそうです。今回の投稿ではビルダーズの地形を参考にしましたが、ナンバリングタイトルで、立体的なアレフガルドがどの様に表現されているかは気になるところです[^2]。ただ、ドラゴンクエスト3でも、フィールドの始点は北が上に固定されているので、ラダトーム側から見た風景を確認するのは難しそうです[^4]。

[^2]:この原稿を執筆している時点で、ダーマ神殿のあたりをウロウロしている状況なので、アレフガルドにたどり着くのは当面先になりそうです…

[^4]:Steam版には視点変更可能なMODもあるらしいですが、私自身はSwitch版をプレイしているので…

# おわりに
　ここまでお読みいただき、ありがとうございました。この取り組みを通じて、マインクラフト上でアレフガルドを再現するという夢を実現するための試行錯誤の過程を共有できたことを嬉しく思います。ドラゴンクエストビルダーズやFC版ドラゴンクエストのデータを活用し、地形の生成や精緻化を行うことで、少しずつアレフガルドの全貌が見えてきました。

この取り組みはまだ道半ばですが、今後も引き続き改良を重ね、よりリアルで詳細なアレフガルドを再現していきたいと考えています。皆様からのフィードバックやアドバイスも大変励みになりますので、ぜひコメントやご意見をお寄せください。

最後に、この取り組みを通じて得られた知識や経験を、今後の活動に活かしていきたいと思います。これからも一緒に、Minecraftの世界で新たな冒険を続けていきましょう。

注：なお、この「おわりに」のセクションは、それ以前の原稿をEdge Copilotに入力することで、Edge Copilotによって生成されました[^3]。

このページで利用している株式会社スクウェア・エニックスを代表とする共同著作者が権利を所有する画像の転載・配布は禁止いたします。
© 1986 ARMOR PROJECT/BIRD STUDIO/SPIKE CHUNSOFT/SQUARE ENIX All Rights Reserved.

 
[^3]:記事に設定したタグもEdge Copilotに候補を挙げてもらっています。