From 7d74f16f32a7bc198640b6a33c72b79b7d191ee8 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sun, 7 Feb 2021 17:37:06 +0100 Subject: [PATCH] Fix ambiguous 960 castling In case there is more than one "king" piece type that could represent the castling piece, resolve this ambiguity in the FEN, e.g., EAH means that an e-file king has a- and h-file rooks with which it can perform castling. For non-960 games disambiguation is achieved via configuration. Closes #239. --- src/movegen.cpp | 2 +- src/parser.cpp | 2 ++ src/position.cpp | 33 +++++++++++++++++++++++++++------ src/position.h | 24 ++++++++++++++++++++++++ src/variant.cpp | 6 ++++++ src/variant.h | 2 ++ src/variants.ini | 4 +++- 7 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index f1becbb..f88222a 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -401,7 +401,7 @@ namespace { // Castling with non-king piece if (!pos.count(Us) && Type != CAPTURES && pos.can_castle(Us & ANY_CASTLING)) { - Square from = make_square(FILE_E, pos.castling_rank(Us)); + Square from = pos.castling_king_square(Us); for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) moveList = make_move_and_gating(pos, moveList, Us, from, pos.castling_rook_square(cr)); diff --git a/src/parser.cpp b/src/parser.cpp index ddb29c1..90590ad 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -248,6 +248,8 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("castlingKingsideFile", v->castlingKingsideFile); parse_attribute("castlingQueensideFile", v->castlingQueensideFile); parse_attribute("castlingRank", v->castlingRank); + parse_attribute("castlingKingFile", v->castlingKingFile); + parse_attribute("castlingKingPiece", v->castlingKingPiece, v->pieceToChar); parse_attribute("castlingRookPiece", v->castlingRookPiece, v->pieceToChar); parse_attribute("kingType", v->kingType, v->pieceToChar); parse_attribute("checking", v->checking); diff --git a/src/position.cpp b/src/position.cpp index 5aab5c2..c5176d3 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -315,6 +315,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, // 3-4. Skip parsing castling and en passant flags if not present st->epSquare = SQ_NONE; + st->castlingKingSquare[WHITE] = st->castlingKingSquare[BLACK] = SQ_NONE; if (!isdigit(ss.peek()) && !sfen) { // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, @@ -342,26 +343,37 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, else continue; + // Determine castling "king" position + if (castling_enabled() && st->castlingKingSquare[c] == SQ_NONE) + { + Bitboard castlingKings = pieces(c, castling_king_piece()) & rank_bb(castling_rank(c)); + // Ambiguity resolution for 960 variants with more than one "king" + // e.g., EAH means that an e-file king can castle with a- and h-file rooks + st->castlingKingSquare[c] = isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece()) ? rsq + : castlingKings && (!more_than_one(castlingKings) || isChess960) ? lsb(castlingKings) + : make_square(castling_king_file(), castling_rank(c)); + } + // 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, castling_rank(c)); + st->gatesBB[c] |= st->castlingKingSquare[c]; // 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 (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand()) continue; } - if (castling_enabled()) + if (castling_enabled() && piece_on(rsq) == rook) set_castling_right(c, rsq); } // Set castling rights for 960 gating variants if (gating() && castling_enabled()) for (Color c : {WHITE, BLACK}) - if ((gates(c) & pieces(KING)) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand())) + if ((gates(c) & pieces(castling_king_piece())) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand())) { Bitboard castling_rooks = gates(c) & pieces(castling_rook_piece()); while (castling_rooks) @@ -474,7 +486,8 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, void Position::set_castling_right(Color c, Square rfrom) { - Square kfrom = count(c) ? square(c) : make_square(FILE_E, castling_rank(c)); + assert(st->castlingKingSquare[c] != SQ_NONE); + Square kfrom = st->castlingKingSquare[c]; CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE); st->castlingRights |= cr; @@ -657,6 +670,10 @@ const string Position::fen(bool sfen, bool showPromoted, int countStarted, std:: ss << (sideToMove == WHITE ? " w " : " b "); + // Disambiguation for chess960 "king" square + if (chess960 && can_castle(WHITE_CASTLING) && popcount(pieces(WHITE, castling_king_piece()) & rank_bb(castling_rank(WHITE))) > 1) + ss << char('A' + castling_king_square(WHITE)); + if (can_castle(WHITE_OO)) ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K'); @@ -668,6 +685,10 @@ const string Position::fen(bool sfen, bool showPromoted, int countStarted, std:: if (gates(WHITE) & file_bb(f)) ss << char('A' + f); + // Disambiguation for chess960 "king" square + if (chess960 && can_castle(BLACK_CASTLING) && popcount(pieces(BLACK, castling_king_piece()) & rank_bb(castling_rank(BLACK))) > 1) + ss << char('a' + castling_king_square(BLACK)); + if (can_castle(BLACK_OO)) ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k'); @@ -1426,7 +1447,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Set castling rights for dropped king or rook if (castling_dropped_piece() && rank_of(to) == castling_rank(us)) { - if (type_of(pc) == KING && file_of(to) == FILE_E) + if (type_of(pc) == castling_king_piece() && file_of(to) == castling_king_file()) { Bitboard castling_rooks = pieces(us, castling_rook_piece()) & rank_bb(castling_rank(us)) @@ -1437,7 +1458,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { else if (type_of(pc) == castling_rook_piece()) { if ( (file_of(to) == FILE_A || file_of(to) == max_file()) - && piece_on(make_square(FILE_E, castling_rank(us))) == make_piece(us, KING)) + && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece())) set_castling_right(us, to); } } diff --git a/src/position.h b/src/position.h index 8719ef7..065ae67 100644 --- a/src/position.h +++ b/src/position.h @@ -52,6 +52,7 @@ struct StateInfo { int countingLimit; CheckCount checksRemaining[COLOR_NB]; Square epSquare; + Square castlingKingSquare[COLOR_NB]; Bitboard gatesBB[COLOR_NB]; // Not copied when making a move (will be recomputed anyhow) @@ -136,6 +137,8 @@ public: File castling_kingside_file() const; File castling_queenside_file() const; Rank castling_rank(Color c) const; + File castling_king_file() const; + PieceType castling_king_piece() const; PieceType castling_rook_piece() const; PieceType king_type() const; bool checking_permitted() const; @@ -202,12 +205,14 @@ public: Piece piece_on(Square s) const; Piece unpromoted_piece_on(Square s) const; Square ep_square() const; + Square castling_king_square(Color c) const; Bitboard gates(Color c) const; bool empty(Square s) const; int count(Color c, PieceType pt) const; template int count(Color c) const; template int count() const; template Square square(Color c) const; + Square square(Color c, PieceType pt) const; bool is_on_semiopen_file(Color c, Square s) const; // Castling @@ -475,6 +480,16 @@ inline Rank Position::castling_rank(Color c) const { return relative_rank(c, var->castlingRank, max_rank()); } +inline File Position::castling_king_file() const { + assert(var != nullptr); + return var->castlingKingFile; +} + +inline PieceType Position::castling_king_piece() const { + assert(var != nullptr); + return var->castlingKingPiece; +} + inline PieceType Position::castling_rook_piece() const { assert(var != nullptr); return var->castlingRookPiece; @@ -891,10 +906,19 @@ template inline Square Position::square(Color c) const { return lsb(pieces(c, Pt)); } +inline Square Position::square(Color c, PieceType pt) const { + assert(count(c, pt) == 1); + return lsb(pieces(c, pt)); +} + inline Square Position::ep_square() const { return st->epSquare; } +inline Square Position::castling_king_square(Color c) const { + return st->castlingKingSquare[c]; +} + inline Bitboard Position::gates(Color c) const { assert(var != nullptr); return st->gatesBB[c]; diff --git a/src/variant.cpp b/src/variant.cpp index 1909e5b..7d500f0 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -209,6 +209,7 @@ namespace { v->remove_piece(KNIGHT); v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1"; v->kingType = KNIGHT; + v->castlingKingPiece = KNIGHT; v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP}; return v; } @@ -227,6 +228,7 @@ namespace { v->variantTemplate = "giveaway"; v->remove_piece(KING); v->add_piece(COMMONER, 'k'); + v->castlingKingPiece = COMMONER; v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; v->stalemateValue = VALUE_MATE; v->extinctionValue = VALUE_MATE; @@ -255,6 +257,7 @@ namespace { Variant* v = fairy_variant_base(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); + v->castlingKingPiece = COMMONER; v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT, PAWN}; @@ -272,6 +275,7 @@ namespace { Variant* v = fairy_variant_base(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); + v->castlingKingPiece = COMMONER; v->startFen = "knbqkbnk/pppppppp/8/8/8/8/PPPPPPPP/KNBQKBNK w - - 0 1"; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER}; @@ -296,6 +300,7 @@ namespace { v->variantTemplate = "atomic"; v->remove_piece(KING); v->add_piece(COMMONER, 'k'); + v->castlingKingPiece = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER}; v->blastOnCapture = true; @@ -352,6 +357,7 @@ namespace { Variant* v = bughouse_variant(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); + v->castlingKingPiece = COMMONER; v->mustDrop = true; v->mustDropType = COMMONER; v->extinctionValue = -VALUE_MATE; diff --git a/src/variant.h b/src/variant.h index 539f627..13a8707 100644 --- a/src/variant.h +++ b/src/variant.h @@ -65,6 +65,8 @@ struct Variant { File castlingKingsideFile = FILE_G; File castlingQueensideFile = FILE_C; Rank castlingRank = RANK_1; + File castlingKingFile = FILE_E; + PieceType castlingKingPiece = KING; PieceType castlingRookPiece = ROOK; PieceType kingType = KING; bool checking = true; diff --git a/src/variants.ini b/src/variants.ini index 5087ec3..dff56c8 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -130,7 +130,9 @@ # castlingKingsideFile: destination file of king after kingside castling [File] (default: g) # castlingQueensideFile: destination file of king after queenside castling [File] (default: c) # castlingRank: relative rank of castling [Rank] (default: 1) -# castlingRookPiece: piece type that participates in castling [PieceType] (default: r) +# castlingKingFile: starting file of the castlingKingPiece if there can be more than one of that type [File] (default: e) +# castlingKingPiece: first piece type that participates in castling [PieceType] (default: k) +# castlingRookPiece: second piece type that participates in castling [PieceType] (default: r) # kingType: piece type defining moves of king/royal piece (default: k) # checking: allow checks [bool] (default: true) # dropChecks: allow checks by piece drops [bool] (default: true) -- 1.7.0.4