2.2 各関数の実装 |
本節では、前節説明した関数の実装を行います。
といっても、メインとなる思考の部分はまだ実装しません。
思考のアルゴリズムを説明してから実装を行います。
それぞれの関数は"com.c"に記述されています。
まずComクラスの構造について見てみましょう。
struct _Com { Board *Board; int MidDepth; int WLDDepth; int ExactDepth; int Node; };
Boardは、後述する探索に使用する盤面です。
MidDepthは中盤での先読み手数、WLDDepthは必勝読みでの先読み手数、ExactDepthは完全読みでの先読み手数です。
Nodeは探索したノードの数を保存しておくための変数です。
それではComクラスの生成処理を見てみましょう。
static int Com_Initialize(Com *self) { memset(self, 0, sizeof(Com)); self->Board = Board_New(); if (!self->Board) { return 0; } self->MidDepth = 1; self->WLDDepth = 1; self->ExactDepth = 1; self->Node = 0; return 1; } Com *Com_New(void) { Com *self; self = malloc(sizeof(Com)); if (self) { if (!Com_Initialize(self)) { Com_Delete(self); self = NULL; } } return self; }
malloc()で領域を確保し、メンバを初期化しているだけです。
初期化に失敗したら後述するCom_Delete()を呼び出すようになっています。
次にComクラスの破棄関数です。
void Com_Delete(Com *self) { if (self->Board) { Board_Delete(self->Board); } free(self); }
Boardを破棄して、Com_New()で確保した領域を解放しているだけです。
強さを設定する関数Com_SetLevel()の実装は以下のようになっています。
メンバ変数に引数を代入するだけです。
void Com_SetLevel(Com *self, int in_mid, int in_exact, int in_wld) { self->MidDepth = in_mid; self->WLDDepth = in_wld; self->ExactDepth = in_exact; }
思考中に探索したノード数を返す関数Com_CountNodes()は以下のようになっています。
メンバ変数のNodeに格納されている値を返しているだけです。
Nodeの値は後述するCom_NextMove()の実行中に格納されます。
int Com_CountNodes(const Com *self) { return self->Node; }
Comクラスの関数のうち、最も複雑な処理をするのがCom_NextMove()です。
本節では途中まで説明します。
int Com_NextMove(Com *self, const Board *in_board, int in_color, int *out_value) { int result; int left; int value; int color; Board_Copy(in_board, self->Board); self->Node = 0; left = Board_CountDisks(self->Board, EMPTY); if (left <= self->ExactDepth) { value = Com_EndSearch(self, left, in_color, Board_OpponentColor(in_color), 0, &result); } else if (left <= self->WLDDepth) { value = Com_EndSearch(self, left, in_color, Board_OpponentColor(in_color), 0, &result); } else { if ((in_color == WHITE && self->MidDepth % 2 == 0) || (in_color == BLACK && self->MidDepth % 2 == 1)) { Board_Reverse(self->Board); color = Board_OpponentColor(in_color); } else { color = in_color; } value = Com_MidSearch(self, self->MidDepth, color, Board_OpponentColor(color), 0, &result); } if (out_value) { *out_value = value; } return result; }
まず引数として渡されたBoardを、Comクラスで保持しているBoardにコピーします。
次に渡された局面の空きマスの個数によって処理を分けています。
完全読みを行える空きマス個数であれば完全読み、必勝読みを行える空きマス個数であれば必勝読みを行います。
Com_EndSearch()という関数が、完全読みまたは必勝読みを行う関数です。
上の例では完全読みと必勝読みで同じ処理になっていますが、節が進むと別の処理を行うようになります。
完全読みも必勝読みも行えない場合にはCom_MidSearch()を呼び出します。
Com_MidSearch()を呼び出す前に局面を反転させることがありますが、これは常に黒番の局面で評価を行うためです。
局面評価は「どちらの手番か」という情報が重要になることがあるため、評価を行う手番を固定にしておくと評価値の精度が高くなることが期待できます。
Com_EndSearch()またはCom_MidSearch()を呼び出したら、out_valueに評価値を格納して関数を終了します。
Com_EndSearch()とCom_MidSearch()の内部については後の節で触れます。