From af71024a6fdc0e3c8c3c71d6fbb7087c03187469 Mon Sep 17 00:00:00 2001 From: Cab Date: Wed, 14 Feb 2024 10:15:20 +0300 Subject: [PATCH 1/4] Hostage chess prison. Add new capture type 'prison'. Prison for black playes contains white pieces. Before return piece back to board, player makes hostage exchange. Exchange rules are defened in array 'hostageExchange' of the variant. --- src/ffishjs.cpp | 2 +- src/movegen.cpp | 54 ++++++++++++++ src/movepick.cpp | 2 +- src/parser.cpp | 70 +++++++++++++++++- src/position.cpp | 187 ++++++++++++++++++++++++++++++++++++++--------- src/position.h | 85 ++++++++++++++++++--- src/psqt.cpp | 10 +-- src/pyffish.cpp | 2 +- src/types.h | 15 ++++ src/uci.cpp | 14 +++- src/uci.h | 1 + src/variant.cpp | 43 +++++++---- src/variant.h | 4 +- src/variants.ini | 37 +++++----- 14 files changed, 435 insertions(+), 91 deletions(-) diff --git a/src/ffishjs.cpp b/src/ffishjs.cpp index 7e4f26dc0..b659a01af 100644 --- a/src/ffishjs.cpp +++ b/src/ffishjs.cpp @@ -491,7 +491,7 @@ namespace ffish { bool captures_to_hand(std::string uciVariant) { const Variant* v = get_variant(uciVariant); - return v->capturesToHand; + return v->captureType != OUT; } std::string starting_fen(std::string uciVariant) { diff --git a/src/movegen.cpp b/src/movegen.cpp index 43807bb0b..1f48f2255 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -92,6 +92,9 @@ namespace { for (PieceSet promotions = pos.promotion_piece_types(c); promotions;) { PieceType pt = pop_msb(promotions); + if (pos.prison_pawn_promotion() && pos.count_in_prison(~c, pt) == 0) { + continue; + } if (!pos.promotion_limit(pt) || pos.promotion_limit(pt) > pos.count(c, pt)) moveList = make_move_and_gating(pos, moveList, pos.side_to_move(), to - D, to, pt); } @@ -130,6 +133,50 @@ namespace { return moveList; } + template + ExtMove* generate_exchanges(const Position& pos, ExtMove* moveList, PieceType pt, Bitboard b) { + assert(Type != CAPTURES); + static_assert(SQUARE_BITS >= PIECE_TYPE_BITS, "not enough bits for exchange move"); + Color opp = ~Us; + if (pos.count_in_prison(opp, pt) > 0) { + PieceSet rescue = NO_PIECE_SET; + for (PieceSet r = pos.rescueFor(pt); r; ) { + PieceType ex = pop_lsb(r); + if (pos.count_in_prison(Us, ex) > 0) { + rescue |= ex; + } + } + if (rescue == NO_PIECE_SET) { + return moveList; + } + // Restrict to valid target + b &= pos.drop_region(Us, pt); + // Add to move list + if (pos.drop_promoted() && pos.promoted_piece_type(pt)) { + Bitboard b2 = b; + if (Type == QUIET_CHECKS) + b2 &= pos.check_squares(pos.promoted_piece_type(pt)); + while (b2) { + auto to = pop_lsb(b2); + for (PieceSet r = rescue; r; ) { + PieceType ex = pop_lsb(r); + *moveList++ = make_exchange(to, ex, pt, pos.promoted_piece_type(pt)); + } + } + } + if (Type == QUIET_CHECKS) + b &= pos.check_squares(pt); + while (b) { + auto to = pop_lsb(b); + for (PieceSet r = rescue; r; ) { + PieceType ex = pop_lsb(r); + *moveList++ = make_exchange(to, ex, pt, pt); + } + } + } + return moveList; + } + template ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { @@ -356,6 +403,9 @@ namespace { for (PieceSet ps = pos.promotion_piece_types(Us); ps;) { PieceType ptP = pop_msb(ps); + if (pos.prison_pawn_promotion() && pos.count_in_prison(~Us, ptP) == 0) { + continue; + } if (!pos.promotion_limit(ptP) || pos.promotion_limit(ptP) > pos.count(Us, ptP)) for (Bitboard promotions = pawnPromotions; promotions; ) moveList = make_move_and_gating(pos, moveList, pos.side_to_move(), from, pop_lsb(promotions), ptP); @@ -408,6 +458,10 @@ namespace { if (pos.piece_drops() && Type != CAPTURES && (pos.can_drop(Us, ALL_PIECES) || pos.two_boards())) for (PieceSet ps = pos.piece_types(); ps;) moveList = generate_drops(pos, moveList, pop_lsb(ps), target & ~pos.pieces(~Us)); + // generate exchange + if (pos.capture_type() == PRISON && Type != CAPTURES && pos.has_exchange()) + for (PieceSet ps = pos.piece_types(); ps;) + moveList = generate_exchanges(pos, moveList, pop_lsb(ps), target & ~pos.pieces(~Us)); // Castling with non-king piece if (!pos.count(Us) && Type != CAPTURES && pos.can_castle(Us & ANY_CASTLING)) diff --git a/src/movepick.cpp b/src/movepick.cpp index eac77e8aa..36002be41 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -165,7 +165,7 @@ Move MovePicker::next_move(bool skipQuiets) { case QSEARCH_TT: case PROBCUT_TT: ++stage; - assert(pos.legal(ttMove) == MoveList(pos).contains(ttMove) || pos.virtual_drop(ttMove)); + assert(pos.legal(ttMove) == MoveList(pos).contains(ttMove) || pos.virtual_drop(ttMove) || exchange_piece(ttMove)); return ttMove; case CAPTURE_INIT: diff --git a/src/parser.cpp b/src/parser.cpp index 9d2b55b98..f3af3e282 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -78,6 +78,14 @@ namespace { return value == "win" || value == "loss" || value == "draw" || value == "none"; } + template <> bool set(const std::string& value, CapturingRule& target) { + target = value == "out" ? OUT + : value == "hand" ? HAND + : value == "prison" ? PRISON + : OUT; + return value == "out" || value == "hand" || value == "prison"; + } + template <> bool set(const std::string& value, MaterialCounting& target) { target = value == "janggi" ? JANGGI_MATERIAL : value == "unweighted" ? UNWEIGHTED_MATERIAL @@ -187,6 +195,54 @@ namespace { target |= pt; } + void parse_hostage_exchanges(Variant *v, std::string &map, bool DoCheck) { + bool readPiece = true; + size_t idx = -1; + PieceSet mask = NO_PIECE_SET; + for (int i = 0; i < map.size(); ++i) { + char token = map[i]; + if (token == ' ') { + if (!readPiece) { + if (idx >= 0) { + v->hostageExchange[idx] = mask; + } + readPiece = true; + } + continue; + } + if (readPiece) { + mask = NO_PIECE_SET; + idx = v->pieceToChar.find(toupper(token)); + if (idx == std::string::npos) { + if (DoCheck) { + std::cerr << "hostageExchange - Invalid piece type: " << token << std::endl; + } + return; + } + readPiece = false; + } else if (token == ':') { + if (mask != NO_PIECE_SET) { + if (DoCheck) { + std::cerr << "hostageExchange - Invalid syntax: " << map << std::endl; + } + return; + } + } else { + size_t idx2 = v->pieceToChar.find(toupper(token)); + if (idx2 == std::string::npos) { + if (DoCheck) { + std::cerr << "hostageExchange - Invalid hostage piece type: " << token << std::endl; + } + return; + } + mask = mask | PieceType(idx2); + } + } + if (idx >= 0) { + v->hostageExchange[idx] = mask; + } + } + } // namespace template @@ -207,6 +263,7 @@ template bool VariantParser::parse_attribute(co : std::is_same() ? "MaterialCounting" : std::is_same() ? "CountingRule" : std::is_same() ? "ChasingRule" + : std::is_same() ? "CapturingRule" : std::is_same() ? "EnclosingRule" : std::is_same() ? "Bitboard" : std::is_same() ? "CastlingRights" @@ -453,7 +510,18 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("mustDropType", v->mustDropType, v->pieceToChar); parse_attribute("pieceDrops", v->pieceDrops); parse_attribute("dropLoop", v->dropLoop); - parse_attribute("capturesToHand", v->capturesToHand); + + bool capturesToHand = false; + parse_attribute("capturesToHand", capturesToHand); + if (capturesToHand) v->captureType = HAND; + + parse_attribute("captureType", v->captureType); + // hostage price + const auto& it_host_p = config.find("hostageExchange"); + if (it_host_p != config.end()) { + parse_hostage_exchanges(v, it_host_p->second, DoCheck); + } + parse_attribute("prisonPawnPromotion", v->prisonPawnPromotion); parse_attribute("firstRankPawnDrops", v->firstRankPawnDrops); parse_attribute("promotionZonePawnDrops", v->promotionZonePawnDrops); parse_attribute("enclosingDrop", v->enclosingDrop); diff --git a/src/position.cpp b/src/position.cpp index bf6e60f28..c322753c6 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -322,14 +322,23 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, } } // Pieces in hand - if (!isspace(token)) - while ((ss >> token) && !isspace(token)) - { - if (token == ']') + if (!isspace(token)) { + bool prison = false; + while ((ss >> token) && !isspace(token)) { + if (token == ']') { continue; - else if ((idx = piece_to_char().find(token)) != string::npos) - add_to_hand(Piece(idx)); + } else if (token == '#') { + prison = true; + continue; + } else if ((idx = piece_to_char().find(token)) != string::npos) { + if (prison) { + add_to_prison(Piece(idx)); + } else { + add_to_hand(Piece(idx)); + } + } } + } // 2. Active color ss >> token; @@ -521,6 +530,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, chess960 = isChess960 || v->chess960; tsumeMode = Options["TsumeMode"]; thisThread = th; + updatePawnCheckZone(); set_state(st); assert(pos_is_ok()); @@ -736,15 +746,27 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string if (!variant()->freeDrops && (piece_drops() || seirawan_gating())) { ss << '['; - if (holdings != "-") + if (holdings != "-") { ss << holdings; - else - for (Color c : {WHITE, BLACK}) - for (PieceType pt = KING; pt >= PAWN; --pt) - { + } else { + for (Color c: {WHITE, BLACK}) + for (PieceType pt = KING; pt >= PAWN; --pt) { assert(pieceCountInHand[c][pt] >= 0); ss << std::string(pieceCountInHand[c][pt], piece_to_char()[make_piece(c, pt)]); } + if (capture_type() == PRISON && + (count_in_prison(WHITE, ALL_PIECES) > 0 || count_in_prison(BLACK, ALL_PIECES) > 0)) { + ss << '#'; + for (Color c: {BLACK, WHITE}) + for (PieceType pt = KING; pt >= PAWN; --pt) { + assert(pieceCountInPrison[c][pt] >= 0); + int n = pieceCountInPrison[c][pt]; + if (n > 0) { + ss << std::string(n, piece_to_char()[make_piece(~c, pt)]); + } + } + } + } ss << ']'; } @@ -915,7 +937,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c, Bitboard j // Use a faster version for variants with moderate rule variations if (var->fastAttacks) { - return (pawn_attacks_bb(~c, s) & pieces(c, PAWN)) + return (pawn_attacks_bb(~c, s) & pieces(c, PAWN) & ~pawnCannotCheckZone[c]) | (attacks_bb(s) & pieces(c, KNIGHT, ARCHBISHOP, CHANCELLOR)) | (attacks_bb< ROOK>(s, occupied) & pieces(c, ROOK, QUEEN, CHANCELLOR)) | (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN, ARCHBISHOP)) @@ -1292,7 +1314,11 @@ bool Position::pseudo_legal(const Move m) const { return piece_drops() && pc != NO_PIECE && color_of(pc) == us - && (can_drop(us, in_hand_piece_type(m)) || (two_boards() && allow_virtual_drop(us, type_of(pc)))) + && (can_drop(us, in_hand_piece_type(m)) + || (two_boards() && allow_virtual_drop(us, type_of(pc))) + || (capture_type() == PRISON && exchange_piece(m) != NO_PIECE_TYPE + && count_in_prison(us, exchange_piece(m)) > 0 + && count_in_prison(~us, in_hand_piece_type(m)) > 0)) && (drop_region(us, type_of(pc)) & ~pieces() & to) && ( type_of(pc) == in_hand_piece_type(m) || (drop_promoted() && type_of(pc) == promoted_piece_type(in_hand_piece_type(m)))); @@ -1555,6 +1581,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Square to = to_sq(m); Piece pc = moved_piece(m); Piece captured = piece_on(type_of(m) == EN_PASSANT ? capture_square(to) : to); + PieceType exchanged = exchange_piece(m); if (to == from) { assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass(us))); @@ -1619,7 +1646,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if (type_of(m) == EN_PASSANT) board[capsq] = NO_PIECE; - if (captures_to_hand()) + if (capture_type() == HAND) { Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured : unpromotedCaptured ? ~unpromotedCaptured @@ -1634,6 +1661,17 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { dp.handCount[1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]; } } + else if (capture_type() == PRISON) + { + Piece pieceToPrison = !capturedPromoted || drop_loop() + ? captured + : unpromotedCaptured + ? unpromotedCaptured + : make_piece(color_of(captured), promotion_pawn_type(color_of(captured))); + int n = add_to_prison(pieceToPrison); + k ^= Zobrist::inHand[pieceToPrison][n - 1] + ^ Zobrist::inHand[pieceToPrison][n]; + } else if (Eval::useNNUE) dp.handPiece[1] = NO_PIECE; @@ -1651,9 +1689,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if (type_of(m) == DROP) { Piece pc_hand = make_piece(us, in_hand_piece_type(m)); + // exchanging means that drop is not from hand (but from prison) + int n = pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] + (exchanged != NO_PIECE_TYPE); k ^= Zobrist::psq[pc][to] - ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1] - ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]]; + ^ Zobrist::inHand[pc_hand][n - 1] + ^ Zobrist::inHand[pc_hand][n]; // Reset rule 50 counter for irreversible drops st->rule50 = 0; @@ -1746,7 +1786,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { dp.to[0] = to; } - drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to); + drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to, exchanged); st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1]; if (type_of(pc) != PAWN) st->nonPawnMaterial[us] += PieceValue[MG][pc]; @@ -1802,6 +1842,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->promotionPawn = piece_on(to); remove_piece(to); put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE); + if (prison_pawn_promotion() && type_of(m) == PROMOTION) { + add_to_prison(st->promotionPawn); + remove_from_prison(promotion); + } if (Eval::useNNUE) { @@ -2009,9 +2053,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Piece pieceToHand = !capturedPromoted || drop_loop() ? ~bpc : unpromotedCaptured ? ~unpromotedCaptured : make_piece(~color_of(bpc), PAWN); - add_to_hand(pieceToHand); - k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1] - ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]]; + int n; + if (capture_type() == PRISON) { + pieceToHand = ~pieceToHand; + n = add_to_prison(pieceToHand); + } else { + add_to_hand(pieceToHand); + n = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]; + } + k ^= Zobrist::inHand[pieceToHand][n - 1] + ^ Zobrist::inHand[pieceToHand][n]; if (Eval::useNNUE) { @@ -2061,11 +2112,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { k ^= Zobrist::wall[gating_square(m)]; } + updatePawnCheckZone(); // 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); - assert(givesCheck == bool(st->checkersBB)); + assert(givesCheck == bool(st->checkersBB) || givesCheck && var->prisonPawnPromotion); sideToMove = ~sideToMove; @@ -2123,6 +2175,7 @@ void Position::undo_move(Move m) { Square from = from_sq(m); Square to = to_sq(m); Piece pc = piece_on(to); + PieceType exchange = exchange_piece(m); assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) || (type_of(m) == PROMOTION && sittuyin_promotion()) @@ -2148,9 +2201,15 @@ void Position::undo_move(Move m) { if (bpc) { put_piece(bpc, bsq, isPromoted, st->demotedBycatch & bsq ? unpromotedBpc : NO_PIECE); - if (captures_to_hand()) - remove_from_hand(!drop_loop() && (st->promotedBycatch & bsq) ? make_piece(~color_of(unpromotedBpc), PAWN) - : ~unpromotedBpc); + if (capture_type() == HAND) { + remove_from_hand(!drop_loop() && (st->promotedBycatch & bsq) + ? make_piece(~color_of(unpromotedBpc), PAWN) + : ~unpromotedBpc); + } else if (capture_type() == PRISON) { + remove_from_prison(!drop_loop() && (st->promotedBycatch & bsq) + ? make_piece(color_of(unpromotedBpc), PAWN) + : unpromotedBpc); + } } } // Reset piece since it exploded itself @@ -2174,6 +2233,10 @@ void Position::undo_move(Move m) { assert(type_of(pc) >= KNIGHT && type_of(pc) < KING); assert(type_of(st->promotionPawn) == promotion_pawn_type(us) || !captures_to_hand()); + if (prison_pawn_promotion() && type_of(st->promotionPawn) == PAWN) { + remove_from_prison(st->promotionPawn); + add_to_prison(pc); + } remove_piece(to); pc = st->promotionPawn; put_piece(pc, to); @@ -2201,7 +2264,7 @@ void Position::undo_move(Move m) { else { if (type_of(m) == DROP) - undrop_piece(make_piece(us, in_hand_piece_type(m)), to); // Remove the dropped piece + undrop_piece(make_piece(us, in_hand_piece_type(m)), to, exchange); // Remove the dropped piece else move_piece(to, from); // Put the piece back at the source square @@ -2219,10 +2282,19 @@ void Position::undo_move(Move m) { } put_piece(st->capturedPiece, capsq, st->capturedpromoted, st->unpromotedCapturedPiece); // Restore the captured piece - if (captures_to_hand()) - remove_from_hand(!drop_loop() && st->capturedpromoted ? (st->unpromotedCapturedPiece ? ~st->unpromotedCapturedPiece - : make_piece(~color_of(st->capturedPiece), promotion_pawn_type(us))) - : ~st->capturedPiece); + if (capture_type() == HAND) { + remove_from_hand(!drop_loop() && st->capturedpromoted + ? (st->unpromotedCapturedPiece + ? ~st->unpromotedCapturedPiece + : make_piece(~color_of(st->capturedPiece), promotion_pawn_type(us))) + : ~st->capturedPiece); + } else if (capture_type() == PRISON) { + remove_from_prison(!drop_loop() && st->capturedpromoted + ? (st->unpromotedCapturedPiece + ? st->unpromotedCapturedPiece + : make_piece(color_of(st->capturedPiece), promotion_pawn_type(us))) + : st->capturedPiece); + } } } @@ -2344,18 +2416,29 @@ Key Position::key_after(Move m) const { if (captured) { k ^= Zobrist::psq[captured][to]; - if (captures_to_hand()) - { - Piece removeFromHand = !drop_loop() && is_promoted(to) ? make_piece(~color_of(captured), promotion_pawn_type(color_of(captured))) : ~captured; - k ^= Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)] + 1] - ^ Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)]]; + if (captures_to_hand()) { + Piece removedPiece = !drop_loop() && is_promoted(to) + ? make_piece(~color_of(captured), promotion_pawn_type(color_of(captured))) + : ~captured; + int n; + if (capture_type() == HAND) { + n = pieceCountInHand[color_of(removedPiece)][type_of(removedPiece)]; + } else { + n = pieceCountInPrison[color_of(removedPiece)][type_of(removedPiece)]; + removedPiece = ~removedPiece; + } + k ^= Zobrist::inHand[removedPiece][n + 1] + ^ Zobrist::inHand[removedPiece][n]; } } if (type_of(m) == DROP) { Piece pc_hand = make_piece(sideToMove, in_hand_piece_type(m)); - return k ^ Zobrist::psq[pc][to] ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]] - ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1]; + PieceType exchanged = exchange_piece(m); + int n = pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] + (exchanged != NO_PIECE_TYPE); + return k ^ Zobrist::psq[pc][to] + ^ Zobrist::inHand[pc_hand][n] + ^ Zobrist::inHand[pc_hand][n - 1]; } return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; @@ -2873,6 +2956,13 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { return true; } } + if (var->prisonPawnPromotion && + (pawn_attacks_bb(~sideToMove, square(~sideToMove)) + & pieces(sideToMove, PAWN) + & ~pawnCannotCheckZone[sideToMove]) ){ + result = mate_in(ply); + return true; + } return false; } @@ -3171,6 +3261,31 @@ void Position::flip() { assert(pos_is_ok()); } +void Position::updatePawnCheckZone() { + if (!var->prisonPawnPromotion) { + pawnCannotCheckZone[WHITE] = Bitboard(0); + pawnCannotCheckZone[BLACK] = Bitboard(0); + return; + } + for (Color color : { BLACK, WHITE }) { + if (count(~color) == 0) { + pawnCannotCheckZone[color] = Bitboard(0); + } else { + bool canPromotion = false; + for (PieceSet prom = promotion_piece_types(color) & rescueFor(PAWN); prom; ) { + PieceType pt = pop_lsb(prom); + if (count_in_prison(~color, pt) > 0) { + canPromotion = true; + break; + } + } + Bitboard pz = promotion_zone(color); + pawnCannotCheckZone[color] = canPromotion + ? Bitboard(0) + : color == WHITE ? shift(SOUTH, pz) : shift(NORTH, pz); + } + } +} /// Position::pos_is_ok() performs some consistency checks for the /// position object and raises an asserts if something wrong is detected. diff --git a/src/position.h b/src/position.h index 4ee936657..ef803ebe7 100644 --- a/src/position.h +++ b/src/position.h @@ -168,6 +168,9 @@ class Position { bool captures_to_hand() const; bool first_rank_pawn_drops() const; bool can_drop(Color c, PieceType pt) const; + bool has_exchange() const; + PieceSet rescueFor(PieceType pt) const; + CapturingRule capture_type() const; EnclosingRule enclosing_drop() const; Bitboard drop_region(Color c) const; Bitboard drop_region(Color c, PieceType pt) const; @@ -219,6 +222,8 @@ class Position { int count_in_hand(PieceType pt) const; int count_in_hand(Color c, PieceType pt) const; int count_with_hand(Color c, PieceType pt) const; + int count_in_prison(Color c, PieceType pt) const; + bool prison_pawn_promotion() const; bool bikjang() const; bool allow_virtual_drop(Color c, PieceType pt) const; @@ -367,12 +372,17 @@ class Position { bool tsumeMode; bool chess960; int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB]; + int pieceCountInPrison[COLOR_NB][PIECE_TYPE_NB]; + Bitboard pawnCannotCheckZone[COLOR_NB]; int virtualPieces; Bitboard promotedPieces; void add_to_hand(Piece pc); void remove_from_hand(Piece pc); - void drop_piece(Piece pc_hand, Piece pc_drop, Square s); - void undrop_piece(Piece pc_hand, Square s); + int add_to_prison(Piece pc); + int remove_from_prison(Piece pc); + void updatePawnCheckZone(); + void drop_piece(Piece pc_hand, Piece pc_drop, Square s, PieceType exchange); + void undrop_piece(Piece pc_hand, Square s, PieceType exchange); Bitboard find_drop_region(Direction dir, Square s, Bitboard occupied) const; }; @@ -639,9 +649,14 @@ inline bool Position::drop_loop() const { return var->dropLoop; } +inline CapturingRule Position::capture_type() const { + assert(var != nullptr); + return var->captureType; +} + inline bool Position::captures_to_hand() const { assert(var != nullptr); - return var->capturesToHand; + return var->captureType != OUT; } inline bool Position::first_rank_pawn_drops() const { @@ -1391,7 +1406,7 @@ inline Square Position::capture_square(Square to) const { inline bool Position::virtual_drop(Move m) const { assert(is_ok(m)); - return type_of(m) == DROP && !can_drop(side_to_move(), in_hand_piece_type(m)); + return type_of(m) == DROP && !can_drop(side_to_move(), in_hand_piece_type(m)) && exchange_piece(m) == NO_PIECE_TYPE; } inline Piece Position::captured_piece() const { @@ -1468,6 +1483,14 @@ inline int Position::count_with_hand(Color c, PieceType pt) const { return pieceCount[make_piece(c, pt)] + pieceCountInHand[c][pt]; } +inline int Position::count_in_prison(Color c, PieceType pt) const { + return pieceCountInPrison[c][pt]; +} + +inline bool Position::prison_pawn_promotion() const { + return var->prisonPawnPromotion; +} + inline bool Position::bikjang() const { return st->bikjang; } @@ -1530,25 +1553,63 @@ inline void Position::remove_from_hand(Piece pc) { psq -= PSQT::psq[pc][SQ_NONE]; } -inline void Position::drop_piece(Piece pc_hand, Piece pc_drop, Square s) { - assert(can_drop(color_of(pc_hand), type_of(pc_hand)) || var->twoBoards); +inline int Position::add_to_prison(Piece pc) { + if (variant()->captureType != PRISON) return 0; + Color prison = ~color_of(pc); + int n = ++pieceCountInPrison[prison][type_of(pc)]; + pieceCountInPrison[prison][ALL_PIECES]++; + return n; +} + +inline int Position::remove_from_prison(Piece pc) { + if (variant()->captureType != PRISON) return 0; + Color prison = ~color_of(pc); + int n = --pieceCountInPrison[prison][type_of(pc)]; + pieceCountInPrison[prison][ALL_PIECES]--; + return n; +} + +inline void Position::drop_piece(Piece pc_hand, Piece pc_drop, Square s, PieceType exchange) { + assert(can_drop(color_of(pc_hand), type_of(pc_hand)) || var->twoBoards || exchange != NO_PIECE_TYPE); put_piece(pc_drop, s, pc_drop != pc_hand, pc_drop != pc_hand ? pc_hand : NO_PIECE); - remove_from_hand(pc_hand); - virtualPieces += (pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] < 0); + if (exchange) { + Piece ex = make_piece(~sideToMove, exchange); + add_to_hand(ex); + remove_from_prison(ex); + remove_from_prison(pc_drop); + } else { + remove_from_hand(pc_hand); + virtualPieces += (pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] < 0); + } } -inline void Position::undrop_piece(Piece pc_hand, Square s) { - virtualPieces -= (pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] < 0); +inline void Position::undrop_piece(Piece pc_hand, Square s, PieceType exchange) { remove_piece(s); board[s] = NO_PIECE; - add_to_hand(pc_hand); - assert(can_drop(color_of(pc_hand), type_of(pc_hand)) || var->twoBoards); + if (exchange) { + Piece ex = make_piece(~sideToMove, exchange); + remove_from_hand(ex); + add_to_prison(ex); + add_to_prison(pc_hand); + } else { + virtualPieces -= (pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] < 0); + add_to_hand(pc_hand); + } + assert(can_drop(color_of(pc_hand), type_of(pc_hand)) || var->twoBoards || exchange != NO_PIECE_TYPE); } inline bool Position::can_drop(Color c, PieceType pt) const { return variant()->freeDrops || count_in_hand(c, pt) > 0; } +inline bool Position::has_exchange() const { + return count_in_prison(WHITE, ALL_PIECES) > 0 && count_in_prison(BLACK, ALL_PIECES) > 0; +} + +inline PieceSet Position::rescueFor(PieceType pt) const { + return var->hostageExchange[pt]; +} + } // namespace Stockfish #endif // #ifndef POSITION_H_INCLUDED diff --git a/src/psqt.cpp b/src/psqt.cpp index f5e3b6264..21418871f 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -225,7 +225,7 @@ void init(const Variant* v) { constexpr int lc = 5; constexpr int rm = 5; constexpr int r0 = rm + RANK_8; - int r1 = rm + (v->maxRank + v->maxFile - 2 * v->capturesToHand) / 2; + int r1 = rm + (v->maxRank + v->maxFile - 2 * (v->captureType != OUT)) / 2; int leaper = pi->steps[0][MODALITY_QUIET].size() + pi->steps[0][MODALITY_CAPTURE].size(); int slider = pi->slider[0][MODALITY_QUIET].size() + pi->slider[0][MODALITY_CAPTURE].size() + pi->hopper[0][MODALITY_QUIET].size() + pi->hopper[0][MODALITY_CAPTURE].size(); score = make_score(mg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider), @@ -233,7 +233,7 @@ void init(const Variant* v) { } // Piece values saturate earlier in drop variants - if (v->capturesToHand || v->twoBoards) + if (v->captureType != OUT || v->twoBoards) score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)), eg_value(score) * 7000 / (7000 + eg_value(score))); @@ -267,7 +267,7 @@ void init(const Variant* v) { // The strongest piece of a variant usually has some dominance, such as rooks in Makruk and Xiangqi. // This does not apply to drop variants. - if (pt == strongestPiece && !v->capturesToHand) + if (pt == strongestPiece && v->captureType == OUT) score += make_score(std::max(QueenValueMg - PieceValue[MG][pt], VALUE_ZERO) / 20, std::max(QueenValueEg - PieceValue[EG][pt], VALUE_ZERO) / 20); @@ -285,7 +285,7 @@ void init(const Variant* v) { CapturePieceValue[EG][pc] = CapturePieceValue[EG][~pc] = eg_value(score); // For drop variants, halve the piece values to compensate for double changes by captures - if (v->capturesToHand) + if (v->captureType != OUT) score = score / 2; EvalPieceValue[MG][pc] = EvalPieceValue[MG][~pc] = mg_value(score); @@ -309,7 +309,7 @@ void init(const Variant* v) { File f = std::max(File(edge_distance(file_of(s), v->maxFile)), FILE_A); Rank r = rank_of(s); psq[ pc][s] = score + ( pt == PAWN ? PBonus[std::min(r, RANK_8)][std::min(file_of(s), FILE_H)] - : pt == KING ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + v->capturesToHand) + : pt == KING ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + (v->captureType != OUT)) : pt <= QUEEN ? Bonus[pc][std::min(r, RANK_8)][std::min(f, FILE_D)] * (1 + v->blastOnCapture) : pt == HORSE ? Bonus[KNIGHT][std::min(r, RANK_8)][std::min(f, FILE_D)] : pt == COMMONER && v->extinctionValue == -VALUE_MATE && (v->extinctionPieceTypes & COMMONER) ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] diff --git a/src/pyffish.cpp b/src/pyffish.cpp index d321a57cc..be3dae63f 100644 --- a/src/pyffish.cpp +++ b/src/pyffish.cpp @@ -137,7 +137,7 @@ extern "C" PyObject* pyffish_capturesToHand(PyObject* self, PyObject *args) { return NULL; } - return Py_BuildValue("O", variants.find(std::string(variant))->second->capturesToHand ? Py_True : Py_False); + return Py_BuildValue("O", variants.find(std::string(variant))->second->captureType != OUT ? Py_True : Py_False); } // INPUT variant, fen, move diff --git a/src/types.h b/src/types.h index 5abfac80b..1541885ed 100644 --- a/src/types.h +++ b/src/types.h @@ -309,6 +309,10 @@ enum WallingRule { NO_WALLING, ARROW, DUCK, EDGE, PAST, STATIC }; +enum CapturingRule { + OUT, HAND, PRISON +}; + enum OptBool { NO_VALUE, VALUE_FALSE, VALUE_TRUE }; @@ -822,6 +826,17 @@ 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); } +constexpr PieceType exchange_piece(Move m) { + return type_of(m) != DROP ? NO_PIECE_TYPE : PieceType((m >> SQUARE_BITS) & SQUARE_BIT_MASK); +} + +constexpr Move make_exchange(Square to, PieceType pt_exchange, 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)) + + (pt_exchange << SQUARE_BITS) + + DROP + to); +} + constexpr Move reverse_move(Move m) { return make_move(to_sq(m), from_sq(m)); } diff --git a/src/uci.cpp b/src/uci.cpp index 584977aad..4c6c05681 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -501,6 +501,14 @@ string UCI::dropped_piece(const Position& pos, Move m) { return std::string{pos.piece_to_char()[dropped_piece_type(m)]}; } +string UCI::exchange(const Position &pos, Move m) { + assert(type_of(m) == DROP); + if (exchange_piece(m) == NO_PIECE_TYPE) { + return std::string{}; + } + assert(pos.capture_type() == PRISON); + return std::string{'#', pos.piece_to_char()[exchange_piece(m)]}; +} /// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). /// The only special case is castling, where we print in the e1g1 notation in @@ -531,8 +539,10 @@ string UCI::move(const Position& pos, Move m) { to = to_sq(m); } - string move = (type_of(m) == DROP ? UCI::dropped_piece(pos, m) + (CurrentProtocol == USI ? '*' : '@') - : UCI::square(pos, from)) + UCI::square(pos, to); + string move = (type_of(m) == DROP + ? UCI::dropped_piece(pos, m) + UCI::exchange(pos, m) + (CurrentProtocol == USI ? '*' : '@') + : UCI::square(pos, from)) + + UCI::square(pos, to); // Wall square if (pos.walling() && CurrentProtocol == XBOARD) diff --git a/src/uci.h b/src/uci.h index ef22a5356..45cc932a8 100644 --- a/src/uci.h +++ b/src/uci.h @@ -87,6 +87,7 @@ void init(OptionsMap&); void loop(int argc, char* argv[]); std::string value(Value v); std::string square(const Position& pos, Square s); +std::string exchange(const Position& pos, Move m); std::string dropped_piece(const Position& pos, Move m); std::string move(const Position& pos, Move m); std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); diff --git a/src/variant.cpp b/src/variant.cpp index 386c21d5d..7adaa15b9 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -603,7 +603,7 @@ namespace { v->variantTemplate = "crazyhouse"; v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 0 1"; v->pieceDrops = true; - v->capturesToHand = true; + v->captureType = HAND; return v; } // Loop chess @@ -624,6 +624,20 @@ namespace { v->nnueAlias = "crazyhouse"; return v; } + // Almost hostage chess + // https://en.wikipedia.org/wiki/Hostage_chess + Variant* hostage_variant() { + Variant* v = loop_variant()->init(); + v->nnueAlias = ""; + v->captureType = PRISON; + v->prisonPawnPromotion = true; + v->hostageExchange[QUEEN] = piece_set(QUEEN); + v->hostageExchange[ROOK] = piece_set(ROOK) | QUEEN; + v->hostageExchange[KNIGHT] = piece_set(KNIGHT) | BISHOP | ROOK | QUEEN; + v->hostageExchange[BISHOP] = piece_set(KNIGHT) | BISHOP | ROOK | QUEEN; + v->hostageExchange[PAWN] = piece_set(PAWN) | KNIGHT | BISHOP | ROOK | QUEEN; + return v; + } // Bughouse // A four player variant where captured pieces are introduced on the other board // https://en.wikipedia.org/wiki/Bughouse_chess @@ -631,7 +645,7 @@ namespace { Variant* v = crazyhouse_variant()->init(); v->variantTemplate = "bughouse"; v->twoBoards = true; - v->capturesToHand = false; + v->captureType = OUT; v->stalemateValue = -VALUE_MATE; return v; } @@ -658,7 +672,7 @@ namespace { v->pocketSize = 2; v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Nn] w KQkq - 0 1"; v->pieceDrops = true; - v->capturesToHand = false; + v->captureType = OUT; return v; } // Placement/Pre-chess @@ -670,7 +684,7 @@ namespace { v->startFen = "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1"; v->mustDrop = true; v->pieceDrops = true; - v->capturesToHand = false; + v->captureType = OUT; v->whiteDropRegion = Rank1BB; v->blackDropRegion = Rank8BB; v->dropOppositeColoredBishop = true; @@ -689,7 +703,7 @@ namespace { v->add_piece(MET, 'f'); v->mustDrop = true; v->pieceDrops = true; - v->capturesToHand = false; + v->captureType = OUT; v->whiteDropRegion = Rank1BB | Rank2BB | Rank3BB; v->blackDropRegion = Rank8BB | Rank7BB | Rank6BB; v->sittuyinRookDrop = true; @@ -725,7 +739,7 @@ namespace { Variant* v = seirawan_variant()->init(); v->variantTemplate = "crazyhouse"; v->pieceDrops = true; - v->capturesToHand = true; + v->captureType = HAND; return v; } // Dragon Chess @@ -741,7 +755,7 @@ namespace { v->add_piece(ARCHBISHOP, 'd'); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Dd] w KQkq - 0 1"; v->pieceDrops = true; - v->capturesToHand = false; + v->captureType = OUT; v->whiteDropRegion = Rank1BB; v->blackDropRegion = Rank8BB; return v; @@ -774,7 +788,7 @@ namespace { v->add_piece(KING, 'k'); v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1"; v->pieceDrops = true; - v->capturesToHand = true; + v->captureType = HAND; v->promotionRegion[WHITE] = Rank5BB; v->promotionRegion[BLACK] = Rank1BB; v->doubleStep = false; @@ -924,7 +938,7 @@ namespace { v->add_piece(CUSTOM_PIECE_7, 'e', "KbRfBbF2"); // eagle v->startFen = "rpckcpl/3f3/sssssss/2s1S2/SSSSSSS/3F3/LPCKCPR[-] w 0 1"; v->pieceDrops = true; - v->capturesToHand = true; + v->captureType = HAND; v->promotionRegion[WHITE] = Rank6BB | Rank7BB; v->promotionRegion[BLACK] = Rank2BB | Rank1BB; v->doubleStep = false; @@ -1232,7 +1246,7 @@ namespace { v->add_piece(COMMONER, 'k'); v->add_piece(CUSTOM_PIECE_1, 'e', "FsfW"); // drunk elephant v->startFen = "lnsgkgsnl/1r2e2b1/ppppppppp/9/9/9/PPPPPPPPP/1B2E2R1/LNSGKGSNL w 0 1"; - v->capturesToHand = false; + v->captureType = OUT; v->pieceDrops = false; v->promotedPieceType[CUSTOM_PIECE_1] = COMMONER; v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; @@ -1267,7 +1281,7 @@ namespace { v->promotedPieceType[CUSTOM_PIECE_2] = CUSTOM_PIECE_4; v->promotedPieceType[CUSTOM_PIECE_3] = ROOK; v->pieceDrops = true; - v->capturesToHand = true; + v->captureType = HAND; v->doubleStep = false; v->castling = false; v->dropNoDoubled = SHOGI_PAWN; @@ -1318,7 +1332,7 @@ namespace { Variant* v = capablanca_variant()->init(); v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR[] w KQkq - 0 1"; v->pieceDrops = true; - v->capturesToHand = true; + v->captureType = HAND; return v; } // Capablanca random chess (CRC) @@ -1843,6 +1857,7 @@ void VariantMap::init() { add("crazyhouse", crazyhouse_variant()); add("loop", loop_variant()); add("chessgi", chessgi_variant()); + add("hostage", hostage_variant()); add("bughouse", bughouse_variant()); add("koedem", koedem_variant()); add("pocketknight", pocketknight_variant()); @@ -1967,7 +1982,7 @@ Variant* Variant::conclude() { } // We can not use popcount here yet, as the lookup tables are initialized after the variants int nnueSquares = (maxRank + 1) * (maxFile + 1); - nnueUsePockets = (pieceDrops && (capturesToHand || (!mustDrop && std::bitset<64>(pieceTypes).count() != 1))) || seirawanGating; + nnueUsePockets = (pieceDrops && (captureType == HAND || (!mustDrop && std::bitset<64>(pieceTypes).count() != 1))) || seirawanGating; int nnuePockets = nnueUsePockets ? 2 * int(maxFile + 1) : 0; int nnueNonDropPieceIndices = (2 * std::bitset<64>(pieceTypes).count() - (nnueKing != NO_PIECE_TYPE)) * nnueSquares; int nnuePieceIndices = nnueNonDropPieceIndices + 2 * (std::bitset<64>(pieceTypes).count() - (nnueKing != NO_PIECE_TYPE)) * nnuePockets; @@ -2036,7 +2051,7 @@ Variant* Variant::conclude() { && !connectN && !blastOnCapture && !petrifyOnCaptureTypes - && !capturesToHand + && captureType == OUT && !twoBoards && !restrictedMobility && kingType == KING; diff --git a/src/variant.h b/src/variant.h index ec1991859..b0660f828 100644 --- a/src/variant.h +++ b/src/variant.h @@ -92,7 +92,7 @@ struct Variant { PieceType mustDropType = ALL_PIECES; bool pieceDrops = false; bool dropLoop = false; - bool capturesToHand = false; + CapturingRule captureType = OUT; bool firstRankPawnDrops = false; bool promotionZonePawnDrops = false; EnclosingRule enclosingDrop = NO_ENCLOSING; @@ -104,6 +104,8 @@ struct Variant { bool dropPromoted = false; PieceType dropNoDoubled = NO_PIECE_TYPE; int dropNoDoubledCount = 1; + PieceSet hostageExchange[PIECE_TYPE_NB] = {}; + bool prisonPawnPromotion = false; bool immobilityIllegal = false; bool gating = false; WallingRule wallingRule = NO_WALLING; diff --git a/src/variants.ini b/src/variants.ini index abc2c7099..158069d3d 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -130,6 +130,7 @@ # [MaterialCounting]: material counting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] # [CountingRule]: makruk, cambodian, or ASEAN counting rules [makruk, cambodian, asean, none] # [ChasingRule]: xiangqi chasing rules [axf, none] +# [CapturingRule]: capturing rule [out, hand, prison] # [EnclosingRule]: reversi, ataxx, etc. enclosing rules [reversi, ataxx, quadwrangle, snort, anyside, top, none] # - in enclosingDrop: # - reversi: must enclose opponent's pieces between yours by Queen move @@ -214,7 +215,7 @@ # mustDropType: piece type for which piece drops are mandatory [PieceType] (default: *) # pieceDrops: enable piece drops [bool] (default: false) # dropLoop: captures promoted pieces are not demoted [bool] (default: false) -# capturesToHand: captured pieces go to opponent's hand [bool] (default: false) +# captureType: captured pieces are removed or go to hand or prison [CapturingRule] (default: out) # firstRankPawnDrops: allow pawn drops to first rank [bool] (default: false) # promotionZonePawnDrops: allow pawn drops in promotion zone [bool] (default: false) # dropOnTop: DEPRECATED, use "enclosingDrop = top" @@ -227,6 +228,8 @@ # dropPromoted: pieces may be dropped in promoted state [bool] (default: false) # dropNoDoubled: specified piece type can not be dropped to the same file (e.g. shogi pawn) [PieceType] (default: -) # dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [int] (default: 1) +# hostageExchange: mapping between hostage piece type and exchange types for it, e.g., p:pnbrq n:bnrq q:q (default: ) +# prisonPawnPromotion: pawn promote only into piece from prison by exchanging [bool] (default: false) # immobilityIllegal: pieces may not move to squares where they can never move from [bool] (default: false) # gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false) # wallingRule: rule on where wall can be placed [WallingRule] (default: none) @@ -311,7 +314,7 @@ # king = k # startFen = rbsgk/4p/5/P4/KGSBR[-] w 0 1 # pieceDrops = true -# capturesToHand = true +# captureType = hand # promotionRegionWhite = *5 # promotionRegionBlack = *1 # doubleStep = false @@ -358,7 +361,7 @@ mustCapture = true [makhouse:makruk] startFen = rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR[] w - - 0 1 pieceDrops = true -capturesToHand = true +captureType = hand firstRankPawnDrops = true promotionZonePawnDrops = true immobilityIllegal = true @@ -367,7 +370,7 @@ immobilityIllegal = true [xiangqihouse:xiangqi] startFen = rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1 pieceDrops = true -capturesToHand = true +captureType = hand dropChecks = false whiteDropRegion = *1 *2 *3 *4 *5 blackDropRegion = *6 *7 *8 *9 *10 @@ -382,7 +385,7 @@ mobilityRegionBlackSoldier = *1 *2 *3 *4 *5 a6 a7 c6 c7 e6 e7 g6 g7 i6 i7 [janggihouse:janggi] startFen = rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR[] w - - 0 1 pieceDrops = true -capturesToHand = true +captureType = hand # Hybrid variant of antichess and losalamos [anti-losalamos:losalamos] @@ -528,7 +531,7 @@ enclosingDropStart = c3 d3 c4 d4 [grandhouse:grand] startFen = r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1 pieceDrops = true -capturesToHand = true +captureType = hand [shogun:crazyhouse] variantTemplate = shogi @@ -616,7 +619,7 @@ flagRegionBlack = *1 [gothhouse:capablanca] startFen = rnbqckabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQCKABNR[] w KQkq - 0 1 pieceDrops = true -capturesToHand = true +captureType = hand # Synochess # https://www.pychess.org/variant/synochess @@ -632,7 +635,7 @@ stalemateValue = loss perpetualCheckIllegal = true startFen = rneakenr/8/1c4c1/1ss2ss1/8/8/PPPPPPPP/RNBQKBNR[ss] w KQ - 0 1 flyingGeneral = true -capturesToHand = false +captureType = out blackDropRegion = *5 flagPiece = k flagRegionWhite = *8 @@ -828,7 +831,7 @@ customPiece3 = u:D customPiece4 = w:DWF castling = false pieceDrops = true -capturesToHand = true +captureType = hand immobilityIllegal = true soldier = p knight = n @@ -891,7 +894,7 @@ maxRank = 9 pocketSize = 8 startFen = rnbakqcnm/9/ppppppppp/9/9/9/PPPPPPPPP/9/MNCQKABNR[] w - - 0 1 pieceDrops = true -capturesToHand = true +captureType = hand shogiPawn = p knight = n bishop = b @@ -951,7 +954,7 @@ extinctionOpponentPieceCount = 1 [atomiczh:atomic] dropChecks = false pieceDrops = true -capturesToHand = true +captureType = hand pocketSize = 6 castling = false @@ -1021,7 +1024,7 @@ promotionRegionWhite = *1 *2 *3 *4 *5 *6 *7 promotionRegionBlack = *7 *6 *5 *4 *3 *2 *1 startFen = 1fkm3/1p1s3/7/7/7/3S1P1/3MKF1[] w - 0 1 pieceDrops = true -capturesToHand = true +captureType = hand pieceDemotion = true mandatoryPiecePromotion = true dropPromoted = true @@ -1048,7 +1051,7 @@ pocketSize = 7 castlingKingsideFile = f castlingQueensideFile = b pieceDrops = true -capturesToHand = true +captureType = hand promotionRegionWhite = *7 promotionRegionBlack = *1 promotionPieceTypes = nbr @@ -1071,7 +1074,7 @@ extinctionPieceTypes = r maxRank = 7 maxFile = 7 pieceDrops = true -capturesToHand = true +captureType = hand stalemateValue = loss nFoldValue = loss extinctionValue = loss @@ -1117,7 +1120,7 @@ mandatoryPiecePromotion = true stalemateValue = loss perpetualCheckIllegal = true startFen = lh1ck1hl/pppppppp/8/8/8/8/PPPPPPPP/LH1CK1HL[LHMMDJlhmmdj] w - - 0 1 -capturesToHand = false +captureType = out whiteDropRegion = *1 *2 *3 *4 blackDropRegion = *5 *6 *7 *8 immobilityIllegal = true @@ -1163,7 +1166,7 @@ extinctionPseudoRoyal = false # Variant defined in Liantichess website. Credits to SriMethan for the definition. [antihouse:giveaway] pieceDrops = true -capturesToHand = true +captureType = hand pocketSize = 6 castling = false @@ -1785,7 +1788,7 @@ promotionRegionBlack = *7 *6 *5 *4 *3 *2 *1 mandatoryPiecePromotion = true pieceDemotion = true pieceDrops = true -capturesToHand = true +captureType = hand dropPromoted = true immobilityIllegal = false extinctionValue = loss From 990b47e37e267b09f33cf773e6caae5adfa628ad Mon Sep 17 00:00:00 2001 From: Cab Date: Mon, 19 Feb 2024 10:11:25 +0300 Subject: [PATCH 2/4] fix parse old capturesToHand --- src/parser.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index f3af3e282..ac7dcb33e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -512,8 +512,9 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("dropLoop", v->dropLoop); bool capturesToHand = false; - parse_attribute("capturesToHand", capturesToHand); - if (capturesToHand) v->captureType = HAND; + if (parse_attribute("capturesToHand", capturesToHand)) { + v->captureType = capturesToHand ? HAND : OUT; + } parse_attribute("captureType", v->captureType); // hostage price From 87d504894b01a37959696df495d5508258e7a673 Mon Sep 17 00:00:00 2001 From: cab Date: Thu, 7 Mar 2024 17:05:38 +0300 Subject: [PATCH 3/4] resolve warnings --- src/parser.cpp | 10 +++------- src/position.cpp | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index ac7dcb33e..83f9b0bc4 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -199,13 +199,11 @@ namespace { bool readPiece = true; size_t idx = -1; PieceSet mask = NO_PIECE_SET; - for (int i = 0; i < map.size(); ++i) { + for (size_t i = 0; i < map.size(); ++i) { char token = map[i]; if (token == ' ') { if (!readPiece) { - if (idx >= 0) { - v->hostageExchange[idx] = mask; - } + v->hostageExchange[idx] = mask; readPiece = true; } continue; @@ -238,9 +236,7 @@ namespace { mask = mask | PieceType(idx2); } } - if (idx >= 0) { - v->hostageExchange[idx] = mask; - } + v->hostageExchange[idx] = mask; } } // namespace diff --git a/src/position.cpp b/src/position.cpp index c322753c6..a0418f33c 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2117,7 +2117,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->key = k; // Calculate checkers bitboard (if move gives check) st->checkersBB = givesCheck ? attackers_to(square(them), us) & pieces(us) : Bitboard(0); - assert(givesCheck == bool(st->checkersBB) || givesCheck && var->prisonPawnPromotion); + assert(givesCheck == bool(st->checkersBB) || (givesCheck && var->prisonPawnPromotion)); sideToMove = ~sideToMove; From 0cf9ce01f1eaf71705079a81c670d0cea9772210 Mon Sep 17 00:00:00 2001 From: cab Date: Thu, 7 Mar 2024 18:06:42 +0300 Subject: [PATCH 4/4] OUT -> MOVE_OUT --- src/ffishjs.cpp | 2 +- src/parser.cpp | 6 +++--- src/position.h | 2 +- src/psqt.cpp | 10 +++++----- src/pyffish.cpp | 2 +- src/types.h | 2 +- src/variant.cpp | 14 +++++++------- src/variant.h | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ffishjs.cpp b/src/ffishjs.cpp index b659a01af..cc79f5535 100644 --- a/src/ffishjs.cpp +++ b/src/ffishjs.cpp @@ -491,7 +491,7 @@ namespace ffish { bool captures_to_hand(std::string uciVariant) { const Variant* v = get_variant(uciVariant); - return v->captureType != OUT; + return v->captureType != MOVE_OUT; } std::string starting_fen(std::string uciVariant) { diff --git a/src/parser.cpp b/src/parser.cpp index 83f9b0bc4..8f6bcf09b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -79,10 +79,10 @@ namespace { } template <> bool set(const std::string& value, CapturingRule& target) { - target = value == "out" ? OUT + target = value == "out" ? MOVE_OUT : value == "hand" ? HAND : value == "prison" ? PRISON - : OUT; + : MOVE_OUT; return value == "out" || value == "hand" || value == "prison"; } @@ -509,7 +509,7 @@ Variant* VariantParser::parse(Variant* v) { bool capturesToHand = false; if (parse_attribute("capturesToHand", capturesToHand)) { - v->captureType = capturesToHand ? HAND : OUT; + v->captureType = capturesToHand ? HAND : MOVE_OUT; } parse_attribute("captureType", v->captureType); diff --git a/src/position.h b/src/position.h index ef803ebe7..cb22bc86b 100644 --- a/src/position.h +++ b/src/position.h @@ -656,7 +656,7 @@ inline CapturingRule Position::capture_type() const { inline bool Position::captures_to_hand() const { assert(var != nullptr); - return var->captureType != OUT; + return var->captureType != MOVE_OUT; } inline bool Position::first_rank_pawn_drops() const { diff --git a/src/psqt.cpp b/src/psqt.cpp index 21418871f..cd7e83b3a 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -225,7 +225,7 @@ void init(const Variant* v) { constexpr int lc = 5; constexpr int rm = 5; constexpr int r0 = rm + RANK_8; - int r1 = rm + (v->maxRank + v->maxFile - 2 * (v->captureType != OUT)) / 2; + int r1 = rm + (v->maxRank + v->maxFile - 2 * (v->captureType != MOVE_OUT)) / 2; int leaper = pi->steps[0][MODALITY_QUIET].size() + pi->steps[0][MODALITY_CAPTURE].size(); int slider = pi->slider[0][MODALITY_QUIET].size() + pi->slider[0][MODALITY_CAPTURE].size() + pi->hopper[0][MODALITY_QUIET].size() + pi->hopper[0][MODALITY_CAPTURE].size(); score = make_score(mg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider), @@ -233,7 +233,7 @@ void init(const Variant* v) { } // Piece values saturate earlier in drop variants - if (v->captureType != OUT || v->twoBoards) + if (v->captureType != MOVE_OUT || v->twoBoards) score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)), eg_value(score) * 7000 / (7000 + eg_value(score))); @@ -267,7 +267,7 @@ void init(const Variant* v) { // The strongest piece of a variant usually has some dominance, such as rooks in Makruk and Xiangqi. // This does not apply to drop variants. - if (pt == strongestPiece && v->captureType == OUT) + if (pt == strongestPiece && v->captureType == MOVE_OUT) score += make_score(std::max(QueenValueMg - PieceValue[MG][pt], VALUE_ZERO) / 20, std::max(QueenValueEg - PieceValue[EG][pt], VALUE_ZERO) / 20); @@ -285,7 +285,7 @@ void init(const Variant* v) { CapturePieceValue[EG][pc] = CapturePieceValue[EG][~pc] = eg_value(score); // For drop variants, halve the piece values to compensate for double changes by captures - if (v->captureType != OUT) + if (v->captureType != MOVE_OUT) score = score / 2; EvalPieceValue[MG][pc] = EvalPieceValue[MG][~pc] = mg_value(score); @@ -309,7 +309,7 @@ void init(const Variant* v) { File f = std::max(File(edge_distance(file_of(s), v->maxFile)), FILE_A); Rank r = rank_of(s); psq[ pc][s] = score + ( pt == PAWN ? PBonus[std::min(r, RANK_8)][std::min(file_of(s), FILE_H)] - : pt == KING ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + (v->captureType != OUT)) + : pt == KING ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + (v->captureType != MOVE_OUT)) : pt <= QUEEN ? Bonus[pc][std::min(r, RANK_8)][std::min(f, FILE_D)] * (1 + v->blastOnCapture) : pt == HORSE ? Bonus[KNIGHT][std::min(r, RANK_8)][std::min(f, FILE_D)] : pt == COMMONER && v->extinctionValue == -VALUE_MATE && (v->extinctionPieceTypes & COMMONER) ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] diff --git a/src/pyffish.cpp b/src/pyffish.cpp index be3dae63f..00d4cf7e3 100644 --- a/src/pyffish.cpp +++ b/src/pyffish.cpp @@ -137,7 +137,7 @@ extern "C" PyObject* pyffish_capturesToHand(PyObject* self, PyObject *args) { return NULL; } - return Py_BuildValue("O", variants.find(std::string(variant))->second->captureType != OUT ? Py_True : Py_False); + return Py_BuildValue("O", variants.find(std::string(variant))->second->captureType != MOVE_OUT ? Py_True : Py_False); } // INPUT variant, fen, move diff --git a/src/types.h b/src/types.h index 1541885ed..5f616bb81 100644 --- a/src/types.h +++ b/src/types.h @@ -310,7 +310,7 @@ enum WallingRule { }; enum CapturingRule { - OUT, HAND, PRISON + MOVE_OUT, HAND, PRISON }; enum OptBool { diff --git a/src/variant.cpp b/src/variant.cpp index 7adaa15b9..1318157b2 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -645,7 +645,7 @@ namespace { Variant* v = crazyhouse_variant()->init(); v->variantTemplate = "bughouse"; v->twoBoards = true; - v->captureType = OUT; + v->captureType = MOVE_OUT; v->stalemateValue = -VALUE_MATE; return v; } @@ -672,7 +672,7 @@ namespace { v->pocketSize = 2; v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Nn] w KQkq - 0 1"; v->pieceDrops = true; - v->captureType = OUT; + v->captureType = MOVE_OUT; return v; } // Placement/Pre-chess @@ -684,7 +684,7 @@ namespace { v->startFen = "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1"; v->mustDrop = true; v->pieceDrops = true; - v->captureType = OUT; + v->captureType = MOVE_OUT; v->whiteDropRegion = Rank1BB; v->blackDropRegion = Rank8BB; v->dropOppositeColoredBishop = true; @@ -703,7 +703,7 @@ namespace { v->add_piece(MET, 'f'); v->mustDrop = true; v->pieceDrops = true; - v->captureType = OUT; + v->captureType = MOVE_OUT; v->whiteDropRegion = Rank1BB | Rank2BB | Rank3BB; v->blackDropRegion = Rank8BB | Rank7BB | Rank6BB; v->sittuyinRookDrop = true; @@ -755,7 +755,7 @@ namespace { v->add_piece(ARCHBISHOP, 'd'); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Dd] w KQkq - 0 1"; v->pieceDrops = true; - v->captureType = OUT; + v->captureType = MOVE_OUT; v->whiteDropRegion = Rank1BB; v->blackDropRegion = Rank8BB; return v; @@ -1246,7 +1246,7 @@ namespace { v->add_piece(COMMONER, 'k'); v->add_piece(CUSTOM_PIECE_1, 'e', "FsfW"); // drunk elephant v->startFen = "lnsgkgsnl/1r2e2b1/ppppppppp/9/9/9/PPPPPPPPP/1B2E2R1/LNSGKGSNL w 0 1"; - v->captureType = OUT; + v->captureType = MOVE_OUT; v->pieceDrops = false; v->promotedPieceType[CUSTOM_PIECE_1] = COMMONER; v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; @@ -2051,7 +2051,7 @@ Variant* Variant::conclude() { && !connectN && !blastOnCapture && !petrifyOnCaptureTypes - && captureType == OUT + && captureType == MOVE_OUT && !twoBoards && !restrictedMobility && kingType == KING; diff --git a/src/variant.h b/src/variant.h index b0660f828..9f78e2c60 100644 --- a/src/variant.h +++ b/src/variant.h @@ -92,7 +92,7 @@ struct Variant { PieceType mustDropType = ALL_PIECES; bool pieceDrops = false; bool dropLoop = false; - CapturingRule captureType = OUT; + CapturingRule captureType = MOVE_OUT; bool firstRankPawnDrops = false; bool promotionZonePawnDrops = false; EnclosingRule enclosingDrop = NO_ENCLOSING;