思考関数の作成方法

オセロバトルゲームの思考関数の作成について解説します。

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

  1. 「オセロバトルゲーム」の思考関数は「じゃんけんバトルゲーム」と違い一段と難しくなります。
    プログラムを工夫して強い関数を作成して下さい。
  2. オセロゲームでは四隅を取ると有利と言われています。
    ゲームの手数が少ない内は、隅を取られないように「2の2」に駒を置かないようにします。
  3. ゲームの経験から、四隅の次に辺を取ると有利なようです。
  4. 思考関数の最初の段階として、打つ場所にウエイトを付ける方法が考えられます。
    例えば「隅は150点、2の2は-64点」などと設定して打つ場所を決めます。
    ウエイトが適正に設定できれば「初心者並みのプレイ」が期待できます。
  5. 次の段階として、一手進めてから(相手の駒を裏返してから)評価します。
    局面を評価する方法は色々考えられますが、一例を挙げれば次のようなものです。

DLL の作成

  1. DownLoad したファイルを解凍すると DLL を作成するプロジェクト一式が格納されています。
  2. B_proc.dsp(W_proc.dsp) をダブルクリックして Visual C++ を起動します。
  3. [ビルド(B)][B_proc のビルド(U)] を実行すると DLL が作成されます。
    Release Mode でコンパイルすると DLL のファイルサイズが小さくなります。
  4. 次のような警告が出ることがありますが無視して下さい。
        LINK : warning LNK4098: defaultlib "LIBC" は他のライブラリの使用と競合しています;
               /NODEFAULTLIB:library を使用してください
        LINK : warning LNK4089: "GDI32.dll" へのすべての参照は /OPT:REF によって廃棄されます
        LINK : warning LNK4089: "USER32.dll" へのすべての参照は /OPT:REF によって廃棄されます
        
  5. Debug または Release のフォルダーに DLL のプログラムファイルが作成されます。
    このファイルを OseroBattle.exe と同じフォルダーにコピーして起動して下さい。

思考関数の例-1

  1. 乱数で出す手を決める簡単な B_proc.cpp の例です。
    先頭の部分は DLL を作成するための記述で、実際のプログラムは「// 初期化」から始まります。
        // B_proc.cpp : DLL アプリケーション用のエントリ ポイントを定義します。
        //
    
        #include "stdafx.h"
        #include "B_proc.h"
        #pragma     comment(lib,"OseroApp.lib")
    
        BOOL APIENTRY DllMain( HANDLE hModule, 
                               DWORD  ul_reason_for_call, 
                               LPVOID lpReserved
                             )
        {
            return TRUE;
        }
    
        // 初期化
        EXPORT char *B_init()
        {   time_t  nowtime;
            time(&nowtime);
            srand(nowtime&0xFF);
            return ("Black Random");
        }
    
        // Play の手を考える
        // 上4 Bit:Y座標、下4 Bit:X座標
        EXPORT BYTE B_think(OseroApp *App)
        {   short   n;
            n= App->Search(App->Teban());   //手番で打てる場所を検索
            if (n==0)   return(0xFF);       //パスのとき
            return(App->S[rand()%n]);       //乱数で決める
        }
        
  2. 何時も同じ手を打たないように B_init() 関数で乱数を初期化します。
        EXPORT char *B_init()
        {   time_t  nowtime;
            time(&nowtime);
            srand(nowtime&0xFF);
            return ("Black Random");
        }
        
  3. App->Teban() 関数で、手番(黒:1,白:-1)を取得します。
    App->Search(手番) 関数で、全ての手を検索して S[] に格納します。
    S[] は Object Class の public: 領域です。
    n= App->Search(App->Teban());
  4. n がゼロのときは、打つ手が無い(パスまたは終局)のときです。
    打つ手が無いときは 0xFF をリターンして下さい。
    if (n==0) return(0xFF); //パスのとき
  5. App->S[rand()%n] で全ての打てる手から乱数で打つ手を決めます。
    リターン値は8ビット(BYTE)で、上4ビットにY座標を、下4ビットにX座標を設定して下さい。
    return(App->S[rand()%n]);

思考関数の例-2

  1. コマを置く場所にウエイトを付けて判断する W_proc.cpp の例です。
    static 宣言はモジュールの中でのみ参照できるようにする指定ですが、無くても支障はありません。
        // W_proc.cpp : DLL アプリケーション用のエントリ ポイントを定義します。
        //
    
        #include "stdafx.h"
        #include "W_proc.h"
        #pragma     comment(lib,"OseroApp.lib")
    
        BOOL APIENTRY DllMain( HANDLE hModule, 
                               DWORD  ul_reason_for_call, 
                               LPVOID lpReserved
                             )
        {
            return TRUE;
        }
    
        static  short  VT[8][8]=    //OSERO 評価テーブル
        { { 150, 10, 10, 10, 10, 10,  10, 150 },
          { 10, -64,  0,  0,  0,  0, -64, 10  },
          { 10,   0,  0,  0,  0,  0,   0, 10  },
          { 10,   0,  0,  0,  0,  0,   0, 10  },
          { 10,   0,  0,  0,  0,  0,   0, 10  },
          { 10,   0,  0,  0,  0,  0,   0, 10  },
          { 10, -64,  0,  0,  0,  0, -64, 10  },
          { 150, 10, 10, 10, 10, 10,  10, 150 } };
    
        static  short testv(BYTE v1, BYTE v2)
        {   short   x1,y1,x2,y2;
    
            x1= v1&0xF;
            y1= v1>>4;
            x2= v2&0xF;
            y2= v2>>4;
            return(VT[y1][x1]-VT[y2][x2]);
        }
    
        // 初期化
        EXPORT char *W_init()
        {   time_t  nowtime;
            time(&nowtime);
            srand(nowtime);
            return ("White 打つ場所でウエイト付");
        }
    
        // Play の手を考える
        // 上4 Bit:Y座標、下4 Bit:X座標
        EXPORT BYTE W_think(OseroApp *App)
        {   short   i,n;
            BYTE    mx;
    
            n= App->Search(App->Teban());   //手番で打てる場所を検索
            if (n==0)   return(0xFF);       //パスのとき
            mx= App->S[0];
            for(i=1; i<n; i++)
            {   if (testv(mx,App->S[i])<0)              mx= App->S[i];
                if (testv(mx,App->S[i])==0 && rand()&1) mx= App->S[i];
            }
            return(mx);
        }
        
  2. VT[8][8] がウエイトのテーブルです。
    隅のウエイトが高く、2-2のウエイトが低く設定されています。
  3. BYTE で渡された「Y座標とX座標」の添え字を計算するソースコードです。
    上4 Bit がY座標で、下4 Bit がX座標です。
        x1= v1&0xF;
        y1= v1>>4;
        
  4. BYTE mx; に testv() 関数を使って、最もウエイトの高い手を求めます。
    ウエイトが同じときは乱数で選択します。
    if (testv(mx,App->S[i])==0 && rand()&1) mx= App->S[i];

[Next Chapter ↓] Osero Object Class のメンバー関数
[Previous Chapter ↑] ゲームの Down Load とプログラムの実行

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