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;