diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 734d1179..c6c7ef3d 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1280,22 +1280,8 @@ namespace { // Connect-n if (pos.connect_n() > 0) { - std::vector connect_directions; + for (const Direction& d : pos.getConnectDirections()) - if (pos.connect_horizontal()) - { - connect_directions.push_back(EAST); - } - if (pos.connect_vertical()) - { - connect_directions.push_back(NORTH); - } - if (pos.connect_diagonal()) - { - connect_directions.push_back(NORTH_EAST); - connect_directions.push_back(SOUTH_EAST); - } - for (Direction d : connect_directions) { // Find sufficiently large gaps Bitboard b = pos.board_bb() & ~pos.pieces(Them); diff --git a/src/parser.cpp b/src/parser.cpp index 7623e5aa..43d2bc82 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -114,7 +114,7 @@ namespace { int rank; std::stringstream ss(value); target = 0; - while (!ss.eof() && ss >> file && ss >> rank) + while (!ss.eof() && ss >> file && file != '-' && ss >> rank) { if (Rank(rank - 1) > RANK_MAX || (file != '*' && File(tolower(file) - 'a') > FILE_MAX)) return false; @@ -357,7 +357,10 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion); parse_attribute("pieceDemotion", v->pieceDemotion); parse_attribute("blastOnCapture", v->blastOnCapture); + parse_attribute("blastImmuneTypes", v->blastImmuneTypes, v->pieceToChar); + parse_attribute("mutuallyImmuneTypes", v->mutuallyImmuneTypes, v->pieceToChar); parse_attribute("petrifyOnCapture", v->petrifyOnCapture); + parse_attribute("petrifyBlastPieces", v->petrifyBlastPieces); parse_attribute("doubleStep", v->doubleStep); parse_attribute("doubleStepRegionWhite", v->doubleStepRegion[WHITE]); parse_attribute("doubleStepRegionBlack", v->doubleStepRegion[BLACK]); @@ -523,6 +526,7 @@ Variant* VariantParser::parse(Variant* v) { // Check for limitations if (v->pieceDrops && (v->arrowGating || v->duckGating || v->staticGating || v->pastGating)) std::cerr << "pieceDrops and arrowGating/duckGating are incompatible." << std::endl; + // Options incompatible with royal kings if (v->pieceTypes & KING) { @@ -546,6 +550,17 @@ Variant* VariantParser::parse(Variant* v) { std::cerr << piece_name(v->kingType) << " is not supported as kingType." << std::endl; } } + // Options incompatible with royal kings OR pseudo-royal kings. Possible in theory though: + // 1. In blast variants, moving a (pseudo-)royal blastImmuneType into another piece is legal. + // 2. In blast variants, capturing a piece next to a (pseudo-)royal blastImmuneType is legal. + // 3. Moving a (pseudo-)royal mutuallyImmuneType into a square threatened by the same type is legal. + if ((v->extinctionPseudoRoyal) || (v->pieceTypes & KING)) + { + if (v->blastImmuneTypes) + std::cerr << "Can not use kings or pseudo-royal with blastImmuneTypes." << std::endl; + if (v->mutuallyImmuneTypes) + std::cerr << "Can not use kings or pseudo-royal with mutuallyImmuneTypes." << std::endl; + } } return v; } diff --git a/src/position.cpp b/src/position.cpp index c1734e4d..7363fc28 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1176,6 +1176,14 @@ bool Position::legal(Move m) const { if (var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) == KING) return false; + // mutuallyImmuneTypes (diplomacy in Atomar)-- In no-check Atomic, kings can be beside each other, but in Atomar, this prevents them from actually taking. + // Generalized to allow a custom set of pieces that can't capture a piece of the same type. + if (capture(m) && + (mutually_immune_types() & type_of(moved_piece(m))) && + (type_of(moved_piece(m)) == type_of(piece_on(to))) + ) + return false; + // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. @@ -1921,13 +1929,19 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if (cambodian_moves() && type_of(pc) == ROOK && (square(them) & gates(them) & attacks_bb(to))) st->gatesBB[them] ^= square(them); + // Remove the blast pieces if (captured && (blast_on_capture() || var->petrifyOnCapture)) { std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch)); st->demotedBycatch = st->promotedBycatch = 0; - Bitboard blast = blast_on_capture() ? (attacks_bb(to) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | to - : type_of(pc) != PAWN ? square_bb(to) : Bitboard(0); + Bitboard blastImmune = 0; + for (PieceSet ps = blast_immune_types(); ps;){ + PieceType pt = pop_lsb(ps); + blastImmune |= pieces(pt); + }; + Bitboard blast = blast_on_capture() ? ((attacks_bb(to) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | to) + & (pieces() ^ blastImmune) : type_of(pc) != PAWN ? square_bb(to) : Bitboard(0); while (blast) { Square bsq = pop_lsb(blast); @@ -1989,7 +2003,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Make a wall square where the piece was - if (var->petrifyOnCapture) + if (bsq == to ? var->petrifyOnCapture : var->petrifyBlastPieces) { st->wallSquares |= bsq; byTypeBB[ALL_PIECES] |= bsq; @@ -2717,22 +2731,8 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { if (connect_n() > 0) { Bitboard b; - std::vector connect_directions; - if (connect_horizontal()) - { - connect_directions.push_back(EAST); - } - if (connect_vertical()) - { - connect_directions.push_back(NORTH); - } - if (connect_diagonal()) - { - connect_directions.push_back(NORTH_EAST); - connect_directions.push_back(SOUTH_EAST); - } - for (Direction d : connect_directions) + for (Direction d : var->connect_directions) { b = pieces(~sideToMove); for (int i = 1; i < connect_n() && b; i++) diff --git a/src/position.h b/src/position.h index f0a50739..fcdd6cae 100644 --- a/src/position.h +++ b/src/position.h @@ -140,6 +140,8 @@ class Position { bool mandatory_piece_promotion() const; bool piece_demotion() const; bool blast_on_capture() const; + PieceSet blast_immune_types() const; + PieceSet mutually_immune_types() const; bool endgame_eval() const; Bitboard double_step_region(Color c) const; Bitboard triple_step_region(Color c) const; @@ -206,6 +208,7 @@ class Position { bool connect_horizontal() const; bool connect_vertical() const; bool connect_diagonal() const; + const std::vector& getConnectDirections() const; CheckCount checks_remaining(Color c) const; MaterialCounting material_counting() const; @@ -489,6 +492,16 @@ inline bool Position::blast_on_capture() const { return var->blastOnCapture; } +inline PieceSet Position::blast_immune_types() const { + assert(var != nullptr); + return var->blastImmuneTypes; +} + +inline PieceSet Position::mutually_immune_types() const { + assert(var != nullptr); + return var->mutuallyImmuneTypes; +} + inline bool Position::endgame_eval() const { assert(var != nullptr); return var->endgameEval && !count_in_hand(ALL_PIECES) && count() == 2; @@ -965,6 +978,10 @@ inline bool Position::connect_diagonal() const { return var->connectDiagonal; } +inline const std::vector& Position::getConnectDirections() const { + assert(var != nullptr); + return var->connect_directions; +} inline CheckCount Position::checks_remaining(Color c) const { return st->checksRemaining[c]; @@ -1391,18 +1408,18 @@ inline bool Position::allow_virtual_drop(Color c, PieceType pt) const { } inline Value Position::material_counting_result() const { - auto weigth_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); }; + auto weight_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); }; int materialCount; Value result; switch (var->materialCounting) { case JANGGI_MATERIAL: - materialCount = weigth_count(ROOK, 13) - + weigth_count(JANGGI_CANNON, 7) - + weigth_count(HORSE, 5) - + weigth_count(JANGGI_ELEPHANT, 3) - + weigth_count(WAZIR, 3) - + weigth_count(SOLDIER, 2) + materialCount = weight_count(ROOK, 13) + + weight_count(JANGGI_CANNON, 7) + + weight_count(HORSE, 5) + + weight_count(JANGGI_ELEPHANT, 3) + + weight_count(WAZIR, 3) + + weight_count(SOLDIER, 2) - 1; result = materialCount > 0 ? VALUE_MATE : -VALUE_MATE; break; diff --git a/src/search.cpp b/src/search.cpp index fc7c4f83..38d72f52 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -936,7 +936,7 @@ namespace { && (ss-1)->statScore < 23767 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 159 + 200 * (!pos.double_step_region(pos.side_to_move()) && pos.piece_to_char()[PAWN] != ' ') + && ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 159 + 200 * (!pos.double_step_region(pos.side_to_move()) && (pos.piece_types() & PAWN)) && !excludedMove && pos.non_pawn_material(us) && pos.count(~us) != pos.count(~us) diff --git a/src/variant.cpp b/src/variant.cpp index d477eb66..a0ce8156 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -482,6 +482,15 @@ namespace { return v; } + // Atomar chess + // https://web.archive.org/web/20230519082613/https://chronatog.com/wp-content/uploads/2021/09/atomar-chess-rules.pdf + Variant* atomar_variant() { + Variant* v = nocheckatomic_variant()->init(); + v->blastImmuneTypes = piece_set(COMMONER); + v->mutuallyImmuneTypes = piece_set(COMMONER); + return v; + } + #ifdef ALLVARS // Duck chess Variant* duck_variant() { @@ -1802,6 +1811,7 @@ void VariantMap::init() { add("isolation7x7", isolation7x7_variant()); add("snailtrail", snailtrail_variant()); add("fox-and-hounds", fox_and_hounds_variant()); + add("atomar", atomar_variant()); #ifdef ALLVARS add("duck", duck_variant()); #endif @@ -2016,6 +2026,21 @@ Variant* Variant::conclude() { break; } + connect_directions.clear(); + if (connectHorizontal) + { + connect_directions.push_back(EAST); + } + if (connectVertical) + { + connect_directions.push_back(NORTH); + } + if (connectDiagonal) + { + connect_directions.push_back(NORTH_EAST); + connect_directions.push_back(SOUTH_EAST); + } + return this; } diff --git a/src/variant.h b/src/variant.h index bc4862d7..0492dccc 100644 --- a/src/variant.h +++ b/src/variant.h @@ -64,7 +64,10 @@ struct Variant { bool mandatoryPiecePromotion = false; bool pieceDemotion = false; bool blastOnCapture = false; + PieceSet blastImmuneTypes = NO_PIECE_SET; + PieceSet mutuallyImmuneTypes = NO_PIECE_SET; bool petrifyOnCapture = false; + bool petrifyBlastPieces = false; bool doubleStep = true; Bitboard doubleStepRegion[COLOR_NB] = {Rank2BB, Rank7BB}; Bitboard tripleStepRegion[COLOR_NB] = {}; @@ -167,6 +170,7 @@ struct Variant { int nnueMaxPieces; bool endgameEval = false; bool shogiStylePromotions = false; + std::vector connect_directions; void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') { // Avoid ambiguous definition by removing existing piece with same letter diff --git a/src/variants.ini b/src/variants.ini index 5b139d80..63bd4da6 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -166,7 +166,10 @@ # mandatoryPiecePromotion: piece promotion (and demotion if enabled) is mandatory [bool] (default: false) # pieceDemotion: enable demotion of pieces (e.g., Kyoto shogi) [bool] (default: false) # blastOnCapture: captures explode all adjacent non-pawn pieces (e.g., atomic chess) [bool] (default: false) +# blastImmuneTypes: pieces completely immune to explosions (even at ground zero) [PieceSet] (default: none) +# mutuallyImmuneTypes: pieces that can't capture another piece of same types (e.g., kings (commoners) in atomar) [PieceSet] (default: none) # petrifyOnCapture: non-pawn pieces are turned into wall squares when capturing [bool] (default: false) +# petrifyBlastPieces: if petrify and blast combined, should pieces destroyed in the blast be petrified? [bool] (default: false) # doubleStep: enable pawn double step [bool] (default: true) # doubleStepRegionWhite: region where pawn double steps are allowed for white [Bitboard] (default: *2) # doubleStepRegionBlack: region where pawn double steps are allowed for black [Bitboard] (default: *2) @@ -339,6 +342,12 @@ capturesToHand = true dropChecks = false whiteDropRegion = *1 *2 *3 *4 *5 blackDropRegion = *6 *7 *8 *9 *10 +mobilityRegionWhiteFers = d1 f1 e2 d3 f3 +mobilityRegionBlackFers = d8 f8 e9 d10 f10 +mobilityRegionWhiteElephant = c1 g1 a3 e3 i3 c5 g5 +mobilityRegionBlackElephant = c6 g6 a8 e8 i8 c10 g10 +mobilityRegionWhiteSoldier = a4 a5 c4 c5 e4 e5 g4 g5 i4 i5 *6 *7 *8 *9 *10 +mobilityRegionBlackSoldier = *1 *2 *3 *4 *5 a6 a7 c6 c7 e6 e7 g6 g7 i6 i7 # Hybrid variant of janggi and crazyhouse [janggihouse:janggi] @@ -1326,7 +1335,7 @@ customPiece1 = i:NN customPiece2 = h:mfWcfFfhmnN startFen = r8r/3nkqn3/hcb1ii1bch/1hhhhhhhh1/10/10/1HHHHHHHH1/HCB1II1BCH/3NKQN3/R8R #technically not needed because of initial setup -##enPassantRegion = 0 +##enPassantRegion = - ##castling = false #https://boardgamegeek.com/boardgame/32/buffalo-chess @@ -1574,3 +1583,14 @@ customPiece4 = w:mRpRFAcpR customPiece5 = f:mBpBWDcpB promotedPieceType = u:w a:w c:f i:f startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1 + +#https://www.chessvariants.com/difftaking.dir/deadsquare.html +[nuclear:atomic] +#define a piece that looks exactly like a pawn, but is not one. Takes care of major differences: +#1. Pawns can be petrified. +#2. Pawns can be destroyed by explosions. +pawn = - +customPiece1 = p:fmWfceFifmnD +pawnTypes = p +petrifyOnCapture = true +enPassantRegion = -