Bonanzaの指し手生成ルーチン完全解読(2)
■ Bonanzaの指し手生成ルーチン完全解読(2)
前回の時点で、gennocapとgencapで生成する指し手の種類については解説が終わっている。ここから指し手生成のコードを読まなければならないが、指し手関係のマクロに関して知っていないとソースが読みにくいので今回は指し手関係のマクロについて解説する。
■ 指し手構造体
指し手の構造体については「Bonanzaの指し手表現は何故優れていると言えるのか(→ http://d.hatena.ne.jp/LS3600/20091111 )」
も合わせてご覧いただきたい。
指し手に関連するマクロは以下のようになる。本家Bonanzaでは指し手はunsigned intで表現されているのだが、私は、以下のようにMoveという構造体を作成して、本家Bonanzaのマクロは適宜メンバ関数に書き換えてある。bit layoutなどについては以下のソース中に詳しい説明があるのでそれを読めば意味がわかるだろう。元のソースからマクロ名は多少変更してあるが、見ればわかると思うのでそれについては特に解説はしない。
// 指し手は24bitから成る /* 移動先 xxxxxxxx xxxxxxxx x1111111 destination 移動元。駒を打つ場合は、81を加算したもの。 xxxxxxxx xx111111 1xxxxxxx starting square or drop piece+nsquare-1 成りフラグ xxxxxxxx x1xxxxxx xxxxxxxx flag for promotion 移動させる駒の種類 xxxxx111 1xxxxxxx xxxxxxxx piece to move 捕獲した駒の種類 x1111xxx xxxxxxxx xxxxxxxx captured piece ※ 捕獲する駒自体は手と無縁なので、それはhashに格納するときは削除して 19bitとして表現できる。 ※ またpieceは4bitで表現されており先後の区別はここにはない。 よって後手の駒を負の値で表現しているなら、絶対値をとってから格納する必要がある。 */ // 指し手を表わす構造体のうち成りを意味するbit mask const u32 FLAG_PROMO = (1U << 14); // 指し手クラス struct Move { Move() {} Move(u32 u) : move(u) {} // 暗黙のu32への型変換 operator u32 () const { return move; } // 移動先 BoardPos To() const { return (BoardPos)(((move) >> 0) & 0x007fU); } // 移動元(駒打ちなら、+81。81が歩,82が香,..) BoardPos From() const { return (BoardPos)(((move) >> 7) & 0x007fU); } // fromとtoだけ取り出す u32 FromTo() const { return (((move) >> 0) & 0x3fffU); } // 移動元,移動先,成り(UToFromToPromo) u32 FromToPromo() const { return ((move) & 0x7ffffU); } // 取られた駒の種類(UToCap) Piece Cap() const { return (((move) >> 19) & 0x000fU); } // この移動によって成るのか? // bool I2IsPromote(move) { return ((move) & FLAG_PROMO); } // この形はboolにすると最適化されないので嫌。 u32 IsPromote() const { return ((move) & FLAG_PROMO); } // 動かす駒の種類(先後の区別はない) Piece PieceMove() const { return (((move) >> 15) & 0x000fU); } // 以下はデバッグ用 // 駒打ちか? bool IsDrop() const { return From() >= 81; } // 打つ駒 Piece PieceDrop() const { return (Piece) (From() - 81 + 1); } // デバッグ用に表示する。 void print(); // thisからpLastまで表示する。pLastは含まない。 void print(Move* pLast); private: u32 move; }; // 移動先→指し手に変換 inline Move To2Move(BoardPos to) { return ((u32)(to) << 0); } // 移動元→指し手に変換 inline Move From2Move(BoardPos from) { return ((u32)(from) << 7); } // 駒打(移動元)→指し手に変換 inline Move Drop2Move(Piece piece) { return ((nsquare-1+(piece)) << 7); } // 駒打(駒種)→移動元に変換 inline BoardPos Drop2From(Piece piece) { return (BoardPos)(nsquare-1+(piece)); } // 動かす駒の種類→指し手に変換 // Pieceは先後の区別がないので正の値を渡す必要がある。 inline Move Piece2Move(Piece piece) { return ((piece) << 15); } // Pieceはs8だが、負の値の駒(後手の駒の値)を渡してはならない。 // 取られる駒の種類→指し手に変換 // Pieceは先後の区別がないので正の値を渡す必要がある。 inline Move Cap2Move(Piece piece) { return ((piece) << 19); } // 移動元(Move.From)→打ち駒に 変換 inline Piece From2Drop(BoardPos from) { return ((from)-nsquare+1); }
■ 基本データタイプ
あと、私は以下のtypedefを用いている。C/C++は環境によってshortやint、longなどのサイズが異なる可能性があるので、環境依存を避けるために使用している。このようなtypedefおよびs16,u8のような命名は、CPUエミュレーターのコードなどではしばしば用いられる。
// basic types typedef unsigned char u8; typedef signed char s8; typedef unsigned short u16; typedef signed short s16; typedef unsigned int u32; typedef signed int s32; typedef unsigned long long u64; typedef signed long long s64; // これ32bit。APIによってはu32と区別されることがあるので定義しておく。 typedef unsigned long ulong;