2021年 10月 1日
まだ全部完成してないけどマップ戦略アルゴリズムを作ってる。
つまり CPUの挙動。
今作はマップ生成が一番難しいかと思ったけど、やっぱりこちらの思考ルーチンの方が難しかった。
何手先とか何マス先のような複雑なものは難しくなるので、とりあえずやらない。
1、自領の行動順
まず、その CPUプレイヤーの領土の行動順の順列を作る。
各領土について、その隣接する敵国の数を調べて少ない順で登録する。
つまり行動順は「隣接する国の少ない順」になる。
#deffunc al_cal ; 国の行動順 repeat ldb ; リセット nad(cnt)=0 ; 国の行動順 nacd(cnt)=0 ; の敵国隣接数 loop c1=0 repeat ldb,1 : aldn1=cnt if lpd(aldn1)!pn1 : continue c2=0 ; 隣接する敵国の数 ldlg=aldn1*ldlb repeat ldlb : aldn2=ldl(ldlg+cnt) if aldn2=0 : break if lpd(aldn2)=pn1 : continue c2+ loop nacd(aldn1)=c2 ; 敵国の数を登録 repeat ldb : cnt2=cnt ; 詰める if nad(cnt2)=0 : nad(cnt2)=aldn1 : break if nacd(aldn1)>=nacd(nad(cnt2)) : continue repeat ldb : cnt3=cnt2+cnt aldn2=nad(cnt3) : nad(cnt3)=aldn1 : aldn1=aldn2 if aldn1=0 : break loop if aldn1=0 : break loop loop return
lpd(n) = n国のプレイヤーNo.
ldl(n) = n国の隣接する国の No.が格納されてる。
ldlg = これまた 1次元配列で ldlを扱ってるので、1つのデータの量、長さ。
各国づつ、隣接する敵国の数を調べたら、少ない順にソート(並べ替え)してる。
2、行動順に攻めるかどうか決める
*strategyルーチンは次の通り。
*strategy al_cal ; 国の行動順 ; 攻勢 stf=0 : stc=0 while stf=0 ;repeat ldb : ldn=nad(cnt) ldn1=nad(stc) : stc+ if ldn1=0 : _break if nacd(ldn1)=0 : _continue ; 攻める国が無い ; 隣接国の驚異数 ec=0 ; 隣接する敵国の数 ep=0 ; 隣接する敵国の最大戦力 ldlg=ldn1*ldlb repeat ldlb : ldn2=ldl(ldlg+cnt) if ldn2=0 : break if lpd(ldn2)=pn1 : continue eld1_kln2=ldn2 ; 1国用 ec+ if ep<ldcd(ldn2) : ep=ldcd(ldn2) ; 最大戦力 loop ; 隣接敵国が1国の場合 if ec=1 { eld1_syo ldn1,eld1_kln2,ep if mainf!5 : _break _continue } ; 2マス先が自領 btlf=2 ldpc_lda2 ldn1 ; 隣接国探査、隣接する国の少ない順 ;ldpc_dcb ldn1 ; 隣接国探査、戦力の多い順 ;ldpc_lda ldn1,0 ; 隣接国探査、隣接する国の少ない順 attack : if ldpcf=1 : _continue ; 攻勢 ; 2マス先が後背地 btlf=3 ldpc_lda ldn1,1 ; 隣接国探査、隣接する国の少ない順 attack : if ldpcf=1 : _continue ; 攻勢 ; 通常 btlf=4 ldpc_lda ldn1,0 ; 隣接国探査、隣接する国の少ない順 attack : if ldpcf=1 : _continue ; 攻勢 wend return
繰り返しには repeatではなくて while文を使ってる、これは繰り返しを抜ける条件を反映するため。
つまり stf=1になったら抜ける。
処理は順に「隣接国が 1国の場合」「2マス先が自領」「2マス先が後背地」「通常」で今の所行われる。
3、隣接国 1国の場合は攻める
隣接 0国の場合は無視(continue)するとして、まず 1国の場合に攻められないか調べる。
なぜかと言うと 1国なら相手国の戦力を凌駕すれば、元の国の防衛戦力を考えずに済む。
; 隣接国の驚異数 ec=0 ; 隣接する敵国の数 ep=0 ; 隣接する敵国の最大戦力 ldlg=ldn1*ldlb repeat ldlb : ldn2=ldl(ldlg+cnt) if ldn2=0 : break if lpd(ldn2)=pn1 : continue eld1_kln2=ldn2 ; 1国用 ec+ if ep<ldcd(ldn2) : ep=ldcd(ldn2) ; 最大戦力 loop ; 隣接敵国が1国の場合 if ec=1 { eld1_syo ldn1,eld1_kln2,ep if mainf!5 : _break _continue }
ec = 隣接する敵国の数
ep = 隣接する敵国の最大戦力
ec = 1の時、つまり隣接する敵国が 1国の場合、関数 eld1_syoに飛ぶ。
eld1_syoのソース
; 隣接国が1国の場合 #deffunc eld1_syo int _eld1_syon1,int _eld1_syon2,int _eld1_syon3 eld1n=_eld1_syon1 ; 元の国 eld2n=_eld1_syon2 ; 確認する国 ep1=_eld1_syon3 ; 確認する国のダイス戦力 if dbpl>0 and lpd(eld2n)!dbpl and pn1!dbpl : return ; 第一戦力がある if ldcd(eld1n)<=1 : return ; 戦力が1 if ldcd(eld1n)-dcbh(dcb)<ep1 and ldcd(eld1n)<dcb : return ; 戦力が無い ep2=0 ; 攻め込んだ先の隣接する敵国の最大戦力 ldlg=eld2n*ldlb repeat ldlb : eld2n2=ldl(ldlg+cnt) if eld2n2=0 : break if lpd(eld2n2)=pn1 : continue if ep2<ldcd(eld2n2) : ep2=ldcd(eld2n2) ; 最大戦力 loop if ep2<=8 : n=0 : else : n=1 ; 補正戦力 if ldcd(eld1n)-1<ep2+n and ldcd(eld1n)<dcb : return ; 2マス先に対する戦力が無い ; 攻め込む kln1=eld1n kln2=eld2n : pn2=lpd(kln2) btlf=1 gosub *battle return
冒頭の方に戦力評価をして、戦力があるかどうかを調べてる。
当然攻め込む国よりダイス数が多く無いといけない、第一戦力というのはダイスウォーズにある、全戦力が全プレイヤー戦力の半数以上を占めた場合は、弱小国が連合を組むというもの。
dcbhは補正で、最大ダイスが 9以上の場合は -1、16の場合は -2個、余計にダイスが無ければ攻め込まない、というような仕組みだけど、今の所全部 -1になってる、dcbはルール上ダイスの最大数。
8以下は 0補正なので、8個ルールの場合は関係無し、このソースの場合、普通に攻め込む国よりダイスの数が多ければ攻め込む、つまり 8個までの場合は相手より 1個、9個以上の場合は相手より 2個ダイスが余計に無ければ攻め込まない、例外としてダイス数が dcbつまり最大数の場合は同数で攻め込む。
元のダイスウォーズだと同数でも攻め込んでいるので、この辺のルールがどうなるかは調整次第。
次の「攻め込んだ国の隣接する敵国の戦力」は、攻め込んだ国に隣接する敵国の最大戦力。
その戦力より攻め込んだ後の戦力が低ければ攻め込まない。
これは攻め込んだ後に、逆に攻め込まれないため。
gosub *battleが戦闘処理、つまりダイスを転がして勝敗を決するルーチン。
今の所、戦闘画面はこんな感じ。
4、2マス先が自領
2マス先が自領というのは、こういう場合もそうだが
隣接の場合もそう
つまり優先的に自領を繋げるような挙動になってる。
これはなぜかと言うと、ダイスウォーズのルールでは飛び地はダイス増に計算されない、つまり 2国と 3国の飛び地があったとしてターンエンド時のダイスの増える量は 3個になる。
なので、なるべく自領を繋げるようにした方が強くなる。
調べる国の隣接地の各隣接領土数の少ない順を作る。
; 隣接国探査、隣接する国の少ない順 #deffunc ldpc_lda2 int _ldpc_lda2n ldpc_ldn=_ldpc_lda2n ; 探査する国 repeat ldb ldpc(cnt)=0 ; 攻める国候補、一時格納 ldpg(cnt)=0 ; の敵国隣接数 loop ldlg1=ldpc_ldn*ldlb repeat ldlb : ldn2=ldl(ldlg1+cnt) if ldn2=0 : break if lpd(ldn2)=pn1 : continue f=0 ; 隣接に自領があるか ldc=0 ; 隣接する敵領土数 ldlg2=ldn2*ldlb repeat ldlb : ldn3=ldl(ldlg2+cnt) if ldn3=0 : break if ldn3=ldpc_ldn : continue ; 元の国 if lpd(ldn3)=pn1 : f=1 : continue ; 自国がある if lpd(ldn3)!pn1 : ldc+ : continue ; 敵国がある loop if f=0 : continue ; 自国が無い n1=ldn2 : ldpgn1=ldc repeat ldb ; ソート if ldpc(cnt)=0 : ldpc(cnt)=n1 : ldpg(cnt)=ldpgn1 : break if ldpgn1>ldpg(cnt) : continue ; 少ない順 n2=ldpc(cnt) : ldpgn2=ldpgn1 ; 入れ替え ldpc(cnt)=n1 : ldpg(cnt)=ldpgn1 n1=n2 : ldpgn1=ldpgn2 loop loop return
順列は ldpc(n)になる。
順列順に攻撃するかどうか判断する。
そのルーチンは #deffunc attackになる。
#deffunc attack ; 攻勢 ldpcf=0 ; 攻勢フラグ repeat ldb ; 有利な国を攻める ldn=ldpc(cnt) : if ldn=0 : break if ldcd(ldn)=dcb : continue ; ダイス最大は無し if ldcd(ldn1)-dcbh(dcb)<=ldcd(ldn) and ldcd(ldn1)<dcb : continue ; 戦力が足りない ads_cal ldn : if ads_calf=1 : continue ; 防衛戦力が足りない kln1=ldn1 kln2=ldn : pn2=lpd(kln2) gosub *battle ldpcf=1 : break loop if ldpcf=1 : return repeat 1 ; 最大ダイス国を攻める if ldpc(0)=0 : break if ldcd(ldn1)-dcbh(dcb)<=ldcd(ldpc(0)) and ldcd(ldn1)<dcb : break ; 戦力が足りない ads_cal ldpc(0) : if ads_calf=1 : break kln1=ldn1 kln2=ldpc(0) : pn2=lpd(kln2) gosub *battle ldpcf=1 loop return
これは前半と後半に分かれている。
前半はダイス最大数より 1個以上少ない国、つまり最大数が 8個なら 7個から少ない国を攻める。
後半は ldpc順列の先頭を選んでいるが、最大数 8個である可能性の国を攻めてる。
つまり最初に最大数より少ない国を攻め、無ければ最大数の国を攻めるような流れになってる。
この判断の時に、攻め込む元の国の防衛戦力を考慮する、そのルーチンは #deffunc ads_calになる。
#deffunc ads_cal int _ads_caln ; 防衛戦力判定 ads_caln=_ads_caln ; 攻める国 ads_calf=0 ; 防衛戦力があるか if ldcd(ldn1)<=1 : ads_calf=1 : return ; 戦力が1 if dbpl>0 and lpd(ads_caln)!dbpl and pn1!dbpl : ads_calf=1 : return ; 第一戦力がある if btlf=2 : return ; 2マス先が自領の場合は計算しない ep=0 if btlf=3 or btlf=4 { ; 通常 ; 攻め元の防衛 if ep<=8 : n=0 : else : n=1 ; 補正戦力 if ldcd(ldn1)<=ep+n and ep>1 and ldcd(ldn1)<dcb : ads_calf=1 ; 防衛戦力が足りない ; 攻め先の防衛 ep2=0 ; 隣接する敵国の最大戦力 ldlg=ads_caln*ldlb repeat ldlb : ldn2=ldl(ldlg+cnt) if ldn2=0 : break if lpd(ldn2)=pn1 : continue if ep2<ldcd(ldn2) : ep2=ldcd(ldn2) ; 最大戦力 loop if ep2<=8 : n=0 : else : n=1 ; 補正戦力 if ldcd(ldn1)-1<ep2-n and ep2>1 and ldcd(ldn1)<dcb : ads_calf=1 ; 防衛戦力が足りない if btlf=3 : return } dcbb=0 ; 必要戦力、はdcbまでの数 repeat ldb,1 : ldn3=cnt if lpd(ldn3)!pn1 : continue if ldn3=ldn1 : continue ; 元の国は除外 if ldn3=ads_caln : continue ; 攻める国は除外 dcbb+=dcb-ldcd(ldn3) loop ;dcbb+=dcb-1 ; 元の国の分 ldcb_cal pn1 ; 増ダイス数の算出 if ldcb+pdsd(pn1)-dcbb<ep : ads_calf=1 ; 防衛戦力が足りない return
この中で
if btlf=2 : return ; 2マス先が自領の場合は計算しない
というのがあるが、これが 2マス先が自領の時なので、実はこの場合防衛戦力は考慮されない、つまり積極的に攻めるだけ。
btlf=3 つまり後背地の場合は、攻め元の国の隣接国が、攻め元の国のダイス数と同数以下なら防衛戦力有りとみなす、これは、その国を取られても折り返し戻っても勝てる可能性があると判断するため。
btlf=4 つまり通常の場合は、攻め元の国の隣接国の最大ダイス数まで、攻め元の国のダイスが増えて埋められるまで攻め込まない、つまりもっとも防衛を重視した判断になる。
いずれにしても判断の仕方は変更されるかもしれない。
充分な戦力(ダイス)があると判断された場合、戦闘 gosub *battleになる。
5、2マス先が後背地
後背地って何かと言われると、地政学的に言うと港背地というか、海のようなもので陸が終わってる「端の国」という事になるけど、例えば隣接国が 1国だけでその国が終端になってるような所。
こんな感じで矢印の元になってる様な国の事
地政学的に言えば、後背地を取って、その地を背景に攻めていけば、後面に戦力を置かなくて済むので有利になる。
つまり後面にも敵が居れば、普通に考えて 2正面作戦となって戦力は半分になる。
要するに、選択肢の内できるだけ後背地から取って行くようにしてるという事。
しかしどの国が後背地なのか判別するのは意外に難しいので、適当に隣接国の少ない順にしてる。
; 隣接国探査、隣接する国の少ない順 #deffunc ldpc_lda int _ldpc_ldan,int _ldpc_ldaf ldpc_ldn=_ldpc_ldan ; 探査する国 ldpc_ldf=_ldpc_ldaf ; 隣接国が3以上フラグ repeat ldb ldpc(cnt)=0 ; 攻める国候補、一時格納 ldpg(cnt)=0 ; の敵国隣接数 loop ldlg1=ldpc_ldn*ldlb repeat ldlb : ldn2=ldl(ldlg1+cnt) if ldn2=0 : break if lpd(ldn2)=pn1 : continue ldc=0 ; 隣接する敵領土数 ldlg2=ldn2*ldlb repeat ldlb : ldn3=ldl(ldlg2+cnt) if ldn3=0 : break if lpd(ldn3)=pn1 : continue ldc+ loop if ldc=0 : continue ; 隣接国無し if ldpc_ldf=1 and ldc>2 : continue ; 隣接国が3以上 n1=ldn2 : ldpgn1=ldc repeat ldb ; ソート if ldpc(cnt)=0 : ldpc(cnt)=n1 : ldpg(cnt)=ldpgn1 : break if ldpgn1>ldpg(cnt) : continue ; 少ない順 n2=ldpc(cnt) : ldpgn2=ldpgn1 ; 入れ替え ldpc(cnt)=n1 : ldpg(cnt)=ldpgn1 n1=n2 : ldpgn1=ldpgn2 loop loop return
6、通常
残りを通常としているが、めったに無い。
大体は 2マス先が自領でガンガン攻めるし、でなければ後背地を求めるので出番は少ない。
この場合はもっとも防衛戦力を重視するので、大抵は最大ダイス 例えば 8個の時にストックダイス + ターンエンド時の増ダイスが 8個以上のような条件でないと攻め込まない。
多いのは残り自領が 1国の時にジッとしてるような場合なので、これはこれで機能する。
以上、まだザッと出来上がったくらいの状態だが、そもそも元祖ダイスウォーズには、そういうアルゴリズムが無くて、適当に敵国よりダイス数が同数以上なら攻めてるだけのような感じなので、これだとあるいはゲーム的に難しくなりすぎるかもしれない。
その辺の調整も考えてはいる。