score += pieces<WHITE>(pt) - pieces<BLACK>(pt);
// Evaluate pieces in hand once attack tables are complete
- if (pos.piece_drops())
+ if (pos.piece_drops() || pos.gating())
for (PieceType pt = PAWN; pt < KING; ++pt)
score += hand<WHITE>(pt) - hand<BLACK>(pt);
namespace {
+ template<MoveType T>
+ ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to) {
+
+ *moveList++ = make<T>(from, to);
+
+ // Gating moves
+ if (pos.gating() && (pos.gates(us) & square_bb(from)))
+ for (PieceType pt_gating = PAWN; pt_gating <= KING; ++pt_gating)
+ if (pos.count_in_hand(us, pt_gating) && (pos.drop_region(us, pt_gating) & from))
+ *moveList++ = make_gating<T>(from, to, pt_gating, from);
+ if (pos.gating() && T == CASTLING && (pos.gates(us) & square_bb(to)))
+ for (PieceType pt_gating = PAWN; pt_gating <= KING; ++pt_gating)
+ if (pos.count_in_hand(us, pt_gating) && (pos.drop_region(us, pt_gating) & to))
+ *moveList++ = make_gating<T>(from, to, pt_gating, to);
+
+ return moveList;
+ }
+
template<Color c, GenType Type, Direction D>
ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) {
}
while (b1)
- *moveList++ = make_move(from, pop_lsb(&b1));
+ moveList = make_move_and_gating<NORMAL>(pos, moveList, us, from, pop_lsb(&b1));
// Shogi-style piece promotions
while (b2)
Square ksq = pos.square<KING>(Us);
Bitboard b = pos.attacks_from<KING>(Us, ksq) & target;
while (b)
- *moveList++ = make_move(ksq, pop_lsb(&b));
+ moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, ksq, pop_lsb(&b));
if (Type != CAPTURES && pos.can_castle(CastlingRight(OO | OOO)))
{
if (!pos.castling_impeded(OO) && pos.can_castle(OO))
- *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OO));
+ moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, ksq, pos.castling_rook_square(OO));
if (!pos.castling_impeded(OOO) && pos.can_castle(OOO))
- *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OOO));
+ moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, ksq, pos.castling_rook_square(OOO));
}
}
{
Square from = make_square(FILE_E, relative_rank(Us, pos.castling_rank(), pos.max_rank()));
if (!pos.castling_impeded(OO) && pos.can_castle(OO))
- *moveList++ = make<CASTLING>(from, pos.castling_rook_square(OO));
+ moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, from, pos.castling_rook_square(OO));
if (!pos.castling_impeded(OOO) && pos.can_castle(OOO))
- *moveList++ = make<CASTLING>(from, pos.castling_rook_square(OOO));
+ moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, from, pos.castling_rook_square(OOO));
}
return moveList;
b &= ~PseudoAttacks[~us][QUEEN][pos.square<KING>(~us)];
while (b)
- *moveList++ = make_move(from, pop_lsb(&b));
+ moveList = make_move_and_gating<NORMAL>(pos, moveList, us, from, pop_lsb(&b));
}
return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList, ~pos.pieces())
// Generate evasions for king, capture and non capture moves
Bitboard b = pos.attacks_from<KING>(us, ksq) & ~pos.pieces(us) & ~sliderAttacks;
while (b)
- *moveList++ = make_move(ksq, pop_lsb(&b));
+ moveList = make_move_and_gating<NORMAL>(pos, moveList, us, ksq, pop_lsb(&b));
if (more_than_one(pos.checkers()))
return moveList; // Double check, only a king move can save the day
else
continue;
+ // Set gates (and skip castling rights)
+ if (gating())
+ {
+ st->gatesBB[c] |= rsq;
+ if (token == 'K' || token == 'Q')
+ st->gatesBB[c] |= count<KING>(c) ? square<KING>(c) : make_square(FILE_E, relative_rank(c, castling_rank(), max_rank()));
+ // Do not set castling rights for gates unless there are no pieces in hand,
+ // which means that the file is referring to a chess960 castling right.
+ else if (count_in_hand(c, ALL_PIECES))
+ continue;
+ }
+
set_castling_right(c, rsq);
}
+ // Set castling rights for 960 gating variants
+ if (gating())
+ for (Color c : {WHITE, BLACK})
+ if ((gates(c) & pieces(KING)) && !castling_rights(c) && count_in_hand(c, ALL_PIECES))
+ {
+ Bitboard castling_rooks = gates(c) & pieces(ROOK);
+ while (castling_rooks)
+ set_castling_right(c, pop_lsb(&castling_rooks));
+ }
+
// counting limit
if (counting_rule() && isdigit(ss.peek()))
ss >> st->countingLimit;
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
si->materialKey ^= Zobrist::psq[pc][cnt];
- if (piece_drops())
+ if (piece_drops() || gating())
si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]];
}
}
// pieces in hand
- if (piece_drops())
+ if (piece_drops() || gating())
{
ss << '[';
for (Color c : {WHITE, BLACK})
if (can_castle(WHITE_OOO))
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
+ if (gating() && gates(WHITE))
+ for (File f = FILE_A; f <= max_file(); ++f)
+ if (gates(WHITE) & file_bb(f))
+ ss << char('A' + f);
+
if (can_castle(BLACK_OO))
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k');
if (can_castle(BLACK_OOO))
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
- if (!can_castle(ANY_CASTLING))
+ if (gating() && gates(BLACK))
+ for (File f = FILE_A; f <= max_file(); ++f)
+ if (gates(BLACK) & file_bb(f))
+ ss << char('a' + f);
+
+ if (!can_castle(ANY_CASTLING) && !(gating() && (gates(WHITE) | gates(BLACK))))
ss << '-';
// Counting limit or ep-square
|| (drop_promoted() && promoted_piece_type(type_of(pc)) == in_hand_piece_type(m)));
// Use a slower but simpler function for uncommon cases
- if (type_of(m) != NORMAL)
+ if (type_of(m) != NORMAL || is_gating(m))
return MoveList<LEGAL>(*this).contains(m);
// Handle the case where a mandatory piece promotion/demotion is not taken
&& attackers_to(square<KING>(~sideToMove), (pieces() ^ from) | to, sideToMove))
return true;
+ // Is there a check by gated pieces?
+ if ( is_gating(m)
+ && attacks_bb(sideToMove, gating_type(m), gating_square(m), (pieces() ^ from) | to) & square<KING>(~sideToMove))
+ return true;
+
switch (type_of(m))
{
case NORMAL:
if (captures_to_hand() && !captured)
st->capturedpromoted = false;
+ // Add gating piece
+ if (is_gating(m))
+ {
+ Square gate = gating_square(m);
+ Piece gating_piece = make_piece(us, gating_type(m));
+ put_piece(gating_piece, gate);
+ remove_from_hand(gating_piece);
+ st->gatesBB[us] ^= gate;
+ k ^= Zobrist::psq[gating_piece][gate];
+ st->materialKey ^= Zobrist::psq[gating_piece][pieceCount[gating_piece]];
+ st->nonPawnMaterial[us] += PieceValue[MG][gating_piece];
+ }
+
+ // Remove gates
+ if (gating())
+ {
+ if (is_ok(from) && (gates(us) & from))
+ st->gatesBB[us] ^= from;
+ if (type_of(m) == CASTLING)
+ st->gatesBB[us] ^= to;
+ if (gates(them) & to)
+ st->gatesBB[them] ^= to;
+ if (!count_in_hand(us, ALL_PIECES) && !captures_to_hand())
+ st->gatesBB[us] = 0;
+ }
+
// Update the key with the final value
st->key = k;
-
// Calculate checkers bitboard (if move gives check)
st->checkersBB = givesCheck ? attackers_to(square<KING>(them), us) & pieces(us) : Bitboard(0);
Square to = to_sq(m);
Piece pc = piece_on(to);
- assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || (type_of(m) == PROMOTION && sittuyin_promotion()));
+ assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) || (type_of(m) == PROMOTION && sittuyin_promotion()));
assert(type_of(st->capturedPiece) != KING);
+ // Remove gated piece
+ if (is_gating(m))
+ {
+ Piece gating_piece = make_piece(us, gating_type(m));
+ remove_piece(gating_piece, gating_square(m));
+ add_to_hand(gating_piece);
+ st->gatesBB[us] |= gating_square(m);
+ }
+
if (type_of(m) == PROMOTION)
{
assert(relative_rank(us, to, max_rank()) == promotion_rank() || sittuyin_promotion());
int countingLimit;
CheckCount checksGiven[COLOR_NB];
Square epSquare;
+ Bitboard gatesBB[COLOR_NB];
// Not copied when making a move (will be recomputed anyhow)
int repetition;
bool drop_promoted() const;
bool shogi_doubled_pawn() const;
bool immobility_illegal() const;
+ bool gating() const;
// winning conditions
int n_move_rule() const;
int n_fold_rule() const;
Piece piece_on(Square s) const;
Piece unpromoted_piece_on(Square s) const;
Square ep_square() const;
+ Bitboard gates(Color c) const;
bool empty(Square s) const;
int count(Color c, PieceType pt) const;
template<PieceType Pt> int count(Color c) const;
return var->immobilityIllegal;
}
+inline bool Position::gating() const {
+ assert(var != nullptr);
+ return var->gating;
+}
+
inline int Position::n_move_rule() const {
assert(var != nullptr);
return var->nMoveRule;
return st->epSquare;
}
+inline Bitboard Position::gates(Color c) const {
+ assert(var != nullptr);
+ return st->gatesBB[c];
+}
+
inline bool Position::is_on_semiopen_file(Color c, Square s) const {
return !(pieces(c, PAWN, SHOGI_PAWN) & file_bb(s));
}
return type_of(m) == PROMOTION ? PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)) : NO_PIECE_TYPE;
}
+inline PieceType gating_type(Move m) {
+ return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1));
+}
+
+inline Square gating_square(Move m) {
+ return Square((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) & SQUARE_BIT_MASK);
+}
+
+inline bool is_gating(Move m) {
+ return gating_type(m) && (type_of(m) == NORMAL || type_of(m) == CASTLING);
+}
+
constexpr Move make_move(Square from, Square to) {
return Move((from << SQUARE_BITS) + to);
}
return Move((pt_in_hand << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt_dropped << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + DROP + to);
}
+template<MoveType T>
+constexpr Move make_gating(Square from, Square to, PieceType pt, Square gate) {
+ return Move((gate << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + T + (from << SQUARE_BITS) + to);
+}
+
constexpr PieceType dropped_piece_type(Move m) {
return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1));
}
if (m == MOVE_NULL)
return "0000";
- if (type_of(m) == CASTLING && !pos.is_chess960())
+ if (is_gating(m) && gating_square(m) == to)
+ from = to_sq(m), to = from_sq(m);
+ else if (type_of(m) == CASTLING && !pos.is_chess960())
to = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), rank_of(from));
string move = (type_of(m) == DROP ? UCI::dropped_piece(pos, m) + (Options["Protocol"] == "usi" ? '*' : '@')
move += '+';
else if (type_of(m) == PIECE_DEMOTION)
move += '-';
+ else if (is_gating(m))
+ move += pos.piece_to_char()[make_piece(BLACK, gating_type(m))];
return move;
}
v->countingRule = ASEAN_COUNTING;
return v;
}
+ Variant* seirawan_variant() {
+ Variant* v = fairy_variant_base();
+ v->add_piece(ARCHBISHOP, 'h');
+ v->add_piece(CHANCELLOR, 'e');
+ v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1";
+ v->gating = true;
+ v->whiteDropRegion = Rank1BB;
+ v->blackDropRegion = Rank8BB;
+ v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+ return v;
+ }
Variant* minishogi_variant_base() {
Variant* v = fairy_variant_base();
v->variantTemplate = "shogi";
add("pocketknight", pocketknight_variant());
add("placement", placement_variant());
add("sittuyin", sittuyin_variant());
+ add("seirawan", seirawan_variant());
add("minishogi", minishogi_variant());
add("mini", minishogi_variant());
add("kyotoshogi", kyotoshogi_variant());
bool dropPromoted = false;
bool shogiDoubledPawn = true;
bool immobilityIllegal = false;
+ bool gating = false;
// game end
int nMoveRule = 50;
int nFoldRule = 3;
expect perft.exp sittuyin startpos 3 580096 > /dev/null
expect perft.exp sittuyin "fen 8/8/6R1/s3r3/P5R1/1KP3p1/1F2kr2/8[-] b - - 0 72" 4 657824 > /dev/null
expect perft.exp sittuyin "fen 2r5/6k1/6p1/3s2P1/3npR2/8/p2N2F1/3K4 w - - 1 50" 4 394031 > /dev/null
+ expect perft.exp seirawan startpos 5 27639803 > /dev/null
fi
# large-board variants