Twitterで3D迷路をN-BASICで作成しているのを見かけたので、ついつい熱が入って記事化してしまいました。題して迷路データの省メモリ化です。迷路データをDATA文で扱うのでは無く、バイナリコードで扱って省メモリにする内容です。
※ レトロ系の出力をHDMIに変換するならお約束。これは値段優先の品物です。
- 迷路の構造は2次元配列
迷路の表示をするのであれば、まずは迷路をどうデータ化するかを考えます。しかもなるべくならデータ量を減らしたいですね。そこで迷路の1区画を1バイトで表現する仕様で考えてみたいと思います。
3D迷路と言えば、大御所のウィザードリィやブラックオニキスが、私の中での標準なので、これに必要な要素を考えていきます。なにが必要でしょうか?
ハシゴ(上/下) 怪物出現(出現率/高中低) 閉じた扉 素通りする壁 ランダムワープ
とりあえずサンプルなのでこの程度にしておきましょうか。どういうデータ構造を考えるかが腕の見せ所です。私ならまず壁のあるなしを、最上位bitに割り当てます。これでマシン語ならマイナスフラグで、BASICであれば 128以上かどうかで一発で判断出来るためです。そして、移動可能かどうかのフラグをそれ以下のbitに設定します。
10000000b 壁が見える(移動可能) 11000000b 壁が見える(移動不可) 10100000b 扉が見える(移動可能) 11100000b 扉が見える(移動不可)
床や壁の色も付けたいですね。色情報も付けちゃいましょう。
0000_1111b 0-7 色8種類
さて、壁がない状態ではまだ 3bit 余ってますので、タイプ情報として最大8つを使えそうですね。定義してみましょう。
000 何もない 001 怪物低頻度出現 010 怪物中頻度出現 011 怪物高頻度出現 100 ワープ 101 宝箱
まだ、フラグは残ってますので拡張可能です。
- 最終仕様確認
以上の考察を仕様としてまとめてみます。
色情報
00001111b 下位4bit 0-7
壁がある/色は壁の色
10000000b 壁がある 11000000b 壁がある/移動不可 11100000b 扉がある
壁がない/色は床の色
00000000 下位4bitは床の色情報 00010000 怪物低頻度出現 00100000 怪物中頻度出現 00110000 怪物高頻度出現 01000000 ワープ
やたらとシンプルになりました。でも、これを一つ一つデータ化していくのは面倒ですよね。私ならマップエディタ作っちゃうんですが、実はもう一つ簡単な方法があります。BASIC でバイナリにデータ変換して、メモリに書き込んだらそれをバイナリセーブしてしまうという方法です。話を簡単にするために 9×9 サイズの迷宮で考えます。
小型 RCA to HDMI変換コンバーター AV to HDMI 変換器※ レトロ系の出力をHDMIに変換するならお約束。これは値段優先の品物です。
- データ化
まず、壁と状態を DATA で表します。
1000 DATA "■■■■■■■■■" 1010 DATA "■ 低 転■低上■" 1020 DATA "■ ■■■■■ ■" 1030 DATA "■中扉低■ 中高■" 1040 DATA "■ ■高■□■■■" 1050 DATA "■ ■宝■ 低■" 1060 DATA "■ ■■■低■高■" 1070 DATA "■下 低 中■宝■" 1080 DATA "■■■■■■■■■"
■が通れない壁、□は通れる壁です。ターゲット PC によって、使える記号は違うと思いますので、それは適宜対象の PC に合わせてください。さらに各エリア毎に色を設定します。これは別の DATA 文で構成します。数字でも文字でも構いません。ここでは変わりやすく言葉で表現しておきます。
2000 DATA "白白白白白黄黄黄黄" 2010 DATA "白黒黒黒黒黄緑緑黄" 2020 DATA "白黒白白黄黄黄緑黄" 2030 DATA "白黒黄黒黄緑緑緑黄" 2040 DATA "白黒白黒黄黄黄黄黄" 2050 DATA "白黒白黒白黒黒黒白" 2060 DATA "白黒白白白黒白黒白" 2070 DATA "白黒黒黒黒黒白黒白" 2080 DATA "白白白白白白白白白"
※ 01234567 = 黒青赤紫緑水黄白 と長年叩き込まれている人は、どうぞそのまま数字でデータ化してくださいw
イメージとしては、下から上がってきた冒険者がところどころ黄色の壁になっているのが不思議という状態にしておき、通れる壁の秘密が分かってそこを抜けたら、真っ黄色の壁と緑色の床だったという、ひとつ秘密を暴いた感を演出してみた感じでしょうか。※ちょっと意味不明ですが💦
これら設定したデータを元にバイナリデータ化します。データは1エリア1バイトなので、9x9 エリアであれば 81バイトしか消費しません。このデータの配置用に、メモリの後方の空きエリアに 81バイトほど確保します。BASIC だと大抵は何かしら「ここから先は BASIC は使っちゃダメ」宣言が出来ます。N-BASIC だと CLEAR 命令ですね。ここからは N-BASIC で話を進めますが、考え方はどの BASIC でも同様です。
PC-8001 32K であれば、とりあえず &HE000 からを使いたいので、CLAER で確保します。
100 CLEAR 300,&HDFFF
これで &HE000 - &HE050 を変換したデータとして扱うことが出来ます。先に示した DATA 文を PC-8001 で扱えるように文字情報を置換します。
1000 DATA "WWWWWWWWW" 1010 DATA "W o PWoUW" 1020 DATA "W WWWWW W" 1030 DATA "WO=oW O@W" 1040 DATA "W W@WLWWW" 1050 DATA "W WTW oW" 1060 DATA "W WWWoW@W" 1070 DATA "WD o OWTW" 1080 DATA "WWWWWWWWW" 2000 DATA 7,7,7,7,7,6,6,6,6 2010 DATA 7,0,0,0,0,6,4,4,6 2020 DATA 7,0,7,7,6,6,6,4,6 2030 DATA 7,0,6,0,6,4,4,4,6 2040 DATA 7,0,7,0,6,6,6,6,6 2050 DATA 7,0,7,0,7,0,0,0,7 2060 DATA 7,0,7,7,7,0,7,0,7 2070 DATA 7,0,0,0,0,0,7,0,7 2080 DATA 7,7,7,7,7,7,7,7,7
これを実機に打ち込むか j80 や PC-8001mini 等のエミュに打ち込みます。j80 だと Keyboard → InputFile があるし、Boost で処理の高速化が出来るのでオススメです。
さて、これをデータ変換していきます。まずは DATA 分のままだと扱いづらいので、一部を普通の配列に置き換えてしまいます。
10 CLEAR300,&HDFFF 20 DIMMP$(9*9) 30 PT=0:FORY=1TO9:READA$ 40 FORX=1TO9:MP$(PT)=MID$(A$,X,1):PT=PT+1:NEXT:NEXT
プログラムリストですが、BASIC の場合は極力スペースを詰めてください。このスペースが無駄な1バイトになります。さらに処理速度も落ちるのです。ただ、たまにどうしてもスペースが必要な部分もありますのでご注意ください💦
これで、データ変換する準備が出来ました。この MP$ と CL の情報をマージして1バイトのデータに変換してメモリに落とし込んでいきます。
100 RESTORE2000 110 FORPT=0TO80:READ D:C$=MP$(PT) 120 IFC$="W"THEND=D+12*16 130 IFC$="="THEND=D+14*16 140 IFC$="L"THEND=D+ 8*16 150 IFC$="P"THEND=D+ 4*16 160 IFC$="@"THEND=D+ 3*16 170 IFC$="O"THEND=D+ 2*16 180 IFC$="o"THEND=D+ 1*16 190 POKE&HE000+PT,D:NEXT
これを RUN<ret> で実行すると単に OK で終わります。この時、メモリ &HE000 から &HE050 には変換後のデータが格納されています。
このバイナリデータはマシン語モニタから保存します。MON <ret> WE000,E050 <ret> としてください。
地下20階とかの深い階層を作っても、このデータ形式であれば 1,620バイト(1.58KB)で済んでしまいます。めちゃくちゃ省メモリですね!
地下20階とかの深い階層を作っても、このデータ形式であれば 1,620バイト(1.58KB)で済んでしまいます。めちゃくちゃ省メモリですね!
- BASIC からデータを参照する
参照したい座標が X,Y の時、次のようにメモリから PEEK すればデータを取り出せます。
※ データが &HE000 から格納済みの前提です。
※ データが &HE000 から格納済みの前提です。
DT=PEEK(&HE000+X+Y*9)
このDTの値の使い方です。まず色情報を取り出したい時は以下のようにします。
CL=DT AND15
これで CL には 0-7 の色コードが入ります。この AND は論理積演算子です。15 すなわち 00001111B と論理積を取ることで、下位 4bit を取りだしています。上位4bitの取り出しも同じく論理和でも良いのですが、その後で BASIC で処理しやすいように、数値を加工してしまいます。
MD=DT\16
16で割る事で右シフト4回と同じ結果になります。ただ、普通に割ると小数演算(小数点以下が付いてしまう)になってしまうので、¥として整数計算しています。この MD はその値によって、迷路の状態が分かります。
MD = 0 : 通路 = 1 : 通路/怪物発生率低 = 2 : 通路/怪物発生率中 = 3 : 通路/怪物発生率高 = 4 : 通路/ワープ = 8 : 壁/通り抜け可能 = 12 : 壁 = 14 : 扉
マシン語でも処理しやすいフラグ体系にしていますが、BASIC だけで処理するのであれば、この MD の数値が連番になるように調整も可能です。その際は上記の行番号 120 - 180 の数値を調整してください。
どうでしょうか。これで大幅にデータの削減プログラムはが出来たかと思います。あ、当然ですが、バイナリデータが出来てしまえば、BASIC側のデータをバイナリに落とすプログラムは不要です。CLOAD"hoge"<ret> とした後で、MON<ret> L<ret> として今回作成したバイナリデータをロードすると使えます。マシン語使っていないのに、マシン語のロードをしているみたいで格好いいですよねw
※ 出力された映像の取り込みに。デバッグに良し映像配信に良し!
コメント
コメント一覧 (6)
おっしゃっている自動生成は、小さなマップをルールベースと乱数で組み合わせるということですね。確かにそれなら、適切な小マップを作れば、ギミックを生かした大マップが作られそうです。
自分がイメージしていた自動生成は、ドルアーガの塔みたいな感じでした。
内藤時浩
がしました
四方の組み合わせ16種類というのがわかりません。例えば、右辺と下辺だけ定義してデータを減らすというのは不可能でしょうか。その場合、上辺と左辺は隣のエリアが定義するみたいな。
扉の向かい合わせというのもわかりません。エリアの上下または左右の辺が扉ということでしょうか。広間のようなものも定義できない?
また、破壊できる壁、鍵付や一方通行の扉、回転する床等、出現頻度の少ないギミックの情報も、全エリアで持つのはもったいない気がして、うまい圧縮方法はないかなと。
あと、もし書いていただけるのであれば、迷路の自動生成方法の注意点など教授いただきたいです。自分は一方通行ではないゴールできる迷路を生成するのが精いっぱいで、ギミックや分岐などの解き応えのある迷路が作れません。
内藤時浩
がしました
タイプ情報が壁、扉、柱の場合に厚さを薄く描写すれば、そのままでも行けそうですが、もっと小さく格納できるデータ構造がある気がして。
内藤時浩
がしました