diff --git a/src/ffishjs.cpp b/src/ffishjs.cpp index 7e4f26dc0..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->capturesToHand; + return v->captureType != MOVE_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..8f6bcf09b 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" ? MOVE_OUT + : value == "hand" ? HAND + : value == "prison" ? PRISON + : MOVE_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,50 @@ 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 (size_t i = 0; i < map.size(); ++i) { + char token = map[i]; + if (token == ' ') { + if (!readPiece) { + 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); + } + } + v->hostageExchange[idx] = mask; + } + } // namespace template @@ -207,6 +259,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 +506,19 @@ 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; + if (parse_attribute("capturesToHand", capturesToHand)) { + v->captureType = capturesToHand ? HAND : MOVE_OUT; + } + + 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..a0418f33c 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..cb22bc86b 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 != MOVE_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..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->capturesToHand) / 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->capturesToHand || 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->capturesToHand) + 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->capturesToHand) + 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->capturesToHand) + : 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 d321a57cc..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->capturesToHand ? 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 5abfac80b..5f616bb81 100644 --- a/src/types.h +++ b/src/types.h @@ -309,6 +309,10 @@ enum WallingRule { NO_WALLING, ARROW, DUCK, EDGE, PAST, STATIC }; +enum CapturingRule { + MOVE_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..1318157b2 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 = MOVE_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 = MOVE_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 = MOVE_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 = MOVE_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 = MOVE_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 = MOVE_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 == MOVE_OUT && !twoBoards && !restrictedMobility && kingType == KING; diff --git a/src/variant.h b/src/variant.h index ec1991859..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; - bool capturesToHand = false; + CapturingRule captureType = MOVE_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