From: Fabian Fichter Date: Sat, 22 Apr 2023 14:17:31 +0000 (+0200) Subject: Refactor capture the flag implementation X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=3eea0e9ec3e716e304f44558c4911176d0e053e5;p=fairystockfish.git Refactor capture the flag implementation Simplify and generalize implementation to support color-specific flag pieces as well as allowing all pieces to be eligible. --- diff --git a/src/apiutil.h b/src/apiutil.h index 988ff5e..d753579 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -369,7 +369,7 @@ inline bool has_insufficient_material(Color c, const Position& pos) { if ( pos.captures_to_hand() || pos.count_in_hand(c, ALL_PIECES) || (pos.extinction_value() != VALUE_NONE && !pos.extinction_pseudo_royal()) - || (pos.capture_the_flag_piece() && pos.count(c, pos.capture_the_flag_piece()))) + || (pos.flag_region(c) && pos.count(c, pos.flag_piece(c)))) return false; // Restricted pieces diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c76f2cc..8582766 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1163,8 +1163,8 @@ namespace { int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); Score score = make_score(bonus * weight * weight / 16, 0); - if (pos.capture_the_flag(Us)) - score += make_score(200, 200) * popcount(behind & safe & pos.capture_the_flag(Us)); + if (pos.flag_region(Us)) + score += make_score(200, 200) * popcount(behind & safe & pos.flag_region(Us)); if constexpr (T) Trace::add(SPACE, Us, score); @@ -1184,11 +1184,10 @@ namespace { Score score = SCORE_ZERO; // Capture the flag - if (pos.capture_the_flag(Us)) + if (pos.flag_region(Us)) { - PieceType ptCtf = pos.capture_the_flag_piece(); - Bitboard ctfPieces = pos.pieces(Us, ptCtf); - Bitboard ctfTargets = pos.capture_the_flag(Us) & pos.board_bb(); + Bitboard ctfPieces = pos.pieces(Us, pos.flag_piece(Us)); + Bitboard ctfTargets = pos.flag_region(Us) & pos.board_bb(); Bitboard onHold = 0; Bitboard onHold2 = 0; Bitboard processed = 0; @@ -1201,6 +1200,8 @@ namespace { // Traverse all paths of the CTF pieces to the CTF targets. // Put squares that are attacked or occupied on hold for one iteration. // This reflects that likely a move will be needed to block or capture the attack. + // If all piece types are eligible, use the king path as a proxy for distance. + PieceType ptCtf = pos.flag_piece(Us) == ALL_PIECES ? KING : pos.flag_piece(Us); for (int dist = 0; (ctfPieces || onHold || onHold2) && (ctfTargets & ~processed); dist++) { int wins = popcount(ctfTargets & ctfPieces); diff --git a/src/nnue/features/half_ka_v2_variants.cpp b/src/nnue/features/half_ka_v2_variants.cpp index 4d23de0..1e0fd6e 100644 --- a/src/nnue/features/half_ka_v2_variants.cpp +++ b/src/nnue/features/half_ka_v2_variants.cpp @@ -32,7 +32,7 @@ namespace Stockfish::Eval::NNUE::Features { // Orient a square according to perspective (rotates by 180 for black) // Missing kings map to index 0 (SQ_A1) inline Square HalfKAv2Variants::orient(Color perspective, Square s, const Position& pos) { - return s != SQ_NONE ? to_variant_square( perspective == WHITE || (pos.capture_the_flag(BLACK) & Rank8BB) ? s + return s != SQ_NONE ? to_variant_square( perspective == WHITE || (pos.flag_region(BLACK) & Rank8BB) ? s : flip_rank(s, pos.max_rank()), pos) : SQ_A1; } diff --git a/src/parser.cpp b/src/parser.cpp index d7eab8e..38b253e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -446,7 +446,12 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("extinctionPieceTypes", v->extinctionPieceTypes, v->pieceToChar); parse_attribute("extinctionPieceCount", v->extinctionPieceCount); parse_attribute("extinctionOpponentPieceCount", v->extinctionOpponentPieceCount); - parse_attribute("flagPiece", v->flagPiece, v->pieceToChar); + parse_attribute("flagPiece", v->flagPiece[WHITE], v->pieceToChar); + parse_attribute("flagPiece", v->flagPiece[BLACK], v->pieceToChar); + parse_attribute("flagPieceWhite", v->flagPiece[WHITE], v->pieceToChar); + parse_attribute("flagPieceBlack", v->flagPiece[BLACK], v->pieceToChar); + parse_attribute("flagRegion", v->flagRegion[WHITE]); + parse_attribute("flagRegion", v->flagRegion[BLACK]); parse_attribute("flagRegionWhite", v->flagRegion[WHITE]); parse_attribute("flagRegionBlack", v->flagRegion[BLACK]); parse_attribute("flagPieceCount", v->flagPieceCount); diff --git a/src/position.cpp b/src/position.cpp index 0e52486..6084c4d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2673,64 +2673,28 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { } } // capture the flag - if ( capture_the_flag_piece() - && flag_move() - && ( - (popcount(capture_the_flag(sideToMove) & pieces(sideToMove, capture_the_flag_piece()))>=flag_piece_count()) // opponent has >= number of pieces needed to win - || //-or- - ( - (flag_piece_blocked_win()) //flagPieceBlockedWin variant option true - && //-and- - (capture_the_flag(sideToMove) & pieces(sideToMove, capture_the_flag_piece())) //at least one piece in flag zone - && //-and- - !(capture_the_flag(sideToMove) & ~pieces()) //no empty squares in flag zone - ) - ) - ) - { - result = - ( - ( - (popcount(capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece()))>=flag_piece_count()) // you have >= number of pieces needed to win - || //-or- - ( - (flag_piece_blocked_win()) //flagPieceBlockedWin variant option true - && //-and- - (capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece())) //at least one piece in flag zone - && //-and- - !(capture_the_flag(~sideToMove) & ~pieces()) //no empty squares in flag zone - ) - ) - && - (sideToMove == WHITE) //opponent is white - ) - ? VALUE_DRAW : mate_in(ply); //then it's a draw, otherwise, win + // A flag win by the side to move is only possible if flagMove is enabled + // and they already reached the flag region the move before. + // In the case both colors reached it, it is a draw if white was first. + if (flag_move() && flag_reached(sideToMove)) + { + result = sideToMove == WHITE && flag_reached(BLACK) ? VALUE_DRAW : mate_in(ply); return true; } - if ( capture_the_flag_piece() - && (!flag_move() || capture_the_flag_piece() == KING) //if black doesn't get an extra move to draw, or flag piece is king, - && ( //-and- - (popcount(capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece()))>=flag_piece_count()) // you have >= number of pieces needed to win - || //-or- - ( - (flag_piece_blocked_win()) //flagPieceBlockedWin variant option true - && //-and- - (capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece())) //at least one piece in flag zone - && //-and- - !(capture_the_flag(~sideToMove) & ~pieces()) //no empty squares in flag zone - ) - ) - ) + // A direct flag win is possible if the opponent does not get an extra flag move + // or we can detect early for kings that they won't be able to reach the flag region + // Note: This condition has to be after the above, since both might be true e.g. in racing kings. + if ( (!flag_move() || flag_piece(sideToMove) == KING) // we can do early win detection only for the king + && flag_reached(~sideToMove)) { bool gameEnd = true; - // Check whether king can move to CTF zone + // Check whether king can move to CTF zone (racing kings) to draw if ( flag_move() && sideToMove == BLACK && !checkers() && count(sideToMove) - && (capture_the_flag(sideToMove) & attacks_from(sideToMove, KING, square(sideToMove)))) + && (flag_region(sideToMove) & attacks_from(sideToMove, KING, square(sideToMove)))) { - assert(capture_the_flag_piece() == KING); - gameEnd = true; + assert(flag_piece(sideToMove) == KING); for (const auto& m : MoveList(*this)) - if (type_of(moved_piece(m)) == KING && (capture_the_flag(sideToMove) & to_sq(m)) && legal(m)) + if (type_of(moved_piece(m)) == KING && (flag_region(sideToMove) & to_sq(m)) && legal(m)) { gameEnd = false; break; diff --git a/src/position.h b/src/position.h index 6cd9f6b..381f6fd 100644 --- a/src/position.h +++ b/src/position.h @@ -197,12 +197,11 @@ public: int extinction_piece_count() const; int extinction_opponent_piece_count() const; bool extinction_pseudo_royal() const; - PieceType capture_the_flag_piece() const; - Bitboard capture_the_flag(Color c) const; + PieceType flag_piece(Color c) const; + Bitboard flag_region(Color c) const; bool flag_move() const; + bool flag_reached(Color c) const; bool check_counting() const; - int flag_piece_count() const; - bool flag_piece_blocked_win() const; int connect_n() const; CheckCount checks_remaining(Color c) const; MaterialCounting material_counting() const; @@ -917,12 +916,12 @@ inline bool Position::extinction_pseudo_royal() const { return var->extinctionPseudoRoyal; } -inline PieceType Position::capture_the_flag_piece() const { +inline PieceType Position::flag_piece(Color c) const { assert(var != nullptr); - return var->flagPiece; + return var->flagPiece[c]; } -inline Bitboard Position::capture_the_flag(Color c) const { +inline Bitboard Position::flag_region(Color c) const { assert(var != nullptr); return var->flagRegion[c]; } @@ -932,14 +931,11 @@ inline bool Position::flag_move() const { return var->flagMove; } -inline int Position::flag_piece_count() const { +inline bool Position::flag_reached(Color c) const { assert(var != nullptr); - return var->flagPieceCount; -} - -inline bool Position::flag_piece_blocked_win() const { - assert(var != nullptr); - return var->flagPieceBlockedWin; + return (flag_region(c) & pieces(c, flag_piece(c))) + && ( popcount(flag_region(c) & pieces(c, flag_piece(c))) >= var->flagPieceCount + || (var->flagPieceBlockedWin && !(flag_region(c) & ~pieces()))); } inline bool Position::check_counting() const { diff --git a/src/search.cpp b/src/search.cpp index e1c34c9..fc7c4f8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -982,7 +982,7 @@ namespace { } } - probCutBeta = beta + (209 + 20 * !!pos.capture_the_flag_piece() + 50 * pos.captures_to_hand()) * (1 + pos.check_counting() + pos.extinction_single_piece()) - 44 * improving; + probCutBeta = beta + (209 + 20 * !!pos.flag_region(~pos.side_to_move()) + 50 * pos.captures_to_hand()) * (1 + pos.check_counting() + pos.extinction_single_piece()) - 44 * improving; // Step 9. ProbCut (~4 Elo) // If we have a good enough capture and a reduced search returns a value @@ -1185,7 +1185,7 @@ moves_loop: // When in check, search starts from here continue; // Prune moves with negative SEE (~20 Elo) - if (!pos.variant()->duckGating && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth))) + if (!pos.variant()->duckGating && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.flag_region(pos.side_to_move())) * lmrDepth * lmrDepth))) continue; } } diff --git a/src/variant.cpp b/src/variant.cpp index 6a020a7..6332047 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -325,7 +325,7 @@ namespace { // https://lichess.org/variant/kingOfTheHill Variant* kingofthehill_variant() { Variant* v = chess_variant_base()->init(); - v->flagPiece = KING; + v->flagPiece[WHITE] = v->flagPiece[BLACK] = KING; v->flagRegion[WHITE] = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); v->flagRegion[BLACK] = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); v->flagMove = false; @@ -336,7 +336,7 @@ namespace { Variant* racingkings_variant() { Variant* v = chess_variant_base()->init(); v->startFen = "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1"; - v->flagPiece = KING; + v->flagPiece[WHITE] = v->flagPiece[BLACK] = KING; v->flagRegion[WHITE] = Rank8BB; v->flagRegion[BLACK] = Rank8BB; v->flagMove = true; @@ -549,7 +549,7 @@ namespace { v->add_piece(CUSTOM_PIECE_2, 'f', "mF"); //Fox v->startFen = "1h1h1h1h/8/8/8/8/8/8/4F3 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->flagPiece = CUSTOM_PIECE_2; + v->flagPiece[WHITE] = CUSTOM_PIECE_2; v->flagRegion[WHITE] = Rank8BB; return v; } @@ -845,7 +845,7 @@ namespace { v->mandatoryPiecePromotion = true; v->immobilityIllegal = false; v->shogiPawnDropMateIllegal = false; - v->flagPiece = KING; + v->flagPiece[WHITE] = v->flagPiece[BLACK] = KING; v->flagRegion[WHITE] = Rank4BB; v->flagRegion[BLACK] = Rank1BB; v->dropNoDoubled = NO_PIECE_TYPE; @@ -1091,7 +1091,7 @@ namespace { v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; - v->flagPiece = BREAKTHROUGH_PIECE; + v->flagPiece[WHITE] = v->flagPiece[BLACK] = BREAKTHROUGH_PIECE; v->flagRegion[WHITE] = Rank8BB; v->flagRegion[BLACK] = Rank1BB; return v; @@ -1422,7 +1422,7 @@ namespace { v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; - v->flagPiece = KNIGHT; + v->flagPiece[WHITE] = v->flagPiece[BLACK] = KNIGHT; v->flagRegion[WHITE] = make_bitboard(SQ_E5); v->flagRegion[BLACK] = make_bitboard(SQ_E5); v->flagMove = true; diff --git a/src/variant.h b/src/variant.h index 891b039..3e5dcb5 100644 --- a/src/variant.h +++ b/src/variant.h @@ -141,7 +141,7 @@ struct Variant { PieceSet extinctionPieceTypes = NO_PIECE_SET; int extinctionPieceCount = 0; int extinctionOpponentPieceCount = 0; - PieceType flagPiece = NO_PIECE_TYPE; + PieceType flagPiece[COLOR_NB] = {ALL_PIECES, ALL_PIECES}; Bitboard flagRegion[COLOR_NB] = {}; int flagPieceCount = 1; bool flagPieceBlockedWin = false; @@ -323,7 +323,7 @@ struct Variant { && checkmateValue == -VALUE_MATE && stalemateValue == VALUE_DRAW && !materialCounting - && !flagPiece + && !(flagRegion[WHITE] || flagRegion[BLACK]) && !mustCapture && !checkCounting && !makpongRule diff --git a/src/variants.ini b/src/variants.ini index c882be3..b581fcc 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -245,13 +245,16 @@ # 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) -# flagPiece: piece type for capture the flag win rule [PieceType] (default: -) +# flagPiece: piece type for capture the flag win rule [PieceType] (default: *) +# flagPieceWhite: piece type for capture the flag win rule [PieceType] (default: *) +# flagPieceBlack: piece type for capture the flag win rule [PieceType] (default: *) +# flagRegion: target region for capture the flag win rule [Bitboard] (default: ) # flagRegionWhite: white's target region for capture the flag win rule [Bitboard] (default: ) # flagRegionBlack: black's target region for capture the flag win rule [Bitboard] (default: ) -# flagMove: black gets one more move after white captures the flag [bool] (default: false) -# checkCounting: enable check count win rule (check count is communicated via FEN, see 3check) [bool] (default: false) # flagPieceCount: number of flag pieces that have to be in the flag zone [int] (default: 1) -# flagPieceBlockedWin: for flagPieceCount > 1. if at least one piece in flag zone and all others occupied by opponent pieces, win. [bool] (default: false) +# flagPieceBlockedWin: for flagPieceCount > 1, win if at least one flag piece in flag zone and all others occupied by pieces [bool] (default: false) +# flagMove: the other side gets one more move after one reaches the flag zone [bool] (default: false) +# checkCounting: enable check count win rule (check count is communicated via FEN, see 3check) [bool] (default: false) # connectN: number of aligned pieces for win [int] (default: 0) # materialCounting: enable material counting rules [MaterialCounting] (default: none) # countingRule: enable counting rules [CountingRule] (default: none) @@ -310,8 +313,7 @@ blastOnCapture = true [atomic-giveaway-hill:giveaway] blastOnCapture = true flagPiece = k -flagRegionWhite = d4 e4 d5 e5 -flagRegionBlack = d4 e4 d5 e5 +flagRegion = d4 e4 d5 e5 # Crazyhouse with mandatory captures, using crazyhouse as a template [coffeehouse:crazyhouse] diff --git a/tests/perft.sh b/tests/perft.sh index 8111ee7..718e072 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -186,6 +186,7 @@ if [[ $1 == "all" || $1 == "largeboard" ]]; then expect perft.exp janggi "fen 1n1kaabn1/cr2N4/5C1c1/p1pNp3p/9/9/P1PbP1P1P/3r1p3/4A4/R1BA1KB1R b - - 0 1" 4 76763 > /dev/null expect perft.exp janggi "fen 1Pbcka3/3nNn1c1/N2CaC3/1pB6/9/9/5P3/9/4K4/9 w - - 0 23" 4 151202 > /dev/null expect perft.exp jesonmor startpos 3 27960 > /dev/null + expect perft.exp jesonmor "fen nn1nnn1nn/9/3n1n3/9/9/9/3N1N3/9/NN1NNN1NN w - - 4 3" 3 37564 > /dev/null # non-chess expect perft.exp flipello10 startpos 7 55180 > /dev/null