動物将棋 (14)
そろそろ、終わりにしようと思いつつ、いろいろと触ってしまっています。もう、これで、一区切りにしたいと思います。
公開してから、やっぱり1人対戦もしたいなということで、コンピュータと対戦できるようにしました。
コンピュータと対戦させるためには、紛いなりにも思考ルーチンを作らなければなりません。そのための下準備として、ソースプログラムを大幅に変更しています。
プログラムの変更内容
- グローバル変数BanやMochi、Tejunなどを使っていたが、これらを使わなくし、各関数はすべてテーブル型の引数として扱うようにした。
これは、思考ルーチンにおいて、盤面の思考をさせるときに、グローバル変数を使わないほうが、柔軟に切り替えて使えるからです。
思考ルーチンの説明
このようなパズルゲームの思考の考え方なんて、全く勉強したことがないので、我流で勝手に作っています。故に非常に弱いです。とりあえず、遊べるレベルということで大目にみて下さい。
もし、強力な思考ルーチンを作れる方は、是非、作ってください。動物将棋プログラムはスクリプトなので、ソースは自由に改造できます。
それでは、思考ルーチンの考え方を説明します。
指し手は下記の決まりで指します。
- 相手のライオンが取れるときは必ず取る。
- 自分のライオンが入玉でき、勝てるときは必ず入玉する。
- 王手を掛けられたとは、確率1/2の乱数で逃げるか、王手駒を取るかする。
- 王手を掛けられて逃げる場所が無い場合は、王手駒を取る。
- 王手駒を取る駒は、乱数により決定する。
- 王手駒を取るすべが無い場合は、ライオンは逃げる。
- 王手駒を取るすべもなく、逃げ道も無い場合(詰みの場合)、あきらめて適当に駒を進める。
- 王手が掛けられていない場合は、進める駒を乱数により決定する。
- ライオンは相手に取られる場所に進まない。
- 王手が掛けられていないが、ライオンが相手に取られる場所に進まざるを得なくなった場合、あきらめて、適当に駒を進める。
以上が、駒を指すルールです。見てもお分かりのように、全く、思考していません。ほぼ乱数により手を決めています。
ひたすらシミュレーションする
上記の指し手ルールを使って、コンピュータは手を指すのですが、これをコンピュータ同士で勝手に指しあいます。
例えば、ある盤面の次の一手を考えるとき、それ以降の手をコンピュータ同士で30手先まで指します。30手以内に勝負が付けば、そこで止めます。勝負が付かなければ、下記に示すルールにより勝敗をつけます。
- 30手が終了して、持ち駒も含めて、自分の駒が多い方を勝ちとする。動物将棋は、駒得がすごく有利な気がしたので、このようにしました。
そして、上記の30手先自動打ちシミュレーションを100回行います。100回行った中で勝った場合と負けた場合に、それぞれ評価点を付けていきます。評価点が付けられるのは、「第一指し手」についてです。つまり、「第一指し手(コンピュータが決めるべき次の一手)」から30手打ち合って、勝った場合は、その「第一指し手」に対して、+1/(指し手数)の評価点を加算します。負けた場合は、-1/(指し手数)を加算します。30手指した場合は、先ほどの勝敗ルールで+1/30と-1/30が加算されます。
- 勝った場合、+1/(指し手数)
- 負けた場合、-1/(指し手数)
- 30手で駒が多い場合、+1/30
- 30手で駒が少ない場合、-1/30
- 千日手でひきわけの場合も、+1/(指し手数)
100回仮想対戦を繰り返すので、同じ「第一指し手」からスタートしても、勝ったり負けたり、勝負が付く手数も違うので、評価値はいろいろと変化していきます。
また、さらに、下記の評価値も加算しています。
- 「第一指し手」が王手の場合、0.04ポイント加算
- 「第一指し手」を指すことにより、相手のライオンの動ける場所が減った場合、+(減った数)*0.03加算
- 「第一指し手」を指すことにより、相手のライオンの動ける場所が増えた場合、-(増えた数)*0.03加算
これにより、王手重視、攻め重視の評価になっています。
思考ルーチンのメインループだけですが、下記に書いておきます。
------------------------------------------ -- コンピュータの思考 -- 評価の方法 -- コンピュータ同士で今の盤面から30手先までを、全部で100回を指す。 -- 30手打つ前に勝負がつけば、以下の基準で評価点を加える。もし、 -- 勝負がつかなかったら、自分の駒が多いほうを勝ちとする。 -- 一勝負事に、最初に指し手を覚えておいて、その指し手を打った結果、 -- 勝てば評価値として1/(勝ちまでの手数)を加える。 -- 負ければ、-1/(勝ちまでの手数)を加える。 -- さらに、最初の指し手において、相手ライオンが移動できるマスが減ったときは、 -- 減った分*0.03を評価値に加える。増えれば増えた分*0.03を引く。 -- さらに、最初の指し手が王手になっていれば、評価値に0.04を加える。 -- 100回(100通り)指した結果、最初の指し手の評価値が最も大きい指し手を選択する。 ------------------------------------------ function compthink( msg, mbe, maf ) local tjun local sg = { bn={}, tjun, mchi={} } sg.bn[1] = { 0,0,0,0 } sg.bn[2] = { 0,0,0,0 } sg.bn[3] = { 0,0,0,0 } sg.mchi[1] = { 0,0,0,0,0,0 } sg.mchi[2] = { 0,0,0,0,0,0 } local x,y local koma local be={ koma, x, y } local af={ koma, x, y } local winner local cmpData={} local cmpTe={} local cnt = 0 local ca = 0 local i local imaData, uflg, hyoData local n1, n2 local addv = 0 local ohte local syobuari = 0 local isyobu msgDisp( "かんがえちゅう" ) --今の盤面から100通りの勝負をコンピュータ同士で行う for j=1,100 do --今の盤面のコピー for x=1,3 do for y=1,4 do sg.bn[x][y] = msg.bn[x][y] end end --今の持ち駒のコピー for i=1,6 do sg.mchi[1][i] = msg.mchi[1][i] sg.mchi[2][i] = msg.mchi[2][i] end sg.tjun = msg.tjun be.x = mbe.x be.y = mbe.y be.koma = mbe.koma af.x = maf.x af.y = maf.y af.koma = maf.koma ca = 0 addv = 0 syobuari = 0 for isyobu=1,30 do --30手先までコンピュータ同士でシミュレーションする -- 手を選ぶ compthinkTeChoice( sg, be, af ) if( ca==0 )then --評価用にこれから指すデータを保存する if( be.y==-1 )then imaData = (be.koma+50)*100 + af.x*10 + af.y else imaData = be.x*1000 + be.y*100 + af.x*10 + af.y end --駒を進める前の敵ライオンの動けるマス数 n1 = compthinkLionMasu( sg.bn, 3-sg.tjun ) end --評価値caのカウントアップ ca = ca + 1 if( be.y==-1 )then --持ち駒を打った komaUtsu( sg, be, af ) sg.tjun = 3 - sg.tjun --手順を入れ替える else --盤上の駒を動かした komaMove( sg, be, af ) sg.tjun = 3 - sg.tjun --手順を入れ替える end --盤上の駒と持ち駒を描画します --drawAll( sg.bn, sg.mchi ) if( ca==1 )then --駒を進めた後の敵ライオンの動けるマス数 n2, ohte = compthinkLionMasu( sg.bn, sg.tjun ) --msgDisp( (n1-n2)..":"..ohte ) addv = (n1 - n2)*0.03 --王手になっていれば評価アップさせる if( ohte==1 )then addv = addv + 0.04 end end --勝負がついたか調べます winner = chkSyobuAri( sg.bn ) --千日手をチェックします if( chkSenNichiTe( KifuN-1, sg.bn )==1 )then winner = 3 end if( winner~=0 )then syobuari = 1 break end end --勝負がついていなければ、持ち駒の多いほうを勝ちとする if( syobuari==0 )then local p1 = 0 local p2 = 0 --盤上の駒を数える for y=1,4 do for x=1,3 do if( getPlyer(sg.bn[x][y])==1 )then p1 = p1 + 1 elseif( getPlyer(sg.bn[x][y])==2 )then p2 = p2 + 1 end end end --持ち駒の数を数える for i=1,6 do if( sg.mchi[1][i]~=0 )then p1 = p1 + 1 end if( sg.mchi[2][i]~=0 )then p2 = p2 + 1 end end --評価する if( p1>p2 )then winner = 1 elseif( p1<p2 )then winner = 2 else winner = 3 end end --手数分の1とアドバンテージを足して、評価データを作ります if( winner~=msg.tjun )then hyoData = -1/ca + addv else hyoData = 1/ca + addv end --評価データに足しこみます uflg = 0 for i=1,cnt do if( cmpTe[i]==imaData )then cmpData[i] = cmpData[i] + hyoData uflg = 1 break end end if( uflg==0 )then cnt = cnt + 1 cmpTe[cnt] = imaData cmpData[cnt] = hyoData end end --最大の評価データ値を選びます local max = cmpData[1] local chocnt = 1 for i=2,cnt do if( max<cmpData[i] )then max = cmpData[i] chocnt = i end end if( cmpData[chocnt]<-0.5 )then msgDisp( "あぁ、まけた" ) elseif( cmpData[chocnt]<0 )then msgDisp( "まずいなぁ" ) elseif( cmpData[chocnt]>0.5 )then msgDisp( "よし、かてるぞ" ) else msgDisp( "なかなか、やるなぁ" ) end --msgDisp( cmpTe[chocnt].." "..cmpData[chocnt] ) --次に指す手を作ります local tst = tostring( cmpTe[chocnt] ) if( cmpTe[chocnt]<6000 )then mbe.x = tonumber(string.sub( tst, 1, 1 )) mbe.y = tonumber(string.sub( tst, 2, 2 )) maf.x = tonumber(string.sub( tst, 3, 3 )) maf.y = tonumber(string.sub( tst, 4, 4 )) mbe.koma = msg.bn[mbe.x][mbe.y] maf.koma = 0 else mbe.koma = tonumber(string.sub( tst, 1, 2 )) - 50 for j=1,6 do if( msg.mchi[msg.tjun][j]==mbe.koma )then mbe.x = j break end end mbe.y = -1 maf.koma = 0 maf.x = tonumber(string.sub( tst, 3, 3 )) maf.y = tonumber(string.sub( tst, 4, 4 )) end end