2021年 8月 17日
このゲーム製作ではマップ生成が難しい所だと思う。
とにかくやってみよう。
1、6方向の座標を求める関数を作る
まず現在の HEXから 6方向の座標を帰す関数(命令)を作る。
hm2 基のHEX座標 , 調べる方向
/////// HEX /////////////////////////////////////////////////////////// ht1=0,-1,0, 1, 0, 0,0 ht2=0, 0,0, 0,-1, 0,1 #deffunc hm2 int _hm2n1,int _hm2n2 ; 6方向移動先 横向き ; MAP座標(1=現在HEX、2=移動先方向) hm2n1=_hm2n1 hm2n2=_hm2n2 hm2p=-1 ; 返す値 f=(hm2n1/hxb)\2 ; 奇数列か偶数列か n=hm2n1\(hxb*2) ; 位置種類判定用 switch hm2n2 case 1 ; 右上はあるか ------- if hm2n1/hxb>0 and n!hxb-1 { if f=1 : hm2n1-=hxb+ht1(1) ; 奇数列 if f=0 : hm2n1-=hxb+ht2(1) ; 偶数列 hm2p=hm2n1 ; 右上のマップ座標 } swbreak case 2 ; 右はあるか ------- if n!(hxb-1) and n!(hxb-1)*2 : hm2p=hm2n1+1 ; 右のマップ座標 swbreak case 3 ; 右下はあるか ------- if hm2n1<hyb*hxb-hxb and n!hxb-1 { if f=1 : hm2n1+=hxb+ht1(3) ; 奇数列 if f=0 : hm2n1+=hxb+ht2(3) ; 偶数列 hm2p=hm2n1 ; 右下のマップ座標 } swbreak case 4 ; 左下はあるか ------- if hm2n1<hyb*hxb-hxb and n!0 { if f=1 : hm2n1+=hxb+ht1(4) ; 奇数列 if f=0 : hm2n1+=hxb+ht2(4) ; 偶数列 hm2p=hm2n1 ; 左下のマップ座標 } swbreak case 5 ; 左はあるか ------- if n!0 and n!hxb : hm2p=hm2n1-1 ; 左のマップ座標 swbreak case 6 ; 左上はあるか ------- if hm2n1/hxb>0 and n!0 { if f=1 : hm2n1-=hxb+ht1(6) ; 奇数列 if f=0 : hm2n1-=hxb+ht2(6) ; 偶数列 hm2p=hm2n1 ; 左上のマップ座標 } swbreak swend return
ht1、ht2 = 上下に移動した時の、x座標の変化、ht1=奇数列の時、ht2=偶数列の時
hm2n1 = 基の HEXNo.
hm2n2 = 調べる方向(1=右上、2=右、3=右下、4=左下、5=左、6=左上)
hm2p = 調べた方向の HEXNo.を返す、-1は無し、0はマップの座標で使ってしまっているので -1を使ってる。
f = 奇数列か偶数列か(0=偶数列、1=奇数列)
n = 位置種類判定用、主に、奇数列の右端左端か?、偶数列の右端左端か?
x座標(左右)は -1 +1ずつで良い。
本ゲームでは MAP座標(HEX座標)を 1次元で扱うため、上下にずれる時は横の長さ(hxb)分 + -する。
2、作りたい国の数だけ MAP上にランダムに点を打つ
まず国の中心点を決める。
この時に国同士が一定の距離を持つようにすれば、だま(固まり)になる事が無い。
#deffunc map_build hxb=26 ; 横のマスの数 hyb=41 ; 縦のマスの数 mb=hxb*hyb dim map,mb+1 ; マップデータ repeat mb : map(cnt)=-1 : loop ; 国の中心点 ---------- ldb=40 ; 国の数 ldob=100 ; 国土の最大 dim ld,(ldb+1)*ldob+1 ; 領土 ldn=1 ; 国のNo. repeat await 0 r=rnd(mb)+1 if r\(hxb*2)=hxb*2-1 : continue ; 右の空白 if map(r)>0 : continue ; 既にある mf=0 repeat 6,1 ; 周辺に国が無いか hm2 r,cnt if hm2p=-1 : mf=1 : break if map(hm2p)>0 : mf=1 : break hm2p2=hm2p repeat 6,1 ; 2マス目 hm2 hm2p2,cnt if hm2p=-1 : mf=1 : break if map(hm2p)>0 : mf=1 : break loop if mf=1 : break loop if mf=1 : continue ; 周辺に国がある map(r)=ldn : ld(ldn*ldob)=r if ldn=ldb : break ldn+ loop
r=rnd(mb)+1 でランダムに点を決めます。
その周辺 2マスに既に点が打ってないか調べます。
周辺 2マスに点が無ければ、その場所に点を打ちます。
これを国の数(この場合は 40個)だけ行います。
各国の国土は ldで持ちます。
この場合、マップの大きさに対して国の数が多いと、点が打てなくて無限ループします、await 0はその時用に書いてあります。
もし、この段階で、例えば中心の 20 × 20マスの範囲には点を打てないようにすれば、マップとしてはドーナツ型、リング状のマップを作成したりできると思います。
3、各国の中心点を基に国土を拡大する
打った点を基に周辺に拡大して行きます。
拡大するかどうかは確率でランダムに決めます。
; 各領土 ---------- ldpb=100 dim ldp,(ldb+1)*ldpb+1 ; 拡張領土No.一時格納 dim ldpc,ldb+1 ; 登録カウント dim ldpg,ldb+1 ; 現在検索中カウント repeat (ldb+1)*ldpb+1 : ldp(cnt)=-1 : loop ; リセット repeat ldb,1 ; リセット ldp(cnt*ldpb)=ld(cnt*ldob) ; 基点 ldpc(cnt)=0 ; 登録カウント ldpg(cnt)=0 ; 現在検索中カウント loop ldpec=1 ; 一度に拡張する数 repeat 6 : rc=cnt ; 6周 repeat ldpec repeat ldb,1 : ldn=cnt ; 国No, mn=ldp(ldn*ldpb+ldpg(ldn)) ; 検索するHEX if mn=-1 : break ldpg(ldn)+ repeat repeat 6,1 r=rnd(100)+1 : if r>60-rc*10 : continue ; 領土伸ばすか hm2 mn,cnt if hm2p=-1 : continue ; 領土あるか if map(hm2p)>0 : continue ; 既に領土がある map(hm2p)=ldn ; 国土拡張 ldpc(ldn)+ ld(ldn*ldob+ldpc(ldn))=hm2p ; 国土登録 ldp(ldn*ldpb+ldpc(ldn))=hm2p ; 検索国No.格納 loop if ldpc(ldn)>1 : break ; 国土が3つ以上ある loop loop ldpec+=2 loop loop
まず最初に国の中心点の回り 6マスに国土を拡張するかどうか乱数で決めます。
r=rnd(100)+1 : if r>60-rc*10 : continue ; 領土伸ばすか
なので 100%で最初は 60%の確率で国土を伸ばすか決めます。
この時、1マスも国土を拡張しない確率もあり、その場合はその国は 1マスという事になってしまいますけど、1マス60% × 6マスなのでまず無いでしょう。
伸ばした国土を ldに格納し、次に調べる HEXとして ldpに格納します。
2週目は ldpに格納した HEXNo.を基に、拡張するかどうか決めます、2週目は 50%になります。
調べるマスの数は ldpecですので、この場合 1週目は 1マス、2週目は 3マス、3週目は 5マスですね。
こんな風に 3週目は 40%、4週目は 30%と段々確率が低くなり、先細りでも有る程度まとまった国土をランダムに作る事ができます。
なぜ何週もしてるのかと言うと、各周回毎に各国全てを一通り拡大してます。
そうする事により、1国だけが極端に大きくなったり、後からの国が広げる領土がなかったりを防いで、均等な大きさになるうようにしてます。
1つの国に穴が空いていたりするので、元の DiceWarsのマップ作成アルゴリズムとは、また違うのでしょうけど、まあ、穴は湖か山にでもすれば、おもむきがあるんじゃないかと考えてます。
4、隣接国判定
各国が、どの国と隣接しているかの判定をします。
ldb = 国の数
ldl = 各国の隣接国を登録
ldlb = 登録数の上限
; 隣国判定 ldlb=20 ; 隣接国登録、最大20国 dim ldl,(ldb+1)*ldlb+1 ; 隣接国 repeat ldb,1 : ldn=cnt ; 国 ldlc=ldn*ldlb ; 隣接国登録No. repeat ldob ; 国土 mn=ld(ldn*ldob+cnt) ; 調べるマップNo, if mn=0 : break ; 終わり repeat 6,1 hm2 mn,cnt : if hm2p=-1 : continue mn2=map(hm2p) if mn2<0 : continue ; 地形 if mn2=ldn : continue ; 自国 f=0 repeat ldlb ; 既に登録済みか if mn2=ldl(ldn*ldlb+cnt) : f=1 : break loop if f=1 : continue ; 登録済み ldl(ldlc)=mn2 : ldlc+ ; 隣接国登録 loop loop loop
各国の領土のデータは ldで持ってます。
それを 1マスづつ 6方向探査して、その国と違う国があれば ldlに登録します。
5、全国の接続判定
全部の国が一つに繋がっているか判定します。
ここまでだと島国ができてしまいます。
これだと攻められない国ができてしまいます。
島は地形的には味があるので、船で攻めるような設定で隣接国を設定してもみようかと考えましたけど、まずどの国が島か判定し、かつその島の大陸に一番近い国と、大陸側の一番近くの国がどれかを判定し、かつどう繋がってるかを表示するという事を考えると、結構ややこしくてめんどくさいので、とりあえず全部の国が繋がってる事にしました。
; 全国接続判定 dim ldl1,ldb+1 ; 1群 repeat ldb : ldl1(cnt)=0 : loop ; リセット c=0 : ldl1(c)=1 ; 1国から repeat ldb ; 国 ldn=ldl1(c) : if ldn=0 : break repeat ldlb ; の隣接登録 n=ldl(ldn*ldlb+cnt) : if n=0 : break repeat ldb if ldl1(cnt)=n : break ; 登録済み if ldl1(cnt)=0 : ldl1(cnt)=n : break ; 1群に登録 loop loop c+ loop return
1国から始めます。
判定には先ほど作った隣接国のデータ ldlを使います。
ldl1に 1国に隣接する国を登録し、それを元に順番に接続国を登録していきます。
最終的にマップ生成は次のように呼び出します。
*map_create ; マップ生成 repeat map_build f=0 repeat ldb if ldl1(cnt)=0 : f=1 : break loop if f=0 : break ; 全部繋がってる loop return
ldb = 国の数 40国なので、要するに接続国が 40国あるか判定してます。
接続国が 40国に満たない場合は、もう一度マップを作り直しています。