From de7e7dae4dd651f2bcadc975a031947a5c50e7d8 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sun, 28 Feb 2021 12:27:27 +0100 Subject: [PATCH] Support pseudo-royal pieces This implements support for pseudo-royal pieces, which allows to now fully support some new variants: - lichess atomic rules - coregal chess - maharajah and the sepoys Closes #81. No functional change for other variants. --- src/parser.cpp | 1 + src/position.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++---------- src/position.h | 14 +++++++++ src/variant.cpp | 10 ++++++- src/variant.h | 2 +- src/variants.ini | 17 +++++++++++ tests/perft.sh | 7 +++++ 7 files changed, 113 insertions(+), 17 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 90590ad..bba3654 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -298,6 +298,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("bikjangRule", v->bikjangRule); parse_attribute("extinctionValue", v->extinctionValue); parse_attribute("extinctionClaim", v->extinctionClaim); + parse_attribute("extinctionPseudoRoyal", v->extinctionPseudoRoyal); // extinction piece types const auto& it_ext = config.find("extinctionPieceTypes"); if (it_ext != config.end()) diff --git a/src/position.cpp b/src/position.cpp index 332aadf..ce812a4 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -519,6 +519,17 @@ void Position::set_check_info(StateInfo* si) const { si->shak = si->checkersBB & (byTypeBB[KNIGHT] | byTypeBB[ROOK] | byTypeBB[BERS]); si->bikjang = var->bikjangRule && ksq != SQ_NONE ? bool(attacks_bb(sideToMove, ROOK, ksq, pieces()) & pieces(sideToMove, KING)) : false; si->legalCapture = NO_VALUE; + if (var->extinctionPseudoRoyal) + { + si->pseudoRoyals = 0; + for (PieceType pt : extinction_piece_types()) + { + if (count(sideToMove, pt) <= var->extinctionPieceCount + 1) + si->pseudoRoyals |= pieces(sideToMove, pt); + if (count(~sideToMove, pt) <= var->extinctionPieceCount + 1) + si->pseudoRoyals |= pieces(~sideToMove, pt); + } + } } @@ -944,6 +955,47 @@ bool Position::legal(Move m) const { return false; } + // Check for attacks to pseudo-royal pieces + if (var->extinctionPseudoRoyal) + { + Square kto = to; + if (type_of(m) == CASTLING && (st->pseudoRoyals & from)) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + kto = make_square(to > from ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); + Direction step = to > from ? WEST : EAST; + for (Square s = kto; s != from + step; s += step) + if ( !(blast_on_capture() && (attacks_bb(s) & st->pseudoRoyals & pieces(~sideToMove))) + && attackers_to(s, (s == kto ? (pieces() ^ to) : pieces()) ^ from, ~us)) + return false; + } + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | kto; + if (type_of(m) == EN_PASSANT) + occupied &= ~square_bb(kto - pawn_push(us)); + if (capture(m) && blast_on_capture()) + occupied &= ~((attacks_bb(kto) & (pieces() ^ pieces(PAWN))) | kto); + Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); + Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); + if (is_ok(from) && (pseudoRoyals & from)) + pseudoRoyals ^= square_bb(from) ^ kto; + if (type_of(m) == PROMOTION && extinction_piece_types().find(promotion_type(m)) != extinction_piece_types().end()) + pseudoRoyals |= kto; + // Self-explosions are illegal + if (pseudoRoyals & ~occupied) + return false; + // Check for legality unless we capture a pseudo-royal piece + if (!(pseudoRoyalsTheirs & ~occupied)) + while (pseudoRoyals) + { + Square sr = pop_lsb(&pseudoRoyals); + // Touching pseudo-royal pieces are immune + if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto)))) + return false; + } + } + // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. @@ -965,28 +1017,24 @@ bool Position::legal(Move m) const { // enemy attacks, it is delayed at a later time: now! if (type_of(m) == CASTLING) { - // Non-royal pieces can not be impeded from castling - if (type_of(piece_on(from)) != KING && !var->extinctionPseudoRoyal) - return true; - // After castling, the rook and king final positions are the same in // Chess960 as they would be in standard chess. to = make_square(to > from ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); Direction step = to > from ? WEST : EAST; - for (Square s = to; s != from; s += step) - if (attackers_to(s, ~us)) - return false; - - // TODO: need to consider touching kings - if (var->extinctionPseudoRoyal && attackers_to(from, ~us)) - return false; - // Will the gate be blocked by king or rook? Square rto = to + (to_sq(m) > from_sq(m) ? WEST : EAST); if (is_gating(m) && (gating_square(m) == to || gating_square(m) == rto)) return false; + // Non-royal pieces can not be impeded from castling + if (type_of(piece_on(from)) != KING) + return true; + + for (Square s = to; s != from; s += step) + if (attackers_to(s, ~us)) + return false; + // In case of Chess960, verify if the Rook blocks some checks // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. return !chess960 || !attackers_to(to, pieces() ^ to_sq(m), ~us); @@ -1013,7 +1061,7 @@ bool Position::legal(Move m) const { // square is attacked by the opponent. Castling moves are checked // for legality during move generation. if (type_of(moved_piece(m)) == KING) - return type_of(m) == CASTLING || !attackers_to(to, occupied, ~us); + return !attackers_to(to, occupied, ~us); Bitboard janggiCannons = pieces(JANGGI_CANNON); if (type_of(moved_piece(m)) == JANGGI_CANNON) @@ -2273,8 +2321,9 @@ bool Position::is_optional_game_end(Value& result, int ply, int countStarted) co bool Position::is_immediate_game_end(Value& result, int ply) const { - // extinction - if (extinction_value() != VALUE_NONE) + // Extinction + // Extinction does not apply for pseudo-royal pieces, because they can not be captured + if (extinction_value() != VALUE_NONE && (!var->extinctionPseudoRoyal || blast_on_capture())) { for (Color c : { ~sideToMove, sideToMove }) for (PieceType pt : extinction_piece_types()) diff --git a/src/position.h b/src/position.h index e439ad7..8fa03e0 100644 --- a/src/position.h +++ b/src/position.h @@ -68,6 +68,7 @@ struct StateInfo { Bitboard pinners[COLOR_NB]; Bitboard checkSquares[PIECE_TYPE_NB]; Bitboard flippedPieces; + Bitboard pseudoRoyals; OptBool legalCapture; bool capturedpromoted; bool shak; @@ -741,6 +742,19 @@ inline Value Position::stalemate_value(int ply) const { int c = count(sideToMove) - count(~sideToMove); return c == 0 ? VALUE_DRAW : convert_mate_value(c < 0 ? var->stalemateValue : -var->stalemateValue, ply); } + // Check for checkmate of pseudo-royal pieces + if (var->extinctionPseudoRoyal) + { + Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); + Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); + while (pseudoRoyals) + { + Square sr = pop_lsb(&pseudoRoyals); + if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && attackers_to(sr, ~sideToMove)) + return convert_mate_value(var->checkmateValue, ply); + } + } return convert_mate_value(var->stalemateValue, ply); } diff --git a/src/variant.cpp b/src/variant.cpp index 7d500f0..80de6a0 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -310,7 +310,6 @@ namespace { // https://en.wikipedia.org/wiki/Atomic_chess Variant* atomic_variant() { Variant* v = nocheckatomic_variant(); - // TODO: castling, check(-mate), stalemate are not yet properly implemented v->extinctionPseudoRoyal = true; return v; } @@ -615,6 +614,14 @@ namespace { v->shatarMateRule = true; return v; } + Variant* coregal_variant() { + Variant* v = fairy_variant(); + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = {QUEEN}; + v->extinctionPseudoRoyal = true; + v->extinctionPieceCount = 64; // no matter how many queens, all are royal + return v; + } Variant* clobber_variant() { Variant* v = fairy_variant_base(); v->pieceToCharTable = "P.................p................."; @@ -1051,6 +1058,7 @@ void VariantMap::init() { add("almost", almost_variant()->conclude()); add("chigorin", chigorin_variant()->conclude()); add("shatar", shatar_variant()->conclude()); + add("coregal", coregal_variant()->conclude()); add("clobber", clobber_variant()->conclude()); add("breakthrough", breakthrough_variant()->conclude()); add("ataxx", ataxx_variant()->conclude()); diff --git a/src/variant.h b/src/variant.h index a68dac9..d8586dd 100644 --- a/src/variant.h +++ b/src/variant.h @@ -115,7 +115,7 @@ struct Variant { bool bikjangRule = false; Value extinctionValue = VALUE_NONE; bool extinctionClaim = false; - bool extinctionPseudoRoyal = false; // TODO: implementation incomplete + bool extinctionPseudoRoyal = false; std::set extinctionPieceTypes = {}; int extinctionPieceCount = 0; int extinctionOpponentPieceCount = 0; diff --git a/src/variants.ini b/src/variants.ini index da9512f..d51171e 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -179,6 +179,7 @@ # bikjangRule: consider Janggi bikjang (facing kings) rule [bool] (default: false) # extinctionValue: result when one of extinctionPieceTypes is extinct [Value] (default: none) # extinctionClaim: extinction of opponent pieces can only be claimed as side to move [bool] (default: false) +# extinctionPseudoRoyal: treat the last extinction piece like a royal piece [bool] (default: false) # extinctionPieceTypes: list of piece types for extinction rules, e.g., pnbrq (* means all) (default: ) # extinctionPieceCount: piece count at which the game is decided by extinction rule (default: 0) # extinctionOpponentPieceCount: opponent piece count required to adjudicate by extinction rule (default: 0) @@ -296,6 +297,22 @@ promotionPieceTypes = q doubleStep = false castling = false +# Mahajarah and the Sepoys +# https://en.wikipedia.org/wiki/Maharajah_and_the_Sepoys +[maharajah] +pawn = p +knight = n +bishop = b +rook = r +queen = q +king = k +amazon = m +pieceToCharTable = PNBRQ.............MKpnbrq.............mk +startFen = rnbqkbnr/pppppppp/8/8/8/8/8/4M3 w kq - 0 1 +extinctionValue = loss +extinctionPieceTypes = m +extinctionPseudoRoyal = true + # Upside-down [upsidedown:chess] startFen = RNBKQBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbkqbnr w - - 0 1 diff --git a/tests/perft.sh b/tests/perft.sh index b32f495..381191d 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -47,6 +47,13 @@ if [[ $1 == "" || $1 == "variant" ]]; then expect perft.exp racingkings startpos 4 296242 > /dev/null expect perft.exp racingkings "fen 6r1/2K5/5k2/8/3R4/8/8/8 w - - 0 1" 4 86041 > /dev/null expect perft.exp racingkings "fen 6R1/2k5/5K2/8/3r4/8/8/8 b - - 0 1" 4 86009 > /dev/null + expect perft.exp atomic startpos 4 197326 > /dev/null + expect perft.exp atomic "fen rn2kb1r/1pp1p2p/p2q1pp1/3P4/2P3b1/4PN2/PP3PPP/R2QKB1R b KQkq - 0 1" 4 1434825 > /dev/null + expect perft.exp atomic "fen rn1qkb1r/p5pp/2p5/3p4/N3P3/5P2/PPP4P/R1BQK3 w Qkq - 0 1" 4 714499 > /dev/null + expect perft.exp coregal startpos 4 195896 > /dev/null + expect perft.exp coregal "fen rn2kb1r/ppp1pppp/6q1/8/2PP2b1/5B2/PP3P1P/R1BQK1NR w KQkq - 1 9" 3 20421 > /dev/null + expect perft.exp coregal "fen 2Q5/3Pq2k/6p1/4Bp1p/5P1P/8/8/K7 w - - 2 72" 4 55970 > /dev/null + expect perft.exp coregal "fen r3kb1r/1pp1pppp/p1q2n2/3P4/6b1/2N2N2/PPP2PPP/R1BQ1RK1 b kq - 0 9" 4 136511 > /dev/null expect perft.exp knightmate startpos 5 3249033 > /dev/null expect perft.exp losers startpos 5 2723795 > /dev/null expect perft.exp antichess startpos 5 2732672 > /dev/null -- 1.7.0.4