/// <summary> /// staticなテーブルの初期化 /// 起動時にInitializerから一度だけ呼び出される。 /// 普段は呼び出してはならない。 /// </summary> public static void Init() { ALL_BB = new Bitboard(0x1FFFFFF); ZERO_BB = new Bitboard(0U); Bitboard FILE1_BB = new Bitboard(0x1fU << (5 * 0)); Bitboard FILE2_BB = new Bitboard(0x1fU << (5 * 1)); Bitboard FILE3_BB = new Bitboard(0x1fU << (5 * 2)); Bitboard FILE4_BB = new Bitboard(0x1fU << (5 * 3)); Bitboard FILE5_BB = new Bitboard(0x1fU << (5 * 4)); FILE_BB = new Bitboard[(int)File.NB] { FILE1_BB, FILE2_BB, FILE3_BB, FILE4_BB, FILE5_BB }; Bitboard RANK1_BB = new Bitboard(0x108421 << 0); Bitboard RANK2_BB = new Bitboard(0x108421 << 1); Bitboard RANK3_BB = new Bitboard(0x108421 << 2); Bitboard RANK4_BB = new Bitboard(0x108421 << 3); Bitboard RANK5_BB = new Bitboard(0x108421 << 4); RANK_BB = new Bitboard[(int)Rank.NB] { RANK1_BB, RANK2_BB, RANK3_BB, RANK4_BB, RANK5_BB }; EnemyFieldBB = new Bitboard[(int)Color.NB] { RANK1_BB, RANK5_BB }; SQUARE_BB = new Bitboard[(int)Square.NB + 1]; ForwardRanksBB = new Bitboard[(int)Color.NB, (int)Rank.NB] { { ZERO_BB, RANK1_BB, RANK1_BB | RANK2_BB, ~(RANK4_BB | RANK5_BB), ~RANK5_BB }, { ~RANK1_BB, ~(RANK1_BB | RANK2_BB), RANK4_BB | RANK5_BB, RANK5_BB, ZERO_BB } }; // 2つの升のfileの差、rankの差のうち大きいほうの距離を返す。sq1,sq2のどちらかが盤外ならINT_MAXが返る。 int dist(Square sq1, Square sq2) { return((!sq1.IsOk() || !sq2.IsOk()) ? int.MaxValue : System.Math.Max(System.Math.Abs(sq1.ToFile() - sq2.ToFile()), System.Math.Abs(sq1.ToRank() - sq2.ToRank()))); } BetweenBB_ = new Bitboard[89]; BetweenIndex = new UInt16[(int)Square.NB + 1, (int)Square.NB + 1]; // 1) SquareWithWallテーブルの初期化。 /* * for (Square sq = Square.ZERO; sq < Square.NB; ++sq) * SquareWithWallExtensions.SquareToSqww[sq.ToInt()] = (SquareWithWall) * ((int)SquareWithWall.SQWW_11 + sq.ToFile().ToInt() * (int)SquareWithWall.SQWW_L + sq.ToRank().ToInt() * (int)SquareWithWall.SQWW_D); */ // 2) direct_tableの初期化 Util.direc_table = new Directions[(int)Square.NB + 1, (int)Square.NB + 1]; for (var sq1 = Square.ZERO; sq1 < Square.NB; ++sq1) { for (var dir = Direct.ZERO; dir < Direct.NB; ++dir) { // dirの方角に壁にぶつかる(盤外)まで延長していく。このとき、sq1から見てsq2のDirectionsは (1 << dir)である。 var delta = (int)dir.ToDeltaWW(); for (var sq2 = sq1.ToSqww() + delta; sq2.IsOk(); sq2 += delta) { Util.direc_table[(int)sq1, (int)sq2.ToSquare()] = dir.ToDirections(); } } } // 3) Square型のsqの指す升が1であるBitboardがSquareBB。これをまず初期化する。 // SQUARE_BBは上記のRANK_BBとFILE_BBを用いて初期化すると楽。 for (Square sq = Square.ZERO; sq < Square.NB; ++sq) { File f = sq.ToFile(); Rank r = sq.ToRank(); // 筋と段が交差するところがSQUARE_BB SQUARE_BB[sq.ToInt()] = FILE_BB[f.ToInt()] & RANK_BB[r.ToInt()]; } // 4) 遠方利きのテーブルの初期化 // thanks to Apery (Takuya Hiraoka) // 引数のindexをbits桁の2進数としてみなす。すなわちindex(0から2^bits-1)。 // 与えられたmask(1の数がbitsだけある)に対して、1のbitのいくつかを(indexの値に従って)0にする。 Bitboard indexToOccupied(int index, int bits, Bitboard mask) { var result = ZERO_BB; for (int i = 0; i < bits; ++i) { Square sq = mask.Pop(); if ((index & (1 << i)) != 0) { result ^= new Bitboard(sq); } } return(result); } // Rook or Bishop の利きの範囲を調べて bitboard で返す。 // occupied 障害物があるマスが 1 の bitboard // n = 0 右上から左下 , n = 1 左上から右下 Bitboard effectCalc(Square square, Bitboard occupied, int n) { Bitboard result = ZERO_BB; // 角の利きのrayと飛車の利きのray SquareWithWall[] deltaArray; if (n == 0) { deltaArray = new SquareWithWall[2] { SquareWithWall.SQWW_RU, SquareWithWall.SQWW_LD } } ; else { deltaArray = new SquareWithWall[2] { SquareWithWall.SQWW_RD, SquareWithWall.SQWW_LU } }; foreach (var delta in deltaArray) { // 壁に当たるまでsqを利き方向に伸ばしていく for (var sq = (SquareWithWall)(square.ToSqww().ToInt() + delta.ToInt()); sq.IsOk(); sq += delta.ToInt()) { result ^= sq.ToSquare(); // まだ障害物に当っていないのでここまでは利きが到達している if ((occupied & sq.ToSquare()).IsNotZero()) // sqの地点に障害物があればこのrayは終了。 { break; } } } return(result); } // pieceをsqにおいたときに利きを得るのに関係する升を返す Bitboard calcBishopEffectMask(Square sq, int n) { Bitboard result; result = ZERO_BB; // 外周は角の利きには関係ないのでそこは除外する。 for (Rank r = Rank.RANK_2; r <= Rank.RANK_4; ++r) { for (File f = File.FILE_2; f <= File.FILE_4; ++f) { var dr = sq.ToRank() - r; var df = sq.ToFile() - f; // dr == dfとdr != dfとをnが0,1とで切り替える。 if (System.Math.Abs(dr) == System.Math.Abs(df) && ((((int)dr == (int)df) ? 1 : 0) ^ n) != 0) { result ^= Util.MakeSquare(f, r); } } } // sqの地点は関係ないのでクリアしておく。 result &= ~new Bitboard(sq); return(result); } // 角の利きテーブルの初期化 for (int n = 0; n < 2; ++n) { int index = 0; for (var sq = Square.ZERO; sq < Square.NB; ++sq) { // sqの升に対してテーブルのどこを見るかのindex BishopEffectIndex[n, sq.ToInt()] = index; // sqの地点にpieceがあるときにその利きを得るのに関係する升を取得する var mask = calcBishopEffectMask(sq, n); BishopEffectMask[n, sq.ToInt()] = mask; // p[0]とp[1]が被覆していると正しく計算できないのでNG。 // Bitboardのレイアウト的に、正しく計算できるかのテスト。 // 縦型Bitboardであるならp[0]のbit63を余らせるようにしておく必要がある。 //ASSERT_LV3(!(mask.cross_over())); // sqの升用に何bit情報を拾ってくるのか int bits = mask.PopCount(); // 参照するoccupied bitboardのbit数と、そのbitの取りうる状態分だけ.. int num = 1 << bits; for (int i = 0; i < num; ++i) { Bitboard occupied = indexToOccupied(i, bits, mask); // 初期化するテーブル BishopEffectBB[n, index + (int)OccupiedToIndex(occupied & mask, mask)] = effectCalc(sq, occupied, n); } index += num; } // 盤外(SQ_NB)に駒を配置したときに利きがZERO_BBとなるときのための処理 BishopEffectIndex[n, (int)Square.NB] = index; // 何番まで使ったか出力してみる。(確保する配列をこのサイズに収めたいので) // cout << index << endl; } // 5. 飛車の縦方向の利きテーブルの初期化 // ここでは飛車の利きを使わずに初期化しないといけない。 for (Rank rank = Rank.RANK_1; rank <= Rank.RANK_5; ++rank) { // sq = SQ_11 , SQ_12 , ... , SQ_15 Square sq = Util.MakeSquare(File.FILE_1, rank); const int num1s = 3; for (int i = 0; i < (1 << num1s); ++i) { // iはsqに駒をおいたときに、その筋の2段~8段目の升がemptyかどうかを表現する値なので // 1ビットシフトして、1~5段目の升を表現するようにする。 int ii = i << 1; Bitboard bb = ZERO_BB; for (int r = sq.ToRank().ToInt() - 1; r >= (int)Rank.RANK_1; --r) { bb |= Util.MakeSquare(sq.ToFile(), (Rank)r); if ((ii & (1 << r)) != 0) { break; } } for (int r = sq.ToRank().ToInt() + 1; r <= (int)Rank.RANK_5; ++r) { bb |= Util.MakeSquare(sq.ToFile(), (Rank)r); if ((ii & (1 << r)) != 0) { break; } } RookFileEffectBB[(int)rank, i] = bb.p; // RookEffectFile[RANK_NB][x] には値を代入していないがC++の規約によりゼロ初期化されている。 } } // 飛車の横の利き for (File file = File.FILE_1; file <= File.FILE_5; ++file) { // sq = SQ_11 , SQ_21 , ... , SQ_NBまで Square sq = Util.MakeSquare(file, Rank.RANK_1); const int num1s = 3; for (int i = 0; i < (1 << num1s); ++i) { int ii = i << 1; Bitboard bb = ZERO_BB; for (int f = (int)sq.ToFile() - 1; f >= (int)File.FILE_1; --f) { bb |= Util.MakeSquare((File)f, sq.ToRank()); if ((ii & (1 << f)) != 0) { break; } } for (int f = (int)sq.ToFile() + 1; f <= (int)File.FILE_5; ++f) { bb |= Util.MakeSquare((File)f, sq.ToRank()); if ((ii & (1 << f)) != 0) { break; } } RookRankEffectBB[(int)file, i] = bb; // RookRankEffect[FILE_NB][x] には値を代入していないがC++の規約によりゼロ初期化されている。 } } // 6. 近接駒(+盤上の利きを考慮しない駒)のテーブルの初期化。 // 上で初期化した、香・馬・飛の利きを用いる。 foreach (var sq in All.Squares()) { // 玉は長さ1の角と飛車の利きを合成する KingEffectBB[(int)sq] = BishopEffect(sq, ALL_BB) | RookEffect(sq, ALL_BB); } foreach (var c in All.Colors()) { foreach (var sq in All.Squares()) { // 障害物がないときの香の利き // これを最初に初期化しないとlanceEffect()が使えない。 LanceStepEffectBB[(int)sq, (int)c] = RookFileEffect(sq, ZERO_BB) & ForwardRanks(c, sq.ToRank()); } } foreach (var c in All.Colors()) { foreach (var sq in All.Squares()) { // 歩は長さ1の香の利きとして定義できる PawnEffectBB[(int)sq, (int)c] = LanceEffect(c, sq, ALL_BB); // 桂の利きは、歩の利きの地点に長さ1の角の利きを作って、前方のみ残す。 Bitboard tmp = ZERO_BB; Bitboard pawn = LanceEffect(c, sq, ALL_BB); if (pawn.IsNotZero()) { Square sq2 = pawn.Pop(); Bitboard pawn2 = LanceEffect(c, sq2, ALL_BB); // さらに1つ前 if (pawn2.IsNotZero()) { tmp = BishopEffect(sq2, ALL_BB) & RANK_BB[(int)pawn2.Pop().ToRank()]; } } KnightEffectBB[(int)sq, (int)c] = tmp; // 銀は長さ1の角の利きと長さ1の香の利きの合成として定義できる。 SilverEffectBB[(int)sq, (int)c] = LanceEffect(c, sq, ALL_BB) | BishopEffect(sq, ALL_BB); // 金は長さ1の角と飛車の利き。ただし、角のほうは相手側の歩の行き先の段でmaskしてしまう。 Bitboard e_pawn = LanceEffect(c.Not(), sq, ALL_BB); Bitboard mask = ZERO_BB; if (e_pawn.IsNotZero()) { mask = RANK_BB[(int)e_pawn.Pop().ToRank()]; } GoldEffectBB[(int)sq, (int)c] = (BishopEffect(sq, ALL_BB) & ~mask) | RookEffect(sq, ALL_BB); // 障害物がないときの角と飛車の利き BishopStepEffectBB[(int)sq] = BishopEffect(sq, ZERO_BB); RookStepEffectBB[(int)sq] = RookEffect(sq, ZERO_BB); } } // 7) 二歩用のテーブル初期化 for (int i = 0; i < 0x20; ++i) { Bitboard b = ZERO_BB; for (int k = 0; k < 5; ++k) { if ((i & (1 << k)) == 0) { b |= FILE_BB[k]; } } PAWN_DROP_MASK_BB[i].p = b.p; } // 8) BetweenBB , LineBBの初期化 { UInt16 between_index = 1; // BetweenBB[0] == ZERO_BBであることを保証する。 foreach (var s1 in All.Squares()) { foreach (var s2 in All.Squares()) { // 十字方向か、斜め方向かだけを判定して、例えば十字方向なら // rookEffect(sq1,Bitboard(s2)) & rookEffect(sq2,Bitboard(s1)) // のように初期化したほうが明快なコードだが、この初期化をそこに依存したくないので愚直にやる。 // これについてはあとで設定する。 if (s1 >= s2) { continue; } // 方角を用いるテーブルの初期化 if (Util.DirectionsOf(s1, s2) != Directions.ZERO) { Bitboard bb = ZERO_BB; // 間に挟まれた升を1に int delta = (s2 - s1) / dist(s1, s2); for (Square s = s1 + delta; s != s2; s += delta) { bb |= s; } // ZERO_BBなら、このindexとしては0を指しておけば良いので書き換える必要ない。 if (bb.IsZero()) { continue; } BetweenIndex[(int)s1, (int)s2] = between_index; BetweenBB_[between_index++] = bb; } } } // ASSERT_LV1(between_index == 785); // 対称性を考慮して、さらにシュリンクする。 foreach (var s1 in All.Squares()) { foreach (var s2 in All.Squares()) { if (s1 > s2) { BetweenIndex[(int)s1, (int)s2] = BetweenIndex[(int)s2, (int)s1]; } } } LineBB_ = new Bitboard[(int)Square.NB, 4]; for (var s1 = Square.ZERO; s1 < Square.NB; ++s1) { for (int d = 0; d < 4; ++d) { // BishopEffect0 , RookRankEffect , BishopEffect1 , RookFileEffectを用いて初期化したほうが // 明快なコードだが、この初期化をそこに依存したくないので愚直にやる。 Square[] deltas = new Square[] { Square.SQ_RU, Square.SQ_R, Square.SQ_RD, Square.SQ_U }; int delta = (int)deltas[d]; Bitboard bb = new Bitboard(s1); // 壁に当たるまでs1から-delta方向に延長 for (Square s = s1; dist(s, s - delta) <= 1; s -= delta) { bb |= (s - delta); } // 壁に当たるまでs1から+delta方向に延長 for (Square s = s1; dist(s, s + delta) <= 1; s += delta) { bb |= (s + delta); } LineBB_[(int)s1, d] = bb; } } } }
/// <summary> /// 指し手が合法か /// </summary> /// <param name="m"></param> /// <returns></returns> public bool IsLegal(Move m) { Color Us = sideToMove; Square to = m.To(); Piece toPcType; if (m.IsDrop()) { Piece pr = toPcType = m.DroppedPiece(); // 打つ駒は適切か if (pr < Piece.PAWN || Piece.KING <= pr) { return(false); } // 駒を持っているか if (!Hand(Us).Exist(pr)) { return(false); } // 行き先に駒はないか if ((Pieces() & to).IsNotZero()) { return(false); } if (pr == Piece.PAWN) { var rank_1 = (Us == Color.BLACK) ? Rank.RANK_1 : Rank.RANK_5; if (to.ToRank() == rank_1) { return(false); } // 二歩と打ち歩詰めのチェック if (!LegalPawnDrop(Us, to)) { return(false); } } } else { Square from = m.From(); Piece moved_pc = PieceOn(from); if (moved_pc == Piece.NO_PIECE) { return(false); } // 手番側の駒か if (moved_pc.PieceColor() != sideToMove) { return(false); } // 行き先に味方の駒はないか if ((Pieces(Us) & to).IsNotZero()) /* to_pc != Piece.NO_PIECE && to_pc.PieceColor() == SideToMove */ { return(false); } // 駒の動きは適切か if ((Bitboard.EffectsFrom(moved_pc, from, Pieces()) & to).IsZero()) { return(false); } // 成りが適切か if (m.IsPromote()) { } // 王手している駒があるか if (InCheck()) { if (moved_pc.Type() != Piece.KING) { // 両王手の場合、玉を動かすほかない if (Checkers().PopCount() > 1) { return(false); } if (((Bitboard.BetweenBB(Checkers().Pop(), KingSquare(Us)) | Checkers()) & to).IsZero()) { return(false); } } } // 自殺手 if (moved_pc.Type() == Piece.KING) { // 玉の移動先に相手側の利きがあるか if (EffectedTo(Us.Not(), to, from)) { return(false); } } else { var b = (PinnedPieces(Us) & from).IsZero() || // ピンされていない駒の移動は自由である Util.IsAligned(from, to, KingSquare(Us)); // ピンされている方角への移動は合法 if (!b) { return(false); } } toPcType = moved_pc.Type(); } if (InCheck() && toPcType != Piece.KING) { Bitboard target = Checkers(); Square checkSq = target.Pop(); // 王手している駒を1個取り除いて、もうひとつあるということは王手している駒が // 2つあったということであり、両王手なので合い利かず。 if (target.IsNotZero()) { return(false); } // 王と王手している駒との間の升に駒を打っていない場合、それは王手を回避していることに // ならないので、これは非合法手。 // 王手している駒が1つなら、王手している駒を取る指し手であるか、 // 遮断する指し手でなければならない if (!((Bitboard.BetweenBB(checkSq, KingSquare(Us)) & to).IsNotZero() || checkSq == to)) { return(false); } } return(true); }