残り3手を読みきる

残り手数が3手になった白の手番で、最後まで読みきるプログラムを作成します。
「たった3手」と思われるかも知れませんが、この考え方をマスターすれば何手先でも読み切れます。
 ・白は「A,B,C」の三箇所に駒を置くことができるものとします。
 ・黒は「A,B,C」のそれぞれに対して「1,2]の二箇所に駒が置くことができます。
 ・最後の一箇所に白の駒を置いて終局です。
○残り3手 Aの手Bの手Cの手
●残り2手 1の手2の手1の手2の手1の手2の手
○最後の1手 2の手1の手2の手1の手2の手1の手
結果 ●20○44●33○31●30○34●32○32●38○26●35○29
○の評価 +24-2+40-12-6

前田稔(Maeda Minoru)の超初心者のプログラム入門

W_think() から Think3() を呼び出す

  1. 白の思考関数 W_think() から Think3() 関数を呼び出します。
    Think3 は最後の3手のときに呼び出されます。
    if (App->Countdown==3) { //最後の3手
    「最後の3手」を読み切る関数は、これまでパスが無ければ後手(○)の手番で呼び出されます。
    呼び出す前にパスのチェックをするので、パスのときに Think3() が呼び出されることはありません。
        EXPORT BYTE W_think(OseroApp *App)
        {
                    :
            my= App->Teban();               //自分(手番)の駒
            n= App->Search(my);             //手番で打てる場所を検索
            if (n==0)   return(0xFF);       //パスのとき
            if (App->Countdown==3)          //最後の3手
            {   App->CopyST();              //T[8][8]  を ST[8][8] にコピーする
                Think3(App,&pos);           //終盤3手を読み切る
                return(pos);
            }
        }
        

残り3手を読みきるプログラム

○番 Think3() 関数

  1. Think3() 関数では、全ての打てる手(最大3手)を検索して s[] に格納します。
    s[] に格納された打てる場所(1手~3手)に対して次の処理を行います。
        sn= App->Search(my,s,App->ST);          //打てる場所を検索(パスは無い)
        for(i=0; i<sn; i++)
        {   App->CopyST();                      //ST[8][8] ← T[8][8]
            App->Reverse(s[i],my,App->ST);      //my のコマを置く
            val= Think2A(App);                  //現在の局面を Think2A() で評価
              :
        

●番 Think2A() 関数

  1. 「最後の2手」を読み切る関数 Think2A() を作成します。
    Think3() から●の手番で呼ばれます。
  2. 全ての打てる手(最大2手)を検索して s[] に格納します。
    先に作成した Think2() のときは、パスは起こりませんでしたが、今回はパスも考慮しなければなりません。
  3. s[] に格納された手に対して次の処理を行います。
  4. ●の手番でパスのときはどうすれば良いのでしょう。
    手番を変えて「最後の2手」を読みきる関数 Think2B() を呼べば良いことが解かるでしょう。
        sn= App->Search(you,s,App->ST);         //打てる場所を検索
        if (sn==0)  return(Think2B(App));       //パスのとき Think2B() を呼ぶ
              :
       

○番 Think2B() 関数

  1. 「最後の2手」を読み切る関数 Think2B() を作成します。
    Think2A() から●がパスのときに○の手番で呼ばれます。
  2. 全ての打てる手(最大2手)を検索して s[] に格納します。
  3. s[] に格納された手に対して次の処理を行います。
  4. ○の手番でパスのときはどうすれば良いのでしょう。
    この関数は●がパスのときに呼ばれたのですから、○もパスなら終局です。
    ○の駒をカウントしてリターンすれば良いでしょう。

○番 Think1A() 関数

  1. 最後の駒を置いて、局面を評価する関数(Think1A)です。
  2. 残された場所に○のコマを置きます。
    ○のコマがパスのときには●のコマを置きます。
    ●もパスなら終局です。
  3. ○のコマの数を数えてリターンします。

●番 Think1B() 関数

  1. 最後の駒を置いて、局面を評価する関数(Think1B)です。
    この関数は Think2B() から呼ばれます。
  2. 残された場所に●のコマを置きます。
    ●のコマがパスのときには○のコマを置きます。
    ○もパスなら終局です。
  3. ○のコマの数を数えてリターンします。

【演習1】

  1. 良く似た関数が沢山でてきましたが、まとめて整理することができそうですね。
    特に Think1A() と Think1B() は、プレイする駒が違うだけです。
    関数を統合してシンプルなプログラムを作成して下さい。
  2. 「最後の2手」を読みきる関数 Think2 は、パスが無ければ先手(●)の手番で呼び出されます。
    「最後の3手」を読み切る関数 Think3 は、パスが無ければ後手(○)の手番で呼び出されます。
    途中でパスがあると手番が変わって呼び出されます。
    どちらの手番でも「最後の3手」まで読みきるように B_think と W_think を作成して下さい。

【演習2】

残り3手(白の手番)で、幾つかの局面を設定してみました。
プログラムを作成して、最後まで読みきれることを確かめて下さい。

☆【例1】

○残り3手 Aの手Bの手Cの手
●残り2手 1の手2の手1の手2の手終局
○最後の1手 2の手1の手2の手1の手
結果 ●20○44●33○31●30○34●32○32●31○33
○の評価 +24-2+40+2

☆【例2】

○残り3手 Aの手Bの手Cの手
●残り2手 1の手2の手1の手2の手PASS
○残り1手 2の手1の手2の手1の手
○残り2手 1の手2の手
●残り1手 1の手2の手
結果 ●34○30●31○33●30○34●31○33●30○34●31○33
○の評価 -4+2+4+2+4+2

【ヒント】

  1. Think1A() と Think1B() の関数は手番が違うだけなので、まとめるのは簡単でしょう。
    手番をパラメータで渡して、最後の石を置いてから my の駒をカウントします。
    手番の側がパスなら、手番で無い側の石を置いて my の駒をカウントします。
    両方パスならそのままカウントします。
        long  Think1(OseroApp *App,int c)
        {   BYTE    s[30];                              //盤面の打てる手を検索
            short   sn;
    
            sn= App->Search(c,s,App->ST);               //手番で打てる場所を検索
            if (sn>0)   App->Reverse(s[0],c,App->ST);   //プレイする
              :
       
  2. 「最後の2手」を読みきる関数では、一手目の解析が終わると「後2手」の局面に戻して二手目をプレイ しなければならないことに注意して下さい。
    Think3 のときは App->CopyST(); で T[8][8] を ST[8][8] にコピーすれば設定できたのですが Think2 では T[8][8] から一手進めた局面になっています。
    私は、関数が呼ばれたときの ST[8][8] の状態を保存しておいて、元の局面に戻しています。
        long  Think2(OseroApp *App,int c)
        {   BYTE    s[30];
            char    tt[8][8];       //盤面の保存
            long    val,vest;       //ベスト値
            short   sn,i;
    
            memcpy(tt,App->ST,64);                  //tt[8][8] ← ST[8][8]
              :
       

[Next Chapter ↓] 終盤を読み切る
[Previous Chapter ↑] MinMax 法による思考

超初心者の方のために全ソースコードを掲載します。 (^_^;)
全ソースコード

超初心者のプログラム入門(Win32API C++)