コンピュータを楽しもう!!

今、自分が面白くていろいろやってみたことを書き綴りたいと思います。連絡先はtarosa.yでgmail.comです。

動物将棋 (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