From: Fabian Fichter Date: Sun, 4 Aug 2019 16:35:47 +0000 (+0200) Subject: Support S-chess X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=6f023653396184b86e6a9e39b33d578f521d48e9;p=fairystockfish.git Support S-chess https://en.wikipedia.org/wiki/Seirawan_chess No functional change for other variants. --- diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c469a85..60d02a3 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1053,7 +1053,7 @@ namespace { score += pieces(pt) - pieces(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(pt) - hand(pt); diff --git a/src/movegen.cpp b/src/movegen.cpp index 6cb35f2..3263266 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -25,6 +25,24 @@ namespace { + template + ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to) { + + *moveList++ = make(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(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(from, to, pt_gating, to); + + return moveList; + } + template ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) { @@ -273,7 +291,7 @@ namespace { } while (b1) - *moveList++ = make_move(from, pop_lsb(&b1)); + moveList = make_move_and_gating(pos, moveList, us, from, pop_lsb(&b1)); // Shogi-style piece promotions while (b2) @@ -308,15 +326,15 @@ namespace { Square ksq = pos.square(Us); Bitboard b = pos.attacks_from(Us, ksq) & target; while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + moveList = make_move_and_gating(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(ksq, pos.castling_rook_square(OO)); + moveList = make_move_and_gating(pos, moveList, Us, ksq, pos.castling_rook_square(OO)); if (!pos.castling_impeded(OOO) && pos.can_castle(OOO)) - *moveList++ = make(ksq, pos.castling_rook_square(OOO)); + moveList = make_move_and_gating(pos, moveList, Us, ksq, pos.castling_rook_square(OOO)); } } @@ -325,10 +343,10 @@ namespace { { 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(from, pos.castling_rook_square(OO)); + moveList = make_move_and_gating(pos, moveList, Us, from, pos.castling_rook_square(OO)); if (!pos.castling_impeded(OOO) && pos.can_castle(OOO)) - *moveList++ = make(from, pos.castling_rook_square(OOO)); + moveList = make_move_and_gating(pos, moveList, Us, from, pos.castling_rook_square(OOO)); } return moveList; @@ -390,7 +408,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { b &= ~PseudoAttacks[~us][QUEEN][pos.square(~us)]; while (b) - *moveList++ = make_move(from, pop_lsb(&b)); + moveList = make_move_and_gating(pos, moveList, us, from, pop_lsb(&b)); } return us == WHITE ? generate_all(pos, moveList, ~pos.pieces()) @@ -422,7 +440,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { // Generate evasions for king, capture and non capture moves Bitboard b = pos.attacks_from(us, ksq) & ~pos.pieces(us) & ~sliderAttacks; while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + moveList = make_move_and_gating(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 diff --git a/src/position.cpp b/src/position.cpp index 931e346..a9449cc 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -372,9 +372,31 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, else continue; + // Set gates (and skip castling rights) + if (gating()) + { + st->gatesBB[c] |= rsq; + if (token == 'K' || token == 'Q') + st->gatesBB[c] |= count(c) ? square(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; @@ -527,7 +549,7 @@ void Position::set_state(StateInfo* si) const { 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]]; } @@ -598,7 +620,7 @@ const string Position::fen() const { } // pieces in hand - if (piece_drops()) + if (piece_drops() || gating()) { ss << '['; for (Color c : {WHITE, BLACK}) @@ -615,13 +637,23 @@ const string Position::fen() const { 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 @@ -854,7 +886,7 @@ bool Position::pseudo_legal(const Move m) const { || (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(*this).contains(m); // Handle the case where a mandatory piece promotion/demotion is not taken @@ -949,6 +981,11 @@ bool Position::gives_check(Move m) const { && attackers_to(square(~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(~sideToMove)) + return true; + switch (type_of(m)) { case NORMAL: @@ -1241,9 +1278,34 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { 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(them), us) & pieces(us) : Bitboard(0); @@ -1311,9 +1373,18 @@ void Position::undo_move(Move m) { 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()); diff --git a/src/position.h b/src/position.h index 6e75dd0..fb396ca 100644 --- a/src/position.h +++ b/src/position.h @@ -49,6 +49,7 @@ struct StateInfo { int countingLimit; CheckCount checksGiven[COLOR_NB]; Square epSquare; + Bitboard gatesBB[COLOR_NB]; // Not copied when making a move (will be recomputed anyhow) int repetition; @@ -128,6 +129,7 @@ public: 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; @@ -158,6 +160,7 @@ public: 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 int count(Color c) const; @@ -494,6 +497,11 @@ inline bool Position::immobility_illegal() 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; @@ -684,6 +692,11 @@ inline Square Position::ep_square() const { 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)); } diff --git a/src/types.h b/src/types.h index 22d3075..bf276c6 100644 --- a/src/types.h +++ b/src/types.h @@ -677,6 +677,18 @@ inline PieceType promotion_type(Move m) { 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); } @@ -690,6 +702,11 @@ constexpr Move make_drop(Square to, PieceType pt_in_hand, PieceType pt_dropped) 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 +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)); } diff --git a/src/uci.cpp b/src/uci.cpp index 81b2702..0d6f9b9 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -315,7 +315,9 @@ string UCI::move(const Position& pos, Move m) { 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" ? '*' : '@') @@ -327,6 +329,8 @@ string UCI::move(const Position& pos, Move m) { 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; } diff --git a/src/variant.cpp b/src/variant.cpp index fd35e6c..443d83e 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -249,6 +249,17 @@ VariantMap variants; // Global object 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"; @@ -641,6 +652,7 @@ void VariantMap::init() { 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()); diff --git a/src/variant.h b/src/variant.h index caa5638..5e72c34 100644 --- a/src/variant.h +++ b/src/variant.h @@ -72,6 +72,7 @@ struct Variant { bool dropPromoted = false; bool shogiDoubledPawn = true; bool immobilityIllegal = false; + bool gating = false; // game end int nMoveRule = 50; int nFoldRule = 3; diff --git a/tests/perft.sh b/tests/perft.sh index c3f4bf1..e53925a 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -54,6 +54,7 @@ if [[ $1 == "" || $1 == "variant" ]]; then 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