From b1124aec70c0ea3ba1db3b7e7a2c72b36adae467 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Fri, 24 Mar 2023 16:54:31 +0100 Subject: [PATCH] Support custom pawn pieces This introduces support for * initial moves, promotions, and en passant for all pieces. * arbitrarily shaped double/triple step and promotion regions. * per color configuration of promotion types and zone, etc. These new configuration options enable support for * Berolina, Spartan, Wolf * Torpedo, pawnsideways, pawnback * Legan, Troitzky Most changed configuration options are kept as deprected options for now for backwards compatibility, but it is recommended to migrate variant configuration files as the deprecated options might be removed later. Closes #305, closes #4, closes #209, closes #260, closes #51, closes #328, closes #596. No functional change for other variants. --- src/apiutil.h | 4 +- src/bitboard.cpp | 106 ++++++++++------- src/bitboard.h | 15 ++- src/endgame.cpp | 20 ++-- src/evaluate.cpp | 19 ++-- src/magic.h | 122 +++++++++++++++++++ src/material.cpp | 4 +- src/movegen.cpp | 189 ++++++++++++++++------------ src/parser.cpp | 133 +++++++++++++++----- src/parser.h | 4 +- src/pawns.cpp | 2 +- src/piece.cpp | 17 ++- src/piece.h | 6 +- src/position.cpp | 240 ++++++++++++++++++++++--------------- src/position.h | 85 +++++++++---- src/psqt.cpp | 34 +++--- src/search.cpp | 2 +- src/types.h | 34 ++++-- src/ucioption.cpp | 5 +- src/variant.cpp | 350 ++++++++++++++++++++++++++++++++++++++++------------- src/variant.h | 34 +++++- src/variants.ini | 128 +++++++++++++------- test.py | 7 +- tests/perft.sh | 18 +++ 24 files changed, 1096 insertions(+), 482 deletions(-) diff --git a/src/apiutil.h b/src/apiutil.h index f75d830..34e7729 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -386,7 +386,7 @@ inline bool has_insufficient_material(Color c, const Position& pos) { // Mating pieces for (PieceType pt : { ROOK, QUEEN, ARCHBISHOP, CHANCELLOR, SILVER, GOLD, COMMONER, CENTAUR }) - if ((pos.pieces(c, pt) & ~restricted) || (pos.count(c, PAWN) && pos.promotion_piece_types().find(pt) != pos.promotion_piece_types().end())) + if ((pos.pieces(c, pt) & ~restricted) || (pos.count(c, pos.promotion_pawn_type(c)) && pos.promotion_piece_types(c).find(pt) != pos.promotion_piece_types(c).end())) return false; // Color-bound pieces @@ -895,7 +895,7 @@ inline std::string get_valid_special_chars(const Variant* v) { // Whether or not '-', '+', '~', '[', ']' are valid depends on the variant being played. if (v->shogiStylePromotions) validSpecialCharactersFirstField += '+'; - if (!v->promotionPieceTypes.empty()) + if (!v->promotionPieceTypes[WHITE].empty() || !v->promotionPieceTypes[BLACK].empty()) validSpecialCharactersFirstField += '~'; if (!v->freeDrops && (v->pieceDrops || v->seirawanGating)) validSpecialCharactersFirstField += "[-]"; diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 63f2bfd..b4671f1 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -33,18 +33,19 @@ Bitboard SquareBB[SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; -Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard PseudoMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; -Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard LeaperMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Bitboard BoardSizeBB[FILE_NB][RANK_NB]; RiderType AttackRiderTypes[PIECE_TYPE_NB]; -RiderType MoveRiderTypes[PIECE_TYPE_NB]; +RiderType MoveRiderTypes[2][PIECE_TYPE_NB]; Magic RookMagicsH[SQUARE_NB]; Magic RookMagicsV[SQUARE_NB]; Magic BishopMagics[SQUARE_NB]; Magic CannonMagicsH[SQUARE_NB]; Magic CannonMagicsV[SQUARE_NB]; +Magic LameDabbabaMagics[SQUARE_NB]; Magic HorseMagics[SQUARE_NB]; Magic ElephantMagics[SQUARE_NB]; Magic JanggiElephantMagics[SQUARE_NB]; @@ -55,7 +56,7 @@ Magic GrasshopperMagicsV[SQUARE_NB]; Magic GrasshopperMagicsD[SQUARE_NB]; Magic* magics[] = {BishopMagics, RookMagicsH, RookMagicsV, CannonMagicsH, CannonMagicsV, - HorseMagics, ElephantMagics, JanggiElephantMagics, CannonDiagMagics, NightriderMagics, + LameDabbabaMagics, HorseMagics, ElephantMagics, JanggiElephantMagics, CannonDiagMagics, NightriderMagics, GrasshopperMagicsH, GrasshopperMagicsV, GrasshopperMagicsD}; namespace { @@ -68,6 +69,7 @@ namespace { Bitboard BishopTable[0x33C00]; // To store bishop attacks Bitboard CannonTableH[0x11800]; // To store horizontal cannon attacks Bitboard CannonTableV[0x4800]; // To store vertical cannon attacks + Bitboard LameDabbabaTable[0x500]; // To store lame dabbaba attacks Bitboard HorseTable[0x500]; // To store horse attacks Bitboard ElephantTable[0x400]; // To store elephant attacks Bitboard JanggiElephantTable[0x1C000]; // To store janggi elephant attacks @@ -82,6 +84,7 @@ namespace { Bitboard BishopTable[0x1480]; // To store bishop attacks Bitboard CannonTableH[0xA00]; // To store horizontal cannon attacks Bitboard CannonTableV[0xA00]; // To store vertical cannon attacks + Bitboard LameDabbabaTable[0x240]; // To store lame dabbaba attacks Bitboard HorseTable[0x240]; // To store horse attacks Bitboard ElephantTable[0x1A0]; // To store elephant attacks Bitboard JanggiElephantTable[0x5C00]; // To store janggi elephant attacks @@ -96,6 +99,7 @@ namespace { const std::map RookDirectionsV { {NORTH, 0}, {SOUTH, 0}}; const std::map RookDirectionsH { {EAST, 0}, {WEST, 0} }; const std::map BishopDirections { {NORTH_EAST, 0}, {SOUTH_EAST, 0}, {SOUTH_WEST, 0}, {NORTH_WEST, 0} }; + const std::map LameDabbabaDirections { {2 * NORTH, 0}, {2 * EAST, 0}, {2 * SOUTH, 0}, {2 * WEST, 0} }; const std::map HorseDirections { {2 * SOUTH + WEST, 0}, {2 * SOUTH + EAST, 0}, {SOUTH + 2 * WEST, 0}, {SOUTH + 2 * EAST, 0}, {NORTH + 2 * WEST, 0}, {NORTH + 2 * EAST, 0}, {2 * NORTH + WEST, 0}, {2 * NORTH + EAST, 0} }; const std::map ElephantDirections { {2 * NORTH_EAST, 0}, {2 * SOUTH_EAST, 0}, {2 * SOUTH_WEST, 0}, {2 * NORTH_WEST, 0} }; @@ -232,36 +236,44 @@ void Bitboards::init_pieces() { // Detect rider types for (auto modality : {MODALITY_QUIET, MODALITY_CAPTURE}) { - auto& riderTypes = modality == MODALITY_CAPTURE ? AttackRiderTypes[pt] : MoveRiderTypes[pt]; - riderTypes = NO_RIDER; - for (auto const& [d, limit] : pi->steps[modality]) + for (bool initial : {false, true}) { - if (limit && HorseDirections.find(d) != HorseDirections.end()) - riderTypes |= RIDER_HORSE; - if (limit && ElephantDirections.find(d) != ElephantDirections.end()) - riderTypes |= RIDER_ELEPHANT; - if (limit && JanggiElephantDirections.find(d) != JanggiElephantDirections.end()) - riderTypes |= RIDER_JANGGI_ELEPHANT; - } - for (auto const& [d, limit] : pi->slider[modality]) - { - if (BishopDirections.find(d) != BishopDirections.end()) - riderTypes |= RIDER_BISHOP; - if (RookDirectionsH.find(d) != RookDirectionsH.end()) - riderTypes |= RIDER_ROOK_H; - if (RookDirectionsV.find(d) != RookDirectionsV.end()) - riderTypes |= RIDER_ROOK_V; - if (HorseDirections.find(d) != HorseDirections.end()) - riderTypes |= RIDER_NIGHTRIDER; - } - for (auto const& [d, limit] : pi->hopper[modality]) - { - if (RookDirectionsH.find(d) != RookDirectionsH.end()) - riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_H : RIDER_CANNON_H; - if (RookDirectionsV.find(d) != RookDirectionsV.end()) - riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_V : RIDER_CANNON_V; - if (BishopDirections.find(d) != BishopDirections.end()) - riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_D : RIDER_CANNON_DIAG; + // We do not support initial captures + if (modality == MODALITY_CAPTURE && initial) + continue; + auto& riderTypes = modality == MODALITY_CAPTURE ? AttackRiderTypes[pt] : MoveRiderTypes[initial][pt]; + riderTypes = NO_RIDER; + for (auto const& [d, limit] : pi->steps[initial][modality]) + { + if (limit && LameDabbabaDirections.find(d) != LameDabbabaDirections.end()) + riderTypes |= RIDER_LAME_DABBABA; + if (limit && HorseDirections.find(d) != HorseDirections.end()) + riderTypes |= RIDER_HORSE; + if (limit && ElephantDirections.find(d) != ElephantDirections.end()) + riderTypes |= RIDER_ELEPHANT; + if (limit && JanggiElephantDirections.find(d) != JanggiElephantDirections.end()) + riderTypes |= RIDER_JANGGI_ELEPHANT; + } + for (auto const& [d, limit] : pi->slider[initial][modality]) + { + if (BishopDirections.find(d) != BishopDirections.end()) + riderTypes |= RIDER_BISHOP; + if (RookDirectionsH.find(d) != RookDirectionsH.end()) + riderTypes |= RIDER_ROOK_H; + if (RookDirectionsV.find(d) != RookDirectionsV.end()) + riderTypes |= RIDER_ROOK_V; + if (HorseDirections.find(d) != HorseDirections.end()) + riderTypes |= RIDER_NIGHTRIDER; + } + for (auto const& [d, limit] : pi->hopper[initial][modality]) + { + if (RookDirectionsH.find(d) != RookDirectionsH.end()) + riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_H : RIDER_CANNON_H; + if (RookDirectionsV.find(d) != RookDirectionsV.end()) + riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_V : RIDER_CANNON_V; + if (BishopDirections.find(d) != BishopDirections.end()) + riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_D : RIDER_CANNON_DIAG; + } } } @@ -272,18 +284,24 @@ void Bitboards::init_pieces() { { for (auto modality : {MODALITY_QUIET, MODALITY_CAPTURE}) { - auto& pseudo = modality == MODALITY_CAPTURE ? PseudoAttacks[c][pt][s] : PseudoMoves[c][pt][s]; - auto& leaper = modality == MODALITY_CAPTURE ? LeaperAttacks[c][pt][s] : LeaperMoves[c][pt][s]; - pseudo = 0; - leaper = 0; - for (auto const& [d, limit] : pi->steps[modality]) + for (bool initial : {false, true}) { - pseudo |= safe_destination(s, c == WHITE ? d : -d); - if (!limit) - leaper |= safe_destination(s, c == WHITE ? d : -d); + // We do not support initial captures + if (modality == MODALITY_CAPTURE && initial) + continue; + auto& pseudo = modality == MODALITY_CAPTURE ? PseudoAttacks[c][pt][s] : PseudoMoves[initial][c][pt][s]; + auto& leaper = modality == MODALITY_CAPTURE ? LeaperAttacks[c][pt][s] : LeaperMoves[initial][c][pt][s]; + pseudo = 0; + leaper = 0; + for (auto const& [d, limit] : pi->steps[initial][modality]) + { + pseudo |= safe_destination(s, c == WHITE ? d : -d); + if (!limit) + leaper |= safe_destination(s, c == WHITE ? d : -d); + } + pseudo |= sliding_attack(pi->slider[initial][modality], s, 0, c); + pseudo |= sliding_attack(pi->hopper[initial][modality], s, 0, c); } - pseudo |= sliding_attack(pi->slider[modality], s, 0, c); - pseudo |= sliding_attack(pi->hopper[modality], s, 0, c); } } } @@ -316,6 +334,7 @@ void Bitboards::init() { init_magics(BishopTable, BishopMagics, BishopDirections, BishopMagicInit); init_magics(CannonTableH, CannonMagicsH, RookDirectionsH, CannonMagicHInit); init_magics(CannonTableV, CannonMagicsV, RookDirectionsV, CannonMagicVInit); + init_magics(LameDabbabaTable, LameDabbabaMagics, LameDabbabaDirections, LameDabbabaMagicInit); init_magics(HorseTable, HorseMagics, HorseDirections, HorseMagicInit); init_magics(ElephantTable, ElephantMagics, ElephantDirections, ElephantMagicInit); init_magics(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections, JanggiElephantMagicInit); @@ -330,6 +349,7 @@ void Bitboards::init() { init_magics(BishopTable, BishopMagics, BishopDirections); init_magics(CannonTableH, CannonMagicsH, RookDirectionsH); init_magics(CannonTableV, CannonMagicsV, RookDirectionsV); + init_magics(LameDabbabaTable, LameDabbabaMagics, LameDabbabaDirections); init_magics(HorseTable, HorseMagics, HorseDirections); init_magics(ElephantTable, ElephantMagics, ElephantDirections); init_magics(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections); diff --git a/src/bitboard.h b/src/bitboard.h index 60a57a4..b1b6f3d 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -106,13 +106,13 @@ extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; -extern Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard PseudoMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; -extern Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard LeaperMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard BoardSizeBB[FILE_NB][RANK_NB]; extern RiderType AttackRiderTypes[PIECE_TYPE_NB]; -extern RiderType MoveRiderTypes[PIECE_TYPE_NB]; +extern RiderType MoveRiderTypes[2][PIECE_TYPE_NB]; #ifdef LARGEBOARDS int popcount(Bitboard b); // required for 128 bit pext @@ -149,6 +149,7 @@ extern Magic RookMagicsV[SQUARE_NB]; extern Magic BishopMagics[SQUARE_NB]; extern Magic CannonMagicsH[SQUARE_NB]; extern Magic CannonMagicsV[SQUARE_NB]; +extern Magic LameDabbabaMagics[SQUARE_NB]; extern Magic HorseMagics[SQUARE_NB]; extern Magic ElephantMagics[SQUARE_NB]; extern Magic JanggiElephantMagics[SQUARE_NB]; @@ -407,6 +408,7 @@ inline Bitboard rider_attacks_bb(Square s, Bitboard occupied) { : R == RIDER_ROOK_V ? RookMagicsV[s] : R == RIDER_CANNON_H ? CannonMagicsH[s] : R == RIDER_CANNON_V ? CannonMagicsV[s] + : R == RIDER_LAME_DABBABA ? LameDabbabaMagics[s] : R == RIDER_HORSE ? HorseMagics[s] : R == RIDER_ELEPHANT ? ElephantMagics[s] : R == RIDER_JANGGI_ELEPHANT ? JanggiElephantMagics[s] @@ -477,12 +479,13 @@ inline Bitboard attacks_bb(Color c, PieceType pt, Square s, Bitboard occupied) { } +template inline Bitboard moves_bb(Color c, PieceType pt, Square s, Bitboard occupied) { - Bitboard b = LeaperMoves[c][pt][s]; - RiderType r = MoveRiderTypes[pt]; + Bitboard b = LeaperMoves[Initial][c][pt][s]; + RiderType r = MoveRiderTypes[Initial][pt]; while (r) b |= rider_attacks_bb(pop_rider(&r), s, occupied); - return b & PseudoMoves[c][pt][s]; + return b & PseudoMoves[Initial][c][pt][s]; } diff --git a/src/endgame.cpp b/src/endgame.cpp index 80de6b9..186258a 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -183,9 +183,9 @@ Value Endgame::operator()(const Position& pos) const { Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; // Non-standard promotion, evaluation unclear - if ( pos.promotion_rank() != RANK_8 + if ( pos.promotion_zone(us) != rank_bb(relative_rank(us, RANK_8, pos.max_rank())) || RANK_MAX != RANK_8 - || pos.promotion_piece_types().find(QUEEN) == pos.promotion_piece_types().end()) + || pos.promotion_piece_types(us).find(QUEEN) == pos.promotion_piece_types(us).end()) { Value result = PawnValueEg + Value(rank_of(strongPawn)); return strongSide == pos.side_to_move() ? result : -result; @@ -371,10 +371,14 @@ Value Endgame::operator()(const Position& pos) const { Bitboard b = pos.pieces(strongSide, PAWN); while (b && (!dark || !light)) { - if (file_of(pop_lsb(b)) % 2 != relative_rank(strongSide, pos.promotion_rank(), pos.max_rank()) % 2) - light = true; - else - dark = true; + Square s = pos.promotion_square(strongSide, pop_lsb(b)); + if (s != SQ_NONE) + { + if (DarkSquares & s) + dark = true; + else + light = true; + } } if (!dark || !light) return VALUE_DRAW; // we can not checkmate with same colored ferzes @@ -933,9 +937,9 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, // it's probably at least a draw even with the pawn. - if ( pos.promotion_rank() != RANK_8 + if ( pos.promotion_zone(us) != rank_bb(relative_rank(us, RANK_8, pos.max_rank())) || RANK_MAX != RANK_8 - || pos.promotion_piece_types().find(QUEEN) == pos.promotion_piece_types().end()) + || pos.promotion_piece_types(us).find(QUEEN) == pos.promotion_piece_types(us).end()) return SCALE_FACTOR_NONE; return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; diff --git a/src/evaluate.cpp b/src/evaluate.cpp index b22e0b2..24ca187 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -494,7 +494,7 @@ namespace { // Piece promotion bonus if (pos.promoted_piece_type(Pt) != NO_PIECE_TYPE) { - Bitboard zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank()); + Bitboard zone = pos.promotion_zone(Us); if (zone & (b | s)) score += make_score(PieceValue[MG][pos.promoted_piece_type(Pt)] - PieceValue[MG][Pt], PieceValue[EG][pos.promoted_piece_type(Pt)] - PieceValue[EG][Pt]) / (zone & s && b ? 6 : 12); @@ -734,7 +734,7 @@ namespace { if (pos.promoted_piece_type(pt)) { otherChecks = attacks_bb(Us, pos.promoted_piece_type(pt), ksq, pos.pieces()) & attackedBy[Them][pt] - & zone_bb(Them, pos.promotion_rank(), pos.max_rank()) & pos.board_bb(); + & pos.promotion_zone(Them) & pos.board_bb(); if (otherChecks & safe) kingDanger += SafeCheck[FAIRY_PIECES][more_than_one(otherChecks & safe)]; else @@ -1016,7 +1016,7 @@ namespace { assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); - int r = std::max(RANK_8 - std::max(pos.promotion_rank() - relative_rank(Us, s, pos.max_rank()), 0), 0); + int r = std::max(RANK_8 - std::max(relative_rank(Us, pos.promotion_square(Us, s), pos.max_rank()) - relative_rank(Us, s, pos.max_rank()), 0), 0); Score bonus = PassedRank[r]; @@ -1067,7 +1067,7 @@ namespace { // Scale by maximum promotion piece value Value maxMg = VALUE_ZERO, maxEg = VALUE_ZERO; - for (PieceType pt : pos.promotion_piece_types()) + for (PieceType pt : pos.promotion_piece_types(Us)) { maxMg = std::max(maxMg, PieceValue[MG][pt]); maxEg = std::max(maxEg, PieceValue[EG][pt]); @@ -1083,11 +1083,11 @@ namespace { while (b) { Square s = pop_lsb(b); - if ((pos.pieces(Them, SHOGI_PAWN) & forward_file_bb(Us, s)) || relative_rank(Us, s, pos.max_rank()) == pos.max_rank()) + if ((pos.pieces(Them, SHOGI_PAWN) & forward_file_bb(Us, s)) || pos.promotion_square(Us, s) == SQ_NONE) continue; Square blockSq = s + Up; - int d = 2 * std::max(pos.promotion_rank() - relative_rank(Us, s, pos.max_rank()), 1); + int d = 2 * std::max(relative_rank(Us, pos.promotion_square(Us, s), pos.max_rank()) - relative_rank(Us, s, pos.max_rank()), 1); d += !!(attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us] & blockSq); score += make_score(PieceValue[MG][pt], PieceValue[EG][pt]) / (d * d); } @@ -1111,7 +1111,7 @@ namespace { bool pawnsOnly = !(pos.pieces(Us) ^ pos.pieces(Us, PAWN)); // Early exit if, for example, both queens or 6 minor pieces have been exchanged - if (pos.non_pawn_material() < SpaceThreshold && !pawnsOnly && pos.double_step_enabled()) + if (pos.non_pawn_material() < SpaceThreshold && !pawnsOnly && pos.double_step_region(Us)) return SCORE_ZERO; constexpr Color Them = ~Us; @@ -1194,7 +1194,7 @@ namespace { { Square s = pop_lsb(current); Bitboard attacks = ( (PseudoAttacks[Us][ptCtf][s] & pos.pieces()) - | (PseudoMoves[Us][ptCtf][s] & ~pos.pieces())) & ~processed & pos.board_bb(); + | (PseudoMoves[0][Us][ptCtf][s] & ~pos.pieces())) & ~processed & pos.board_bb(); ctfPieces |= attacks & ~blocked; onHold |= attacks & ~doubleBlocked; onHold2 |= attacks & ~inaccessible; @@ -1345,7 +1345,8 @@ namespace { // Compute the initiative bonus for the attacking side complexity = 9 * pe->passed_count() - + 12 * pos.count() + + 12 * pos.count(WHITE, pos.promotion_pawn_type(WHITE)) * bool(pos.promotion_pawn_type(WHITE)) + + 12 * pos.count(BLACK, pos.promotion_pawn_type(BLACK)) * bool(pos.promotion_pawn_type(BLACK)) + 15 * pos.count() + 9 * outflanking + 21 * pawnsOnBothFlanks diff --git a/src/magic.h b/src/magic.h index 7fb2d1b..ff1453d 100644 --- a/src/magic.h +++ b/src/magic.h @@ -635,6 +635,128 @@ namespace Stockfish { B(0xA00002102810020, 0xB1080240015408), B(0x810080200806, 0x410440804080046), }; + constexpr Bitboard LameDabbabaMagicInit[SQUARE_NB] = { + B(0x3C080482A592000C, 0x540104000020000), + B(0x2802C40008000420, 0x4A00000001818009), + B(0x1083040280804000, 0x120004C20100880), + B(0x6840940880000892, 0x2014A01080800C2), + B(0x8401489004000180, 0x2000800000400000), + B(0x820161C800000110, 0x8000100000204020), + B(0x610011A122000109, 0x1000004020008004), + B(0x83282004023000, 0xE000020004848446), + B(0x6840940880000892, 0x2014A01080800C2), + B(0x4020120800800002, 0x88008000010020), + B(0x30025B140A1000, 0x3141801401000040), + B(0x41104D1810100050, 0x8141002010910), + B(0x4200828A298400, 0x400340001040C000), + B(0x8016A4900110040, 0x844812001068020), + B(0x2250035820400A2, 0x8012010080900), + B(0x820080083A009000, 0x880404091080110), + B(0x80401500AF0020, 0x240000082201A04), + B(0x668020020C081005, 0x4008001004100021), + B(0x240100910000000, 0x82000A0030454000), + B(0xA24091400008, 0x200014880004A921), + B(0x840110042200410, 0x100080000A400000), + B(0x40024024102000, 0x1000000002180404), + B(0x92828423000530, 0x118800020110), + B(0x1122404A1C90A8, 0x822040280020D00), + B(0x41201A40900A000, 0x80C0480040605100), + B(0x2504A85005488280, 0x3028112120022800), + B(0x210180080626B048, 0x8000401000014000), + B(0x1000410401040200, 0x41014000050C0106), + B(0x1040650210802200, 0x80C0041000000), + B(0x4020C10110900002, 0x2140C2001050009), + B(0x191180092200022, 0x6010008400400800), + B(0x8010821088080202, 0xCA240011008208), + B(0x8C0488120024214, 0x8414880202291), + B(0x8C0488120024214, 0x8414880202291), + B(0x22080C8A0161401, 0x200C10004C002002), + B(0x8430818023034080, 0x210090800000801), + B(0x4845087008200, 0x40661480000), + B(0x1202804428812050, 0x100022038020000), + B(0x400016001201080, 0x24002200402060), + B(0x680E041300800800, 0xE00130080004000), + B(0x3409080200, 0x282840210000000), + B(0x803310108400, 0x85200000080100A0), + B(0xE180008A04162104, 0x9088240412404), + B(0x20080100920020, 0x2002248010242052), + B(0x8A000400C2410, 0x1000024086014300), + B(0x1821040024663, 0x100000100010009), + B(0x4000822310611, 0x120280406014008), + B(0x1004008010818D08, 0x800000141892000), + B(0x8010800004024042, 0x44B106008800896), + B(0xA0063423444, 0x41002C15811008), + B(0x2040012381001282, 0x4804080104A4000), + B(0x10840101820880, 0xA800008000020020), + B(0x10840101820880, 0xA800008000020020), + B(0x60201D8300408190, 0x2010020920200000), + B(0x4048100200090090, 0x2008090100000900), + B(0x24200000280210, 0xD440050008004000), + B(0x1280001000580020, 0x2200040089000A4), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x4108000010209089, 0x913000000024840), + B(0x410C208008008E02, 0xE8000000000001), + B(0x802208004005, 0x94206000022080), + B(0xC00290018902002, 0x4204100000000000), + B(0x2102801400093816, 0x9810004001000202), + B(0x8008304000015800, 0x4A5C000000020000), + B(0x1020108380800514, 0x1144210000000080), + B(0xC0001000008090, 0x2812060000204000), + B(0x1001100200003100, 0x246240060A004004), + B(0xA00020A008002030, 0x2440C40000110B00), + B(0x80502104000C008, 0x8222200042100010), + B(0xC020200088014, 0x422094000000480), + B(0x1029002000001030, 0x8105841120000210), + B(0x49040D, 0x2310808A14042C0), + B(0x200040200080A02C, 0xB890290400080000), + B(0x2240180C0800002, 0x4151050280000100), + B(0x2240180C0800002, 0x4151050280000100), + B(0x8220224180420006, 0x4024501212011000), + B(0x1806810A0881000, 0x802002048400080), + B(0x400400A080842, 0x9305000401180000), + B(0x10008001444110, 0x4420401040041833), + B(0x2000002C02010E00, 0x400408D08009804), + B(0x69D008200020100, 0x100842240049021), + B(0x42C24450020000, 0xD38400880090884), + B(0x485800800100001, 0x2484086522018840), + B(0x900200020820042, 0x22302421400040C0), + B(0x50B0413001818000, 0x452014040800C40), + B(0x8004040021008, 0x20088A08000290), + B(0x600C000801000004, 0x8015084010200020), + B(0x208000C00, 0xE004804021100100), + B(0x20001000040204, 0x948110C0B2081), + B(0x268502400100021, 0x80A201840802080), + B(0x408C000008, 0x8822102408014), + B(0x1182080410100000, 0x608002046A0100), + B(0x100820A083C00002, 0x3100100410A00), + B(0x8401040000400124, 0x2000081288202200), + B(0xB014040003000800, 0x11960D1101210), + B(0x10040001900C000, 0x85603C1001280), + B(0x2000844000000100, 0x2000024C60800800), + B(0x120004234800900, 0x210010841040), + B(0x8010300040000002, 0x4200008222104100), + B(0x1000120402200100, 0x209080CC040108B4), + B(0x110049A00000800, 0x80000420022180A8), + B(0x80001C00080384, 0x1400101111081001), + B(0x8011200008100428, 0x2020000880800922), + B(0x10001000000204C8, 0x280C11104240), + B(0x50100C82C000500, 0x28000280618DD1), + B(0x8800498020000, 0x20500A0200320128), + B(0x20010104000860, 0x8021720186008), + B(0x4000000000100080, 0x35040084270C04), + B(0x4500080000800, 0x280100002482C842), + B(0x10400000000000, 0x20080051100130C2), + B(0x10400000000000, 0x20080051100130C2), + B(0x2000002110202014, 0x121004004004681), + B(0x400202001006D40, 0x82240082202424), + B(0x4500080000800, 0x280100002482C842), + B(0xC6000000D00804, 0x1050020C0081090C), + B(0x200080000000042, 0x10800661), + B(0x2000001011200200, 0x2A420000802A0222), + B(0x802020001202412, 0x2400404148426), + B(0x8000440801040002, 0x444002800010052A), + }; constexpr Bitboard HorseMagicInit[SQUARE_NB] = { B(0x3C080482A592000C, 0x540104000020000), B(0x2802C40008000420, 0x4A00000001818009), diff --git a/src/material.cpp b/src/material.cpp index 2a16c55..e806093 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -69,8 +69,8 @@ namespace { // Helper used to detect a given material distribution bool is_KFsPsK(const Position& pos, Color us) { - return pos.promotion_piece_types().size() == 1 - && pos.promotion_piece_types().find(FERS) != pos.promotion_piece_types().end() + return pos.promotion_piece_types(us).size() == 1 + && pos.promotion_piece_types(us).find(FERS) != pos.promotion_piece_types(us).end() && !more_than_one(pos.pieces(~us)) && (pos.count(us) || pos.count(us)) && !(pos.count(us) - pos.count(us) - pos.count(us) - pos.count(us)); diff --git a/src/movegen.cpp b/src/movegen.cpp index c17becb..e713f8e 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -40,7 +40,7 @@ namespace { b ^= square_bb(to) ^ kto ^ rto; } if (T == EN_PASSANT) - b ^= to - pawn_push(us); + b ^= pos.capture_square(to); if (pos.variant()->arrowGating) b &= moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from); if (pos.variant()->staticGating) @@ -73,7 +73,7 @@ namespace { if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { - for (PieceType pt : pos.promotion_piece_types()) + for (PieceType pt : pos.promotion_piece_types(c)) 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); PieceType pt = pos.promoted_piece_type(PAWN); @@ -114,48 +114,62 @@ namespace { template ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { + if (!pos.pieces(Us, PAWN)) + return moveList; + constexpr Color Them = ~Us; constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -pawn_push(Us); constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); - Bitboard TRank8BB = pos.sittuyin_promotion() ? Bitboard(0) : zone_bb(Us, pos.promotion_rank(), pos.max_rank()); - Bitboard TRank7BB = shift(TRank8BB); - // Define squares a pawn can pass during a double step - Bitboard TRank3BB = forward_ranks_bb(Us, relative_rank(Us, pos.double_step_rank_min(), pos.max_rank())) - & ~shift(forward_ranks_bb(Us, relative_rank(Us, pos.double_step_rank_max(), pos.max_rank()))); + const Bitboard promotionZone = pos.promotion_zone(Us); + const Bitboard standardPromotionZone = pos.sittuyin_promotion() ? Bitboard(0) : promotionZone; + const Bitboard doubleStepRegion = pos.double_step_region(Us); + const Bitboard tripleStepRegion = pos.triple_step_region(Us); - const Bitboard emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces()) & pos.board_bb(Us, PAWN); - const Bitboard enemies = (Type == EVASIONS ? (pos.checkers() & pos.non_sliding_riders() ? pos.pieces(Them) : pos.checkers()) - : Type == CAPTURES ? target : pos.pieces(Them)) & pos.board_bb(Us, PAWN); + const Bitboard pawns = pos.pieces(Us, PAWN); + const Bitboard movable = pos.board_bb(Us, PAWN) & ~pos.pieces(); + const Bitboard capturable = pos.board_bb(Us, PAWN) & pos.pieces(Them); - Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; - Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & (pos.mandatory_pawn_promotion() ? ~TRank7BB : AllSquares); + target = Type == EVASIONS ? target : AllSquares; - // Single and double pawn pushes, no promotions - if (Type != CAPTURES) - { - Bitboard b1 = shift(pawnsNotOn7) & emptySquares; - Bitboard b2 = pos.double_step_enabled() ? shift(b1 & TRank3BB) & emptySquares : Bitboard(0); + // Define single and double push, left and right capture, as well as respective promotion moves + Bitboard b1 = shift(pawns) & movable & target; + Bitboard b2 = shift(shift(pawns & doubleStepRegion) & movable) & movable & target; + Bitboard b3 = shift(shift(shift(pawns & tripleStepRegion) & movable) & movable) & movable & target; + Bitboard brc = shift(pawns) & capturable & target; + Bitboard blc = shift(pawns) & capturable & target; - if (Type == EVASIONS) // Consider only blocking squares - { - b1 &= target; - b2 &= target; - } + Bitboard b1p = b1 & standardPromotionZone; + Bitboard b2p = b2 & standardPromotionZone; + Bitboard b3p = b3 & standardPromotionZone; + Bitboard brcp = brc & standardPromotionZone; + Bitboard blcp = blc & standardPromotionZone; - if (Type == QUIET_CHECKS && pos.count(Them)) - { - // To make a quiet check, you either make a direct check by pushing a pawn - // or push a blocker pawn that is not on the same file as the enemy king. - // Discovered check promotion has been already generated amongst the captures. - Square ksq = pos.square(Them); - Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); - b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns); - b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); - } + // Restrict regions based on rules and move generation type + if (pos.mandatory_pawn_promotion()) + { + b1 &= ~standardPromotionZone; + b2 &= ~standardPromotionZone; + b3 &= ~standardPromotionZone; + brc &= ~standardPromotionZone; + blc &= ~standardPromotionZone; + } + if (Type == QUIET_CHECKS && pos.count(Them)) + { + // To make a quiet check, you either make a direct check by pushing a pawn + // or push a blocker pawn that is not on the same file as the enemy king. + // Discovered check promotion has been already generated amongst the captures. + Square ksq = pos.square(Them); + Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); + b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns); + b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); + } + + // Single and double pawn pushes, no promotions + if (Type != CAPTURES) + { while (b1) { Square to = pop_lsb(b1); @@ -167,48 +181,43 @@ namespace { Square to = pop_lsb(b2); moveList = make_move_and_gating(pos, moveList, Us, to - Up - Up, to); } + + while (b3) + { + Square to = pop_lsb(b3); + moveList = make_move_and_gating(pos, moveList, Us, to - Up - Up - Up, to); + } } // Promotions and underpromotions - if (pawnsOn7) - { - Bitboard b1 = shift(pawnsOn7) & enemies; - Bitboard b2 = shift(pawnsOn7) & enemies; - Bitboard b3 = shift(pawnsOn7) & emptySquares; + while (brcp) + moveList = make_promotions(pos, moveList, pop_lsb(brcp)); - if (Type == EVASIONS) - b3 &= target; + while (blcp) + moveList = make_promotions(pos, moveList, pop_lsb(blcp)); - while (b1) - moveList = make_promotions(pos, moveList, pop_lsb(b1)); + while (b1p) + moveList = make_promotions(pos, moveList, pop_lsb(b1p)); - while (b2) - moveList = make_promotions(pos, moveList, pop_lsb(b2)); + while (b2p) + moveList = make_promotions(pos, moveList, pop_lsb(b2p)); - while (b3) - moveList = make_promotions(pos, moveList, pop_lsb(b3)); - } + while (b3p) + moveList = make_promotions(pos, moveList, pop_lsb(b3p)); // Sittuyin promotions if (pos.sittuyin_promotion() && (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)) { - Bitboard pawns = pos.pieces(Us, PAWN); - // Pawns need to be on diagonals on opponent's half if there is more than one pawn - if (pos.count(Us) > 1) - pawns &= ( PseudoAttacks[Us][BISHOP][make_square(FILE_A, relative_rank(Us, RANK_1, pos.max_rank()))] - | PseudoAttacks[Us][BISHOP][make_square(pos.max_file(), relative_rank(Us, RANK_1, pos.max_rank()))]) - & forward_ranks_bb(Us, relative_rank(Us, Rank((pos.max_rank() - 1) / 2), pos.max_rank())); - while (pawns) + // Pawns need to be in promotion zone if there is more than one pawn + Bitboard promotionPawns = pos.count(Us) > 1 ? pawns & promotionZone : pawns; + while (promotionPawns) { - Square from = pop_lsb(pawns); - for (PieceType pt : pos.promotion_piece_types()) + Square from = pop_lsb(promotionPawns); + for (PieceType pt : pos.promotion_piece_types(Us)) { if (pos.promotion_limit(pt) && pos.promotion_limit(pt) <= pos.count(Us, pt)) continue; - Bitboard b = (pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from; - if (Type == EVASIONS) - b &= target; - + Bitboard b = ((pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from) & target; while (b) { Square to = pop_lsb(b); @@ -222,35 +231,32 @@ namespace { // Standard and en passant captures if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { - Bitboard b1 = shift(pawnsNotOn7) & enemies; - Bitboard b2 = shift(pawnsNotOn7) & enemies; - - while (b1) + while (brc) { - Square to = pop_lsb(b1); + Square to = pop_lsb(brc); moveList = make_move_and_gating(pos, moveList, Us, to - UpRight, to); } - while (b2) + while (blc) { - Square to = pop_lsb(b2); + Square to = pop_lsb(blc); moveList = make_move_and_gating(pos, moveList, Us, to - UpLeft, to); } - if (pos.ep_square() != SQ_NONE) + for (Bitboard epSquares = pos.ep_squares() & ~pos.pieces(); epSquares; ) { - assert(relative_rank(Them, rank_of(pos.ep_square()), pos.max_rank()) <= Rank(pos.double_step_rank_max() + 1)); + Square epSquare = pop_lsb(epSquares); - // An en passant capture cannot resolve a discovered check - if (Type == EVASIONS && (target & (pos.ep_square() + Up))) + // An en passant capture cannot resolve a discovered check (unless there non-sliding riders) + if (Type == EVASIONS && (target & (epSquare + Up)) && !pos.non_sliding_riders()) return moveList; - b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); + Bitboard b = pawns & pawn_attacks_bb(Them, epSquare); - assert(b1); + assert(b); - while (b1) - moveList = make_move_and_gating(pos, moveList, Us, pop_lsb(b1), pos.ep_square()); + while (b) + moveList = make_move_and_gating(pos, moveList, Us, pop_lsb(b), epSquare); } } @@ -258,7 +264,7 @@ namespace { } - template + template ExtMove* generate_moves(const Position& pos, ExtMove* moveList, PieceType Pt, Bitboard target) { assert(Pt != KING && Pt != PAWN); @@ -269,16 +275,25 @@ namespace { { Square from = pop_lsb(bb); - Bitboard b1 = ( (pos.attacks_from(Us, Pt, from) & pos.pieces()) - | (pos.moves_from(Us, Pt, from) & ~pos.pieces())) & target; + Bitboard attacks = pos.attacks_from(Us, Pt, from); + Bitboard quiets = pos.moves_from(Us, Pt, from); + Bitboard b = ( (attacks & pos.pieces()) + | (quiets & ~pos.pieces())); + Bitboard b1 = b & target; + Bitboard promotion_zone = pos.promotion_zone(Us); PieceType promPt = pos.promoted_piece_type(Pt); Bitboard b2 = promPt && (!pos.promotion_limit(promPt) || pos.promotion_limit(promPt) > pos.count(Us, promPt)) ? b1 : Bitboard(0); Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : Bitboard(0); + Bitboard pawnPromotions = pos.variant()->promotionPawnTypes[Us] & Pt ? b & (Type == EVASIONS ? target : ~pos.pieces(Us)) & promotion_zone : Bitboard(0); + Bitboard epSquares = pos.variant()->enPassantTypes[Us] & Pt ? attacks & ~quiets & pos.ep_squares() & ~pos.pieces() : Bitboard(0); + + // target squares considering pawn promotions + if (pawnPromotions && pos.mandatory_pawn_promotion()) + b1 &= ~pawnPromotions; // Restrict target squares considering promotion zone if (b2 | b3) { - Bitboard promotion_zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank()); if (pos.mandatory_piece_promotion()) b1 &= (promotion_zone & from ? Bitboard(0) : ~promotion_zone) | (pos.piece_promotion_on_capture() ? ~pos.pieces() : Bitboard(0)); // Exclude quiet promotions/demotions @@ -295,7 +310,7 @@ namespace { } } - if (Checks) + if (Type == QUIET_CHECKS) { b1 &= pos.check_squares(Pt); if (b2) @@ -314,6 +329,18 @@ namespace { // Piece demotions while (b3) *moveList++ = make(from, pop_lsb(b3)); + + // Pawn-style promotions + if ((Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) && pawnPromotions) + for (PieceType ptP : pos.promotion_piece_types(Us)) + 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); + + // En passant captures + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + while (epSquares) + moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(epSquares)); } return moveList; @@ -353,7 +380,7 @@ namespace { moveList = generate_pawn_moves(pos, moveList, target); for (PieceType pt : pos.piece_types()) if (pt != PAWN && pt != KING) - moveList = generate_moves(pos, moveList, pt, target); + moveList = generate_moves(pos, moveList, pt, target); // generate drops if (pos.piece_drops() && Type != CAPTURES && (pos.can_drop(Us, ALL_PIECES) || pos.two_boards())) for (PieceType pt : pos.piece_types()) diff --git a/src/parser.cpp b/src/parser.cpp index 28104e7..90a5406 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -123,14 +123,28 @@ namespace { return !ss.fail(); } + template void set(PieceType pt, T& target) { + target.insert(pt); + } + + template <> void set(PieceType pt, PieceType& target) { + target = pt; + } + + template <> void set(PieceType pt, PieceSet& target) { + target |= pt; + } + } // namespace template -template void VariantParser::parse_attribute(const std::string& key, T& target) { +template bool VariantParser::parse_attribute(const std::string& key, T& target) { const auto& it = config.find(key); if (it != config.end()) { bool valid = set(it->second, target); + if (DoCheck && !Current) + std::cerr << key << " - Deprecated option might be removed in future version." << std::endl; if (DoCheck && !valid) { std::string typeName = std::is_same() ? "int" @@ -146,34 +160,40 @@ template void VariantParser::parse_attribute(const std::strin : typeid(T).name(); std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl; } + return valid; } + return false; } template -void VariantParser::parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar) { +template bool VariantParser::parse_attribute(const std::string& key, T& target, std::string pieceToChar) { const auto& it = config.find(key); if (it != config.end()) { + target = T(); char token; size_t idx; std::stringstream ss(it->second); - if (ss >> token && (idx = token == '-' ? 0 : pieceToChar.find(toupper(token))) != std::string::npos) - target = PieceType(idx); - else if (DoCheck) + while (ss >> token && (idx = pieceToChar.find(toupper(token))) != std::string::npos) + set(PieceType(idx), target); + if (DoCheck && idx == std::string::npos && token != '-') std::cerr << key << " - Invalid piece type: " << token << std::endl; + return idx != std::string::npos || token == '-'; } + return false; } template Variant* VariantParser::parse() { Variant* v = new Variant(); v->reset_pieces(); - v->promotionPieceTypes = {}; return parse(v); } template Variant* VariantParser::parse(Variant* v) { + parse_attribute("maxRank", v->maxRank); + parse_attribute("maxFile", v->maxFile); // piece types for (PieceType pt = PAWN; pt <= KING; ++pt) { @@ -199,7 +219,15 @@ Variant* VariantParser::parse(Variant* v) { if (is_custom(pt)) { if (keyValue->second.size() > 1) + { v->customPiece[pt - CUSTOM_PIECES] = keyValue->second.substr(2); + // Is there an en passant flag in the Betza notation? + if (v->customPiece[pt - CUSTOM_PIECES].find('e') != std::string::npos) + { + v->enPassantTypes[WHITE] |= piece_set(pt); + v->enPassantTypes[BLACK] |= piece_set(pt); + } + } else if (DoCheck) std::cerr << name << " - Missing Betza move notation" << std::endl; } @@ -242,28 +270,58 @@ Variant* VariantParser::parse(Variant* v) { std::cerr << optionName << " - Invalid piece value for type: " << v->pieceToChar[idx] << std::endl; } } + + // Parse deprecate values for backwards compatibility + Rank promotionRank = RANK_8; + if (parse_attribute("promotionRank", promotionRank)) + { + for (Color c : {WHITE, BLACK}) + v->promotionRegion[c] = zone_bb(c, promotionRank, v->maxRank); + } + Rank doubleStepRank = RANK_2; + Rank doubleStepRankMin = RANK_2; + if ( parse_attribute("doubleStepRank", doubleStepRank) + || parse_attribute("doubleStepRankMin", doubleStepRankMin)) + { + for (Color c : {WHITE, BLACK}) + v->doubleStepRegion[c] = zone_bb(c, doubleStepRankMin, v->maxRank) + & ~forward_ranks_bb(c, relative_rank(c, doubleStepRank, v->maxRank)); + } + parse_attribute("whiteFlag", v->flagRegion[WHITE]); + parse_attribute("blackFlag", v->flagRegion[BLACK]); + + // Parse aliases + parse_attribute("pawnTypes", v->promotionPawnType[WHITE], v->pieceToChar); + parse_attribute("pawnTypes", v->promotionPawnType[BLACK], v->pieceToChar); + parse_attribute("pawnTypes", v->promotionPawnTypes[WHITE], v->pieceToChar); + parse_attribute("pawnTypes", v->promotionPawnTypes[BLACK], v->pieceToChar); + parse_attribute("pawnTypes", v->enPassantTypes[WHITE], v->pieceToChar); + parse_attribute("pawnTypes", v->enPassantTypes[BLACK], v->pieceToChar); + parse_attribute("pawnTypes", v->nMoveRuleTypes[WHITE], v->pieceToChar); + parse_attribute("pawnTypes", v->nMoveRuleTypes[BLACK], v->pieceToChar); + + // Parse the official config options parse_attribute("variantTemplate", v->variantTemplate); parse_attribute("pieceToCharTable", v->pieceToCharTable); parse_attribute("pocketSize", v->pocketSize); - parse_attribute("maxRank", v->maxRank); - parse_attribute("maxFile", v->maxFile); parse_attribute("chess960", v->chess960); parse_attribute("twoBoards", v->twoBoards); parse_attribute("startFen", v->startFen); - parse_attribute("promotionRank", v->promotionRank); - // promotion piece types - const auto& it_prom = config.find("promotionPieceTypes"); - if (it_prom != config.end()) - { - v->promotionPieceTypes = {}; - char token; - size_t idx = 0; - std::stringstream ss(it_prom->second); - while (ss >> token && ((idx = v->pieceToChar.find(toupper(token))) != std::string::npos)) - v->promotionPieceTypes.insert(PieceType(idx)); - if (DoCheck && idx == std::string::npos && token != '-') - std::cerr << "promotionPieceTypes - Invalid piece type: " << token << std::endl; - } + parse_attribute("promotionRegionWhite", v->promotionRegion[WHITE]); + parse_attribute("promotionRegionBlack", v->promotionRegion[BLACK]); + // Take the first promotionPawnTypes as the main promotionPawnType + parse_attribute("promotionPawnTypes", v->promotionPawnType[WHITE], v->pieceToChar); + parse_attribute("promotionPawnTypes", v->promotionPawnType[BLACK], v->pieceToChar); + parse_attribute("promotionPawnTypes", v->promotionPawnTypes[WHITE], v->pieceToChar); + parse_attribute("promotionPawnTypes", v->promotionPawnTypes[BLACK], v->pieceToChar); + parse_attribute("promotionPawnTypesWhite", v->promotionPawnType[WHITE], v->pieceToChar); + parse_attribute("promotionPawnTypesBlack", v->promotionPawnType[BLACK], v->pieceToChar); + parse_attribute("promotionPawnTypesWhite", v->promotionPawnTypes[WHITE], v->pieceToChar); + parse_attribute("promotionPawnTypesBlack", v->promotionPawnTypes[BLACK], v->pieceToChar); + parse_attribute("promotionPieceTypes", v->promotionPieceTypes[WHITE], v->pieceToChar); + parse_attribute("promotionPieceTypes", v->promotionPieceTypes[BLACK], v->pieceToChar); + parse_attribute("promotionPieceTypesWhite", v->promotionPieceTypes[WHITE], v->pieceToChar); + parse_attribute("promotionPieceTypesBlack", v->promotionPieceTypes[BLACK], v->pieceToChar); parse_attribute("sittuyinPromotion", v->sittuyinPromotion); // promotion limit const auto& it_prom_limit = config.find("promotionLimit"); @@ -299,9 +357,15 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("blastOnCapture", v->blastOnCapture); parse_attribute("petrifyOnCapture", v->petrifyOnCapture); parse_attribute("doubleStep", v->doubleStep); - parse_attribute("doubleStepRank", v->doubleStepRank); - parse_attribute("doubleStepRankMin", v->doubleStepRankMin); + parse_attribute("doubleStepRegionWhite", v->doubleStepRegion[WHITE]); + parse_attribute("doubleStepRegionBlack", v->doubleStepRegion[BLACK]); + parse_attribute("tripleStepRegionWhite", v->tripleStepRegion[WHITE]); + parse_attribute("tripleStepRegionBlack", v->tripleStepRegion[BLACK]); parse_attribute("enPassantRegion", v->enPassantRegion); + parse_attribute("enPassantTypes", v->enPassantTypes[WHITE], v->pieceToChar); + parse_attribute("enPassantTypes", v->enPassantTypes[BLACK], v->pieceToChar); + parse_attribute("enPassantTypesWhite", v->enPassantTypes[WHITE], v->pieceToChar); + parse_attribute("enPassantTypesBlack", v->enPassantTypes[BLACK], v->pieceToChar); parse_attribute("castling", v->castling); parse_attribute("castlingDroppedPiece", v->castlingDroppedPiece); parse_attribute("castlingKingsideFile", v->castlingKingsideFile); @@ -347,6 +411,10 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("soldierPromotionRank", v->soldierPromotionRank); parse_attribute("flipEnclosedPieces", v->flipEnclosedPieces); // game end + parse_attribute("nMoveRuleTypes", v->nMoveRuleTypes[WHITE], v->pieceToChar); + parse_attribute("nMoveRuleTypes", v->nMoveRuleTypes[BLACK], v->pieceToChar); + parse_attribute("nMoveRuleTypesWhite", v->nMoveRuleTypes[WHITE], v->pieceToChar); + parse_attribute("nMoveRuleTypesBlack", v->nMoveRuleTypes[BLACK], v->pieceToChar); parse_attribute("nMoveRule", v->nMoveRule); parse_attribute("nFoldRule", v->nFoldRule); parse_attribute("nFoldValue", v->nFoldValue); @@ -363,6 +431,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("extinctionValue", v->extinctionValue); parse_attribute("extinctionClaim", v->extinctionClaim); parse_attribute("extinctionPseudoRoyal", v->extinctionPseudoRoyal); + parse_attribute("dupleCheck", v->dupleCheck); // extinction piece types const auto& it_ext = config.find("extinctionPieceTypes"); if (it_ext != config.end()) @@ -379,8 +448,8 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("extinctionPieceCount", v->extinctionPieceCount); parse_attribute("extinctionOpponentPieceCount", v->extinctionOpponentPieceCount); parse_attribute("flagPiece", v->flagPiece, v->pieceToChar); - parse_attribute("whiteFlag", v->whiteFlag); - parse_attribute("blackFlag", v->blackFlag); + parse_attribute("flagRegionWhite", v->flagRegion[WHITE]); + parse_attribute("flagRegionBlack", v->flagRegion[BLACK]); parse_attribute("flagMove", v->flagMove); parse_attribute("checkCounting", v->checkCounting); parse_attribute("connectN", v->connectN); @@ -435,8 +504,6 @@ Variant* VariantParser::parse(Variant* v) { // Contradictory options if (!v->checking && v->checkCounting) std::cerr << "checkCounting=true requires checking=true." << std::endl; - if (v->doubleStep && v->doubleStepRankMin > v->doubleStepRank) - std::cerr << "Inconsistent settings: doubleStepRankMin > doubleStepRank." << std::endl; if (v->castling && v->castlingRank > v->maxRank) std::cerr << "Inconsistent settings: castlingRank > maxRank." << std::endl; if (v->castling && v->castlingQueensideFile > v->castlingKingsideFile) @@ -460,11 +527,11 @@ Variant* VariantParser::parse(Variant* v) { if (!is_custom(v->kingType)) { const PieceInfo* pi = pieceMap.find(v->kingType)->second; - if ( pi->hopper[MODALITY_QUIET].size() - || pi->hopper[MODALITY_CAPTURE].size() - || std::any_of(pi->steps[MODALITY_CAPTURE].begin(), - pi->steps[MODALITY_CAPTURE].end(), - [](const std::pair& d) { return d.second; })) + if ( pi->hopper[0][MODALITY_QUIET].size() + || pi->hopper[0][MODALITY_CAPTURE].size() + || std::any_of(pi->steps[0][MODALITY_CAPTURE].begin(), + pi->steps[0][MODALITY_CAPTURE].end(), + [](const std::pair& d) { return d.second; })) std::cerr << piece_name(v->kingType) << " is not supported as kingType." << std::endl; } } diff --git a/src/parser.h b/src/parser.h index dd81647..36b208e 100644 --- a/src/parser.h +++ b/src/parser.h @@ -50,8 +50,8 @@ public: private: Config config; - template void parse_attribute(const std::string& key, T& target); - void parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar); + template bool parse_attribute(const std::string& key, T& target); + template bool parse_attribute(const std::string& key, T& target, std::string pieceToChar); }; } // namespace Stockfish diff --git a/src/pawns.cpp b/src/pawns.cpp index e4d6052..00a533e 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -165,7 +165,7 @@ namespace { // Passed pawns will be properly scored later in evaluation when we have // full attack info. - if (passed && is_ok(s + Up) && !pos.sittuyin_promotion()) + if (passed && pos.promotion_square(Us, s) != SQ_NONE && !pos.sittuyin_promotion()) e->passedPawns[Us] |= s; // Score this pawn diff --git a/src/piece.cpp b/src/piece.cpp index 4562738..bdbcf42 100644 --- a/src/piece.cpp +++ b/src/piece.cpp @@ -29,7 +29,7 @@ PieceMap pieceMap; // Global object namespace { - std::map>> leaperAtoms = { + const std::map>> leaperAtoms = { {'W', {std::make_pair(1, 0)}}, {'F', {std::make_pair(1, 1)}}, {'D', {std::make_pair(2, 0)}}, @@ -43,7 +43,7 @@ namespace { {'G', {std::make_pair(3, 3)}}, {'K', {std::make_pair(1, 0), std::make_pair(1, 1)}}, }; - std::map>> riderAtoms = { + const std::map>> riderAtoms = { {'R', {std::make_pair(1, 0)}}, {'B', {std::make_pair(1, 1)}}, {'Q', {std::make_pair(1, 0), std::make_pair(1, 1)}}, @@ -60,6 +60,7 @@ namespace { bool hopper = false; bool rider = false; bool lame = false; + bool initial = false; int distance = 0; std::vector prelimDirections = {}; for (std::string::size_type i = 0; i < betza.size(); i++) @@ -79,6 +80,9 @@ namespace { // Lame leaper else if (c == 'n') lame = true; + // Initial move + else if (c == 'i') + initial = true; // Directional modifiers else if (verticals.find(c) != std::string::npos || horizontals.find(c) != std::string::npos) { @@ -138,9 +142,9 @@ namespace { // Add moves for (auto modality : moveModalities) { - auto& v = hopper ? p->hopper[modality] - : rider ? p->slider[modality] - : p->steps[modality]; + auto& v = hopper ? p->hopper[initial][modality] + : rider ? p->slider[initial][modality] + : p->steps[initial][modality]; auto has_dir = [&](std::string s) { return std::find(directions.begin(), directions.end(), s) != directions.end(); }; @@ -167,6 +171,9 @@ namespace { prelimDirections.clear(); hopper = false; rider = false; + lame = false; + initial = false; + distance = 0; } } return p; diff --git a/src/piece.h b/src/piece.h index f189b91..8d14dfd 100644 --- a/src/piece.h +++ b/src/piece.h @@ -34,9 +34,9 @@ enum MoveModality {MODALITY_QUIET, MODALITY_CAPTURE, MOVE_MODALITY_NB}; struct PieceInfo { std::string name = ""; std::string betza = ""; - std::map steps[MOVE_MODALITY_NB] = {}; - std::map slider[MOVE_MODALITY_NB] = {}; - std::map hopper[MOVE_MODALITY_NB] = {}; + std::map steps[2][MOVE_MODALITY_NB] = {}; + std::map slider[2][MOVE_MODALITY_NB] = {}; + std::map hopper[2][MOVE_MODALITY_NB] = {}; }; struct PieceMap : public std::map { diff --git a/src/position.cpp b/src/position.cpp index f9813e6..99966b3 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -332,7 +332,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, ss >> token; // 3-4. Skip parsing castling and en passant flags if not present - st->epSquare = SQ_NONE; + st->epSquares = 0; st->castlingKingSquare[WHITE] = st->castlingKingSquare[BLACK] = SQ_NONE; if (!isdigit(ss.peek()) && !sfen) { @@ -404,27 +404,33 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, // 4. En passant square. // Ignore if square is invalid or not on side to move relative rank 6. - else if ( ((ss >> col) && (col >= 'a' && col <= 'a' + max_file())) - && ((ss >> row) && (row >= '1' && row <= '1' + max_rank()))) - { - st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + else + while ( ((ss >> col) && (col >= 'a' && col <= 'a' + max_file())) + && ((ss >> row) && (row >= '1' && row <= '1' + max_rank()))) + { + Square epSquare = make_square(File(col - 'a'), Rank(row - '1')); #ifdef LARGEBOARDS - // Consider different rank numbering in CECP - if (max_rank() == RANK_10 && CurrentProtocol == XBOARD) - st->epSquare += NORTH; + // Consider different rank numbering in CECP + if (max_rank() == RANK_10 && CurrentProtocol == XBOARD) + epSquare += NORTH; #endif - // En passant square will be considered only if - // a) side to move have a pawn threatening epSquare - // b) there is an enemy pawn in front of epSquare - // c) there is no piece on epSquare or behind epSquare - bool enpassant; - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) - && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); - if (!enpassant) - st->epSquare = SQ_NONE; - } + // En passant square will be considered only if + // epSquare is within enPassantRegion and + // 1) variant has non-standard rules + // or + // 2) + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn one or two (for triple steps) squares in front of epSquare + // c) there is no piece on epSquare or behind epSquare + if ( (var->enPassantRegion & epSquare) + && ( !var->fastAttacks + || ( pawn_attacks_bb(~sideToMove, epSquare) & pieces(sideToMove, PAWN) + && ( (pieces(~sideToMove, PAWN) & (epSquare + pawn_push(~sideToMove))) + || (pieces(~sideToMove, PAWN) & (epSquare + 2 * pawn_push(~sideToMove)))) + && !(pieces() & (epSquare | (epSquare + pawn_push(sideToMove))))))) + st->epSquares |= epSquare; + } } // Check counter for nCheck @@ -560,9 +566,11 @@ void Position::set_check_info(StateInfo* si) const { si->legalCapture = NO_VALUE; if (var->extinctionPseudoRoyal) { + si->pseudoRoyalCandidates = 0; si->pseudoRoyals = 0; for (PieceType pt : extinction_piece_types()) { + si->pseudoRoyalCandidates |= pieces(pt); if (count(sideToMove, pt) <= var->extinctionPieceCount + 1) si->pseudoRoyals |= pieces(sideToMove, pt); if (count(~sideToMove, pt) <= var->extinctionPieceCount + 1) @@ -603,8 +611,8 @@ void Position::set_state(StateInfo* si) const { si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; } - if (si->epSquare != SQ_NONE) - si->key ^= Zobrist::enpassant[file_of(si->epSquare)]; + for (Bitboard b = si->epSquares; b; ) + si->key ^= Zobrist::enpassant[file_of(pop_lsb(b))]; if (sideToMove == BLACK) si->key ^= Zobrist::side; @@ -774,8 +782,15 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string // Counting limit or ep-square if (st->countingLimit) ss << " " << counting_limit(countStarted) << " "; + else if (!ep_squares()) + ss << " - "; else - ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(*this, ep_square()) + " "); + { + ss << " "; + for (Bitboard b = ep_squares(); b; ) + ss << UCI::square(*this, pop_lsb(b)); + ss << " "; + } // Check count if (check_counting()) @@ -1039,7 +1054,7 @@ bool Position::legal(Move m) const { } // No legal moves from target square - if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(PseudoMoves[us][type_of(moved_piece(m))][to] & board_bb())) + if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(PseudoMoves[0][us][type_of(moved_piece(m))][to] & board_bb())) return false; // Illegal king passing move @@ -1071,7 +1086,7 @@ bool Position::legal(Move m) const { occupied = (pieces() ^ from ^ to) | kto | rto; } if (type_of(m) == EN_PASSANT) - occupied &= ~square_bb(kto - pawn_push(us)); + occupied &= ~square_bb(capture_square(kto)); if (capture(m) && blast_on_capture()) occupied &= ~((attacks_bb(kto) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | kto); Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); @@ -1079,7 +1094,14 @@ bool Position::legal(Move m) const { if (is_ok(from) && (pseudoRoyals & from)) pseudoRoyals ^= square_bb(from) ^ kto; if (type_of(m) == PROMOTION && extinction_piece_types().find(promotion_type(m)) != extinction_piece_types().end()) - pseudoRoyals |= kto; + { + if (count(sideToMove, promotion_type(m)) > extinction_piece_count()) + // increase in count leads to loss of pseudo-royalty + pseudoRoyals &= ~pieces(sideToMove, promotion_type(m)); + else + // promoted piece is pseudo-royal + pseudoRoyals |= kto; + } // Self-explosions are illegal if (pseudoRoyals & ~occupied) return false; @@ -1093,6 +1115,26 @@ bool Position::legal(Move m) const { && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto)))) return false; } + // Look for duple check + if (var->dupleCheck) + { + Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(sideToMove); + if (is_ok(from) && (pseudoRoyalCandidates & from)) + pseudoRoyalCandidates ^= square_bb(from) ^ kto; + if (type_of(m) == PROMOTION && extinction_piece_types().find(promotion_type(m)) != extinction_piece_types().end()) + pseudoRoyalCandidates |= kto; + bool allCheck = bool(pseudoRoyalCandidates); + while (allCheck && pseudoRoyalCandidates) + { + Square sr = pop_lsb(pseudoRoyalCandidates); + // Touching pseudo-royal pieces are immune + if (!( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto))))) + allCheck = false; + } + if (allCheck) + return false; + } } // Petrifying the king is illegal @@ -1105,12 +1147,10 @@ bool Position::legal(Move m) const { if (type_of(m) == EN_PASSANT && count(us)) { Square ksq = square(us); - Square capsq = to - pawn_push(us); + Square capsq = capture_square(to); Bitboard occupied = (pieces() ^ from ^ capsq) | to; - assert(to == ep_square()); - assert(moved_piece(m) == make_piece(us, PAWN)); - assert(piece_on(capsq) == make_piece(~us, PAWN)); + assert(ep_squares() & to); assert(piece_on(to) == NO_PIECE); return !(attackers_to(ksq, occupied, ~us) & occupied); @@ -1225,7 +1265,7 @@ bool Position::pseudo_legal(const Move m) const { // Handle the case where a mandatory piece promotion/demotion is not taken if ( mandatory_piece_promotion() && (is_promoted(from) ? piece_demotion() : promoted_piece_type(type_of(pc)) != NO_PIECE_TYPE) - && (zone_bb(us, promotion_rank(), max_rank()) & (SquareBB[from] | to)) + && (promotion_zone(us) & (SquareBB[from] | to)) && (!piece_promotion_on_capture() || capture(m))) return false; @@ -1247,17 +1287,17 @@ bool Position::pseudo_legal(const Move m) const { { // We have already handled promotion moves, so destination // cannot be on the 8th/1st rank. - if (mandatory_pawn_promotion() && rank_of(to) == relative_rank(us, promotion_rank(), max_rank()) && !sittuyin_promotion()) + if (mandatory_pawn_promotion() && (promotion_zone(us) & to) && !sittuyin_promotion()) return false; if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture && !((from + pawn_push(us) == to) && !(pieces() & to)) // Not a single push && !( (from + 2 * pawn_push(us) == to) // Not a double push - && ( relative_rank(us, from, max_rank()) <= double_step_rank_max() - && relative_rank(us, from, max_rank()) >= double_step_rank_min()) - && !(pieces() & to) - && !(pieces() & (to - pawn_push(us))) - && double_step_enabled())) + && (double_step_region(us) & from) + && !(pieces() & (to | (to - pawn_push(us))))) + && !( (from + 3 * pawn_push(us) == to) // Not a triple push + && (triple_step_region(us) & from) + && !(pieces() & (to | (to - pawn_push(us)) | (to - 2 * pawn_push(us)))))) return false; } else if (!((capture(m) ? attacks_from(us, type_of(pc), from) : moves_from(us, type_of(pc), from)) & to)) @@ -1379,7 +1419,7 @@ bool Position::gives_check(Move m) const { // the captured pawn. case EN_PASSANT: { - Square capsq = make_square(file_of(to), rank_of(from)); + Square capsq = capture_square(to); Bitboard b = (pieces() ^ from ^ capsq) | to; return attackers_to(square(~sideToMove), b) & pieces(sideToMove) & b; @@ -1445,7 +1485,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Square from = from_sq(m); Square to = to_sq(m); Piece pc = moved_piece(m); - Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + Piece captured = piece_on(type_of(m) == EN_PASSANT ? capture_square(to) : to); if (to == from) { assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass())); @@ -1478,25 +1518,20 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { Square capsq = to; + if (type_of(m) == EN_PASSANT) + { + capsq = capture_square(to); + st->captureSquare = capsq; + + assert(st->epSquares & to); + assert(var->enPassantRegion & to); + assert(piece_on(to) == NO_PIECE); + } + // If the captured piece is a pawn, update pawn hash key, otherwise // update non-pawn material. if (type_of(captured) == PAWN) - { - if (type_of(m) == EN_PASSANT) - { - capsq -= pawn_push(us); - - assert(pc == make_piece(us, PAWN)); - assert(to == st->epSquare); - assert((var->enPassantRegion & to) - && relative_rank(~us, to, max_rank()) <= Rank(double_step_rank_max() + 1) - && relative_rank(~us, to, max_rank()) > double_step_rank_min()); - assert(piece_on(to) == NO_PIECE); - assert(piece_on(capsq) == make_piece(them, PAWN)); - } - st->pawnKey ^= Zobrist::psq[captured][capsq]; - } else st->nonPawnMaterial[them] -= PieceValue[MG][captured]; @@ -1519,7 +1554,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured : unpromotedCaptured ? ~unpromotedCaptured - : make_piece(~color_of(captured), PAWN); + : make_piece(~color_of(captured), promotion_pawn_type(color_of(captured))); 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)]]; @@ -1554,12 +1589,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { else k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - // Reset en passant square - if (st->epSquare != SQ_NONE) - { - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - st->epSquare = SQ_NONE; - } + // Reset en passant squares + while (st->epSquares) + k ^= Zobrist::enpassant[file_of(pop_lsb(st->epSquares))]; // Update castling rights if needed if (type_of(m) != DROP && !is_pass(m) && st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) @@ -1663,24 +1695,14 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) { - // Set en passant square if the moved pawn can be captured - if ( type_of(m) != DROP - && std::abs(int(to) - int(from)) == 2 * NORTH - && (var->enPassantRegion & (to - pawn_push(us))) - && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)) - && !(wall_gating() && gating_square(m) == to - pawn_push(us))) - { - st->epSquare = to - pawn_push(us); - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - } - - else if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION) + if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION) { Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(PAWN)); - assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion()); + assert((promotion_zone(us) & to) || sittuyin_promotion()); assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING); + st->promotionPawn = piece_on(to); remove_piece(to); put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE); @@ -1706,18 +1728,38 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[us] += PieceValue[MG][promotion]; } + // Set en passant square(s) if the moved pawn can be captured + else if ( type_of(m) != DROP + && ( std::abs(int(to) - int(from)) == 2 * NORTH + || std::abs(int(to) - int(from)) == 3 * NORTH)) + { + if ( (var->enPassantRegion & (to - pawn_push(us))) + && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)) + && !(wall_gating() && gating_square(m) == to - pawn_push(us))) + { + st->epSquares |= to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(to)]; + } + if ( std::abs(int(to) - int(from)) == 3 * NORTH + && (var->enPassantRegion & (to - 2 * pawn_push(us))) + && (pawn_attacks_bb(us, to - 2 * pawn_push(us)) & pieces(them, PAWN)) + && !(wall_gating() && gating_square(m) == to - 2 * pawn_push(us))) + { + st->epSquares |= to - 2 * pawn_push(us); + k ^= Zobrist::enpassant[file_of(to)]; + } + } + // Update pawn hash key st->pawnKey ^= (type_of(m) != DROP ? Zobrist::psq[pc][from] : 0) ^ Zobrist::psq[pc][to]; - - // Reset rule 50 draw counter - st->rule50 = 0; } - else if (type_of(m) == PIECE_PROMOTION) + else if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION) { - Piece promotion = make_piece(us, promoted_piece_type(type_of(pc))); + Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(type_of(pc))); + st->promotionPawn = piece_on(to); remove_piece(to); - put_piece(promotion, to, true, pc); + put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE); if (Eval::useNNUE) { @@ -1766,6 +1808,19 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update material st->nonPawnMaterial[us] += PieceValue[MG][demotion] - PieceValue[MG][pc]; } + // Set en passant square(s) if the moved piece can be captured + else if ( type_of(m) != DROP + && ((PseudoMoves[1][us][type_of(pc)][from] & ~PseudoMoves[0][us][type_of(pc)][from]) & to)) + { + assert(type_of(pc) != PAWN); + st->epSquares = between_bb(from, to) & var->enPassantRegion; + for (Bitboard b = st->epSquares; b; ) + k ^= Zobrist::enpassant[file_of(pop_lsb(b))]; + } + + // Reset rule 50 draw counter + if (var->nMoveRuleTypes[us] & type_of(pc)) + st->rule50 = 0; // Set capture piece st->capturedPiece = captured; @@ -2014,12 +2069,13 @@ void Position::undo_move(Move m) { if (type_of(m) == PROMOTION) { - assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion()); + assert((promotion_zone(us) & to) || sittuyin_promotion()); assert(type_of(pc) == promotion_type(m)); assert(type_of(pc) >= KNIGHT && type_of(pc) < KING); + assert(type_of(st->promotionPawn) == promotion_pawn_type(us) || !captures_to_hand()); remove_piece(to); - pc = make_piece(us, PAWN); + pc = st->promotionPawn; put_piece(pc, to); } else if (type_of(m) == PIECE_PROMOTION) @@ -2055,19 +2111,17 @@ void Position::undo_move(Move m) { if (type_of(m) == EN_PASSANT) { - capsq -= pawn_push(us); + capsq = st->captureSquare; - assert(type_of(pc) == PAWN); - assert(to == st->previous->epSquare); - assert(relative_rank(~us, to, max_rank()) <= Rank(double_step_rank_max() + 1)); + assert(st->previous->epSquares & to); + assert(var->enPassantRegion & to); assert(piece_on(capsq) == NO_PIECE); - assert(st->capturedPiece == make_piece(~us, PAWN)); } 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), PAWN)) + : make_piece(~color_of(st->capturedPiece), promotion_pawn_type(us))) : ~st->capturedPiece); } } @@ -2145,11 +2199,8 @@ void Position::do_null_move(StateInfo& newSt) { st->accumulator.computed[WHITE] = false; st->accumulator.computed[BLACK] = false; - if (st->epSquare != SQ_NONE) - { - st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; - st->epSquare = SQ_NONE; - } + while (st->epSquares) + st->key ^= Zobrist::enpassant[file_of(pop_lsb(st->epSquares))]; st->key ^= Zobrist::side; prefetch(TT.first_entry(key())); @@ -2195,7 +2246,7 @@ Key Position::key_after(Move m) const { k ^= Zobrist::psq[captured][to]; if (captures_to_hand()) { - Piece removeFromHand = !drop_loop() && is_promoted(to) ? make_piece(~color_of(captured), PAWN) : ~captured; + 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)]]; } @@ -2955,8 +3006,7 @@ bool Position::pos_is_ok() const { if ( (sideToMove != WHITE && sideToMove != BLACK) || (count(WHITE) && piece_on(square(WHITE)) != make_piece(WHITE, KING)) || (count(BLACK) && piece_on(square(BLACK)) != make_piece(BLACK, KING)) - || ( ep_square() != SQ_NONE - && relative_rank(~sideToMove, ep_square(), max_rank()) > Rank(double_step_rank_max() + 1))) + || (ep_squares() & ~var->enPassantRegion)) assert(0 && "pos_is_ok: Default"); if (Fast) diff --git a/src/position.h b/src/position.h index fbdaff7..fa8ffd6 100644 --- a/src/position.h +++ b/src/position.h @@ -52,7 +52,7 @@ struct StateInfo { int countingPly; int countingLimit; CheckCount checksRemaining[COLOR_NB]; - Square epSquare; + Bitboard epSquares; Square castlingKingSquare[COLOR_NB]; Bitboard wallSquares; Bitboard gatesBB[COLOR_NB]; @@ -69,8 +69,11 @@ struct StateInfo { Bitboard pinners[COLOR_NB]; Bitboard checkSquares[PIECE_TYPE_NB]; Piece capturedPiece; + Square captureSquare; // when != to_sq, e.g., en passant + Piece promotionPawn; Bitboard nonSlidingRiders; Bitboard flippedPieces; + Bitboard pseudoRoyalCandidates; Bitboard pseudoRoyals; OptBool legalCapture; bool capturedpromoted; @@ -125,8 +128,10 @@ public: const std::set& piece_types() const; const std::string& piece_to_char() const; const std::string& piece_to_char_synonyms() const; - Rank promotion_rank() const; - const std::set >& promotion_piece_types() const; + Bitboard promotion_zone(Color c) const; + Square promotion_square(Color c, Square s) const; + PieceType promotion_pawn_type(Color c) const; + const std::set >& promotion_piece_types(Color c) const; bool sittuyin_promotion() const; int promotion_limit(PieceType pt) const; PieceType promoted_piece_type(PieceType pt) const; @@ -136,9 +141,8 @@ public: bool piece_demotion() const; bool blast_on_capture() const; bool endgame_eval() const; - bool double_step_enabled() const; - Rank double_step_rank_max() const; - Rank double_step_rank_min() const; + Bitboard double_step_region(Color c) const; + Bitboard triple_step_region(Color c) const; bool castling_enabled() const; bool castling_dropped_piece() const; File castling_kingside_file() const; @@ -220,7 +224,7 @@ public: Bitboard non_sliding_riders() const; Piece piece_on(Square s) const; Piece unpromoted_piece_on(Square s) const; - Square ep_square() const; + Bitboard ep_squares() const; Square castling_king_square(Color c) const; Bitboard gates(Color c) const; bool empty(Square s) const; @@ -260,6 +264,7 @@ public: bool virtual_drop(Move m) const; bool capture(Move m) const; bool capture_or_promotion(Move m) const; + Square capture_square(Square to) const; bool gives_check(Move m) const; Piece moved_piece(Move m) const; Piece captured_piece() const; @@ -418,14 +423,25 @@ inline const std::string& Position::piece_to_char_synonyms() const { return var->pieceToCharSynonyms; } -inline Rank Position::promotion_rank() const { +inline Bitboard Position::promotion_zone(Color c) const { assert(var != nullptr); - return var->promotionRank; + return var->promotionRegion[c]; } -inline const std::set >& Position::promotion_piece_types() const { +inline Square Position::promotion_square(Color c, Square s) const { assert(var != nullptr); - return var->promotionPieceTypes; + Bitboard b = promotion_zone(c) & forward_file_bb(c, s) & board_bb(); + return !b ? SQ_NONE : c == WHITE ? lsb(b) : msb(b); +} + +inline PieceType Position::promotion_pawn_type(Color c) const { + assert(var != nullptr); + return var->promotionPawnType[c]; +} + +inline const std::set >& Position::promotion_piece_types(Color c) const { + assert(var != nullptr); + return var->promotionPieceTypes[c]; } inline bool Position::sittuyin_promotion() const { @@ -473,19 +489,14 @@ inline bool Position::endgame_eval() const { return var->endgameEval && !count_in_hand(ALL_PIECES) && count() == 2; } -inline bool Position::double_step_enabled() const { - assert(var != nullptr); - return var->doubleStep; -} - -inline Rank Position::double_step_rank_max() const { +inline Bitboard Position::double_step_region(Color c) const { assert(var != nullptr); - return var->doubleStepRank; + return var->doubleStepRegion[c]; } -inline Rank Position::double_step_rank_min() const { +inline Bitboard Position::triple_step_region(Color c) const { assert(var != nullptr); - return var->doubleStepRankMin; + return var->tripleStepRegion[c]; } inline bool Position::castling_enabled() const { @@ -643,7 +654,7 @@ inline Bitboard Position::drop_region(Color c, PieceType pt) const { if (pt == PAWN) { if (!var->promotionZonePawnDrops) - b &= ~zone_bb(c, promotion_rank(), max_rank()); + b &= ~promotion_zone(c); if (!first_rank_pawn_drops()) b &= ~rank_bb(relative_rank(c, RANK_1, max_rank())); } @@ -798,6 +809,22 @@ inline Value Position::stalemate_value(int ply) const { && attackers_to(sr, ~sideToMove)) return convert_mate_value(var->checkmateValue, ply); } + // Look for duple check + if (var->dupleCheck) + { + Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(sideToMove); + bool allCheck = bool(pseudoRoyalCandidates); + while (allCheck && pseudoRoyalCandidates) + { + Square sr = pop_lsb(pseudoRoyalCandidates); + // Touching pseudo-royal pieces are immune + if (!( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && attackers_to(sr, ~sideToMove))) + allCheck = false; + } + if (allCheck) + return convert_mate_value(var->checkmateValue, ply); + } } return convert_mate_value(var->stalemateValue, ply); } @@ -894,7 +921,7 @@ inline PieceType Position::capture_the_flag_piece() const { inline Bitboard Position::capture_the_flag(Color c) const { assert(var != nullptr); - return c == WHITE ? var->whiteFlag : var->blackFlag; + return var->flagRegion[c]; } inline bool Position::flag_move() const { @@ -1022,8 +1049,8 @@ inline Square Position::square(Color c, PieceType pt) const { return lsb(pieces(c, pt)); } -inline Square Position::ep_square() const { - return st->epSquare; +inline Bitboard Position::ep_squares() const { + return st->epSquares; } inline Square Position::castling_king_square(Color c) const { @@ -1095,6 +1122,9 @@ inline Bitboard Position::moves_from(Color c, PieceType pt, Square s) const { PieceType movePt = pt == KING ? king_type() : pt; Bitboard b = moves_bb(c, movePt, s, byTypeBB[ALL_PIECES]); + // Add initial moves + if (double_step_region(c) & s) + b |= moves_bb(c, movePt, s, byTypeBB[ALL_PIECES]); // Xiangqi soldier if (pt == SOLDIER && !(promoted_soldiers(c) & s)) b &= file_bb(file_of(s)); @@ -1233,6 +1263,13 @@ inline bool Position::capture(Move m) const { return (!empty(to_sq(m)) && type_of(m) != CASTLING && from_sq(m) != to_sq(m)) || type_of(m) == EN_PASSANT; } +inline Square Position::capture_square(Square to) const { + assert(is_ok(to)); + // The capture square of en passant is either the marked ep piece or the closest piece behind the target square + Bitboard b = ep_squares() & pieces() ? ep_squares() & pieces() : pieces(~sideToMove) & forward_file_bb(~sideToMove, to); + return sideToMove == WHITE ? msb(b) : lsb(b); +} + 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)); diff --git a/src/psqt.cpp b/src/psqt.cpp index d674edc..afa1fde 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -156,16 +156,16 @@ constexpr Score PBonus[RANK_NB][FILE_NB] = Value piece_value(Phase phase, PieceType pt) { const PieceInfo* pi = pieceMap.find(pt)->second; - int v0 = (phase == MG ? 55 : 60) * pi->steps[MODALITY_CAPTURE].size() - + (phase == MG ? 30 : 40) * pi->steps[MODALITY_QUIET].size() - + (phase == MG ? 185 : 180) * pi->slider[MODALITY_CAPTURE].size() - + (phase == MG ? 55 : 50) * pi->slider[MODALITY_QUIET].size() + int v0 = (phase == MG ? 55 : 60) * pi->steps[0][MODALITY_CAPTURE].size() + + (phase == MG ? 30 : 40) * pi->steps[0][MODALITY_QUIET].size() + + (phase == MG ? 185 : 180) * pi->slider[0][MODALITY_CAPTURE].size() + + (phase == MG ? 55 : 50) * pi->slider[0][MODALITY_QUIET].size() // Hoppers are more useful with more pieces on the board - + (phase == MG ? 100 : 80) * pi->hopper[MODALITY_CAPTURE].size() - + (phase == MG ? 80 : 60) * pi->hopper[MODALITY_QUIET].size() + + (phase == MG ? 100 : 80) * pi->hopper[0][MODALITY_CAPTURE].size() + + (phase == MG ? 80 : 60) * pi->hopper[0][MODALITY_QUIET].size() // Rook sliding directions are more valuable, especially in endgame - + (phase == MG ? 10 : 30) * std::count_if(pi->slider[MODALITY_CAPTURE].begin(), pi->slider[MODALITY_CAPTURE].end(), [](const std::pair& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; }) - + (phase == MG ? 30 : 45) * std::count_if(pi->slider[MODALITY_QUIET].begin(), pi->slider[MODALITY_QUIET].end(), [](const std::pair& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; }); + + (phase == MG ? 10 : 30) * std::count_if(pi->slider[0][MODALITY_CAPTURE].begin(), pi->slider[0][MODALITY_CAPTURE].end(), [](const std::pair& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; }) + + (phase == MG ? 30 : 45) * std::count_if(pi->slider[0][MODALITY_QUIET].begin(), pi->slider[0][MODALITY_QUIET].end(), [](const std::pair& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; }); return Value(v0 * exp(double(v0) / 10000)); } @@ -196,7 +196,7 @@ void init(const Variant* v) { } Value maxPromotion = VALUE_ZERO; - for (PieceType pt : v->promotionPieceTypes) + for (PieceType pt : v->promotionPieceTypes[WHITE]) maxPromotion = std::max(maxPromotion, PieceValue[EG][pt]); for (PieceType pt = PAWN; pt <= KING; ++pt) @@ -206,7 +206,7 @@ void init(const Variant* v) { Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); // Consider promotion types in pawn score - if (pt == PAWN) + if (pt == v->promotionPawnType[WHITE]) { score -= make_score(0, (QueenValueEg - maxPromotion) / 100); if (v->blastOnCapture) @@ -214,9 +214,9 @@ void init(const Variant* v) { } const PieceInfo* pi = pieceMap.find(pt)->second; - bool isSlider = pi->slider[MODALITY_QUIET].size() || pi->slider[MODALITY_CAPTURE].size() || pi->hopper[MODALITY_QUIET].size() || pi->hopper[MODALITY_CAPTURE].size(); - bool isPawn = !isSlider && pi->steps[MODALITY_QUIET].size() && !std::any_of(pi->steps[MODALITY_QUIET].begin(), pi->steps[MODALITY_QUIET].end(), [](const std::pair& d) { return d.first < SOUTH / 2; }); - bool isSlowLeaper = !isSlider && !std::any_of(pi->steps[MODALITY_QUIET].begin(), pi->steps[MODALITY_QUIET].end(), [](const std::pair& d) { return dist(d.first) > 1; }); + bool isSlider = pi->slider[0][MODALITY_QUIET].size() || pi->slider[0][MODALITY_CAPTURE].size() || pi->hopper[0][MODALITY_QUIET].size() || pi->hopper[0][MODALITY_CAPTURE].size(); + bool isPawn = !isSlider && pi->steps[0][MODALITY_QUIET].size() && !std::any_of(pi->steps[0][MODALITY_QUIET].begin(), pi->steps[0][MODALITY_QUIET].end(), [](const std::pair& d) { return d.first < SOUTH / 2; }); + bool isSlowLeaper = !isSlider && !std::any_of(pi->steps[0][MODALITY_QUIET].begin(), pi->steps[0][MODALITY_QUIET].end(), [](const std::pair& d) { return dist(d.first) > 1; }); // Scale slider piece values with board size if (isSlider) @@ -225,8 +225,8 @@ void init(const Variant* v) { constexpr int rm = 5; constexpr int r0 = rm + RANK_8; int r1 = rm + (v->maxRank + v->maxFile - 2 * v->capturesToHand) / 2; - int leaper = pi->steps[MODALITY_QUIET].size() + pi->steps[MODALITY_CAPTURE].size(); - int slider = pi->slider[MODALITY_QUIET].size() + pi->slider[MODALITY_CAPTURE].size() + pi->hopper[MODALITY_QUIET].size() + pi->hopper[MODALITY_CAPTURE].size(); + 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), eg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider)); } @@ -249,7 +249,7 @@ void init(const Variant* v) { // Increase leapers' value in makpong else if (v->makpongRule) { - if (std::any_of(pi->steps[MODALITY_CAPTURE].begin(), pi->steps[MODALITY_CAPTURE].end(), [](const std::pair& d) { return dist(d.first) > 1 && !d.second; })) + if (std::any_of(pi->steps[0][MODALITY_CAPTURE].begin(), pi->steps[0][MODALITY_CAPTURE].end(), [](const std::pair& d) { return dist(d.first) > 1 && !d.second; })) score = make_score(mg_value(score) * 4200 / (3500 + mg_value(score)), eg_value(score) * 4700 / (3500 + mg_value(score))); } @@ -272,7 +272,7 @@ void init(const Variant* v) { // For antichess variants, use negative piece values if (v->extinctionValue == VALUE_MATE) - score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->slider[MODALITY_CAPTURE].size())); + score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->slider[0][MODALITY_CAPTURE].size())); // Override variant piece value if (v->pieceValue[MG][pt]) diff --git a/src/search.cpp b/src/search.cpp index 31449cf..67ee805 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_enabled() && 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_to_char()[PAWN] != ' ') && !excludedMove && pos.non_pawn_material(us) && pos.count(~us) != pos.count(~us) diff --git a/src/types.h b/src/types.h index 261d49e..fbf7367 100644 --- a/src/types.h +++ b/src/types.h @@ -419,6 +419,8 @@ enum Piece { PIECE_NB = 2 * PIECE_TYPE_NB }; +enum PieceSet : uint64_t {}; + enum RiderType : int { NO_RIDER = 0, RIDER_BISHOP = 1 << 0, @@ -426,17 +428,18 @@ enum RiderType : int { RIDER_ROOK_V = 1 << 2, RIDER_CANNON_H = 1 << 3, RIDER_CANNON_V = 1 << 4, - RIDER_HORSE = 1 << 5, - RIDER_ELEPHANT = 1 << 6, - RIDER_JANGGI_ELEPHANT = 1 << 7, - RIDER_CANNON_DIAG = 1 << 8, - RIDER_NIGHTRIDER = 1 << 9, - RIDER_GRASSHOPPER_H = 1 << 10, - RIDER_GRASSHOPPER_V = 1 << 11, - RIDER_GRASSHOPPER_D = 1 << 12, + RIDER_LAME_DABBABA = 1 << 5, + RIDER_HORSE = 1 << 6, + RIDER_ELEPHANT = 1 << 7, + RIDER_JANGGI_ELEPHANT = 1 << 8, + RIDER_CANNON_DIAG = 1 << 9, + RIDER_NIGHTRIDER = 1 << 10, + RIDER_GRASSHOPPER_H = 1 << 11, + RIDER_GRASSHOPPER_V = 1 << 12, + RIDER_GRASSHOPPER_D = 1 << 13, HOPPING_RIDERS = RIDER_CANNON_H | RIDER_CANNON_V | RIDER_CANNON_DIAG | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D, - LAME_LEAPERS = RIDER_HORSE | RIDER_ELEPHANT | RIDER_JANGGI_ELEPHANT, + LAME_LEAPERS = RIDER_LAME_DABBABA | RIDER_HORSE | RIDER_ELEPHANT | RIDER_JANGGI_ELEPHANT, ASYMMETRICAL_RIDERS = RIDER_HORSE | RIDER_JANGGI_ELEPHANT | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D, NON_SLIDING_RIDERS = HOPPING_RIDERS | LAME_LEAPERS | RIDER_NIGHTRIDER, @@ -622,6 +625,19 @@ ENABLE_BASE_OPERATORS_ON(RiderType) #undef ENABLE_BASE_OPERATORS_ON #undef ENABLE_BIT_OPERATORS_ON +constexpr PieceSet piece_set(PieceType pt) { + return PieceSet(1ULL << pt); +} + +constexpr PieceSet operator| (PieceSet ps1, PieceSet ps2) { return (PieceSet)((uint64_t)ps1 | (uint64_t)ps2); } +constexpr PieceSet operator& (PieceSet ps1, PieceSet ps2) { return (PieceSet)((uint64_t)ps1 & (uint64_t)ps2); } +constexpr PieceSet operator& (PieceSet ps, PieceType pt) { return ps & piece_set(pt); } +inline PieceSet& operator|= (PieceSet& ps1, PieceSet ps2) { return (PieceSet&)((uint64_t&)ps1 |= (uint64_t)ps2); } +inline PieceSet& operator|= (PieceSet& ps, PieceType pt) { return ps |= piece_set(pt); } + +static_assert(piece_set(PAWN) & PAWN); +static_assert(piece_set(KING) & KING); + /// Additional operators to add a Direction to a Square constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } diff --git a/src/ucioption.cpp b/src/ucioption.cpp index ff9f22b..2d24c0f 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -48,7 +48,8 @@ namespace UCI { std::set standard_variants = { "normal", "nocastle", "fischerandom", "knightmate", "3check", "makruk", "shatranj", "asean", "seirawan", "crazyhouse", "bughouse", "suicide", "giveaway", "losers", "atomic", - "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi", "duck" + "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi", "duck", + "berolina", "spartan" }; void init_variant(const Variant* v) { @@ -145,7 +146,7 @@ void on_variant_change(const Option &o) { suffix += std::string(v->dropNoDoubledCount, 'f'); else if (pt == BISHOP && v->dropOppositeColoredBishop) suffix += "s"; - suffix += "@" + std::to_string(pt == PAWN && !v->promotionZonePawnDrops ? v->promotionRank : v->maxRank + 1); + suffix += "@" + std::to_string(pt == PAWN && !v->promotionZonePawnDrops && v->promotionRegion[WHITE] ? rank_of(lsb(v->promotionRegion[WHITE])) : v->maxRank + 1); } sync_cout << "piece " << v->pieceToChar[pt] << "& " << pieceMap.find(pt == KING ? v->kingType : pt)->second->betza << suffix << sync_endl; PieceType promType = v->promotedPieceType[pt]; diff --git a/src/variant.cpp b/src/variant.cpp index 7bf4659..cb2ea69 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -72,6 +72,67 @@ namespace { v->materialCounting = BLACK_DRAW_ODDS; return v; } + // Torpedo Chess + // https://arxiv.org/abs/2009.04374 + Variant* torpedo_variant() { + Variant* v = chess_variant_base()->init(); + v->doubleStepRegion[WHITE] = AllSquares; + v->doubleStepRegion[BLACK] = AllSquares; + return v; + } + // Berolina Chess + // https://www.chessvariants.com/dpieces.dir/berlin.html + Variant* berolina_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(PAWN); + v->add_piece(CUSTOM_PIECES, 'p', "mfFcfeWimfnA"); + v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = CUSTOM_PIECES; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES); + return v; + } + // Pawnsideways + // https://arxiv.org/abs/2009.04374 + Variant* pawnsideways_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(PAWN); + v->add_piece(CUSTOM_PIECES, 'p', "fsmWfceFifmnD"); + v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = CUSTOM_PIECES; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES); + return v; + } + // Pawnback + // https://arxiv.org/abs/2009.04374 + Variant* pawnback_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(PAWN); + v->add_piece(CUSTOM_PIECES, 'p', "fbmWfceFifmnD"); + v->mobilityRegion[WHITE][CUSTOM_PIECES] = (Rank2BB | Rank3BB | Rank4BB | Rank5BB | Rank6BB | Rank7BB | Rank8BB); + v->mobilityRegion[BLACK][CUSTOM_PIECES] = (Rank7BB | Rank6BB | Rank5BB | Rank4BB | Rank3BB | Rank2BB | Rank1BB); + v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = CUSTOM_PIECES; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES); + return v; + } + // Legan Chess + // https://en.wikipedia.org/wiki/Legan_chess + Variant* legan_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(PAWN); + v->add_piece(CUSTOM_PIECES, 'p', "mflFcflW"); + v->promotionRegion[WHITE] = make_bitboard(SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_A7, SQ_A6, SQ_A5); + v->promotionRegion[BLACK] = make_bitboard(SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_H2, SQ_H3, SQ_H4); + v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = CUSTOM_PIECES; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->startFen = "knbrp3/bqpp4/npp5/rp1p3P/p3P1PR/5PPN/4PPQB/3PRBNK w - - 0 1"; + v->doubleStep = false; + return v; + } // Pseudo-variant only used for endgame initialization Variant* fairy_variant() { Variant* v = chess_variant_base()->init(); @@ -81,7 +142,7 @@ namespace { } // Raazuva (Maldivian Chess) Variant* raazuvaa_variant() { - Variant* v = chess_variant()->init(); + Variant* v = chess_variant_base()->init(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; v->castling = false; v->doubleStep = false; @@ -98,8 +159,10 @@ namespace { v->add_piece(KHON, 's'); v->add_piece(MET, 'm'); v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1"; - v->promotionRank = RANK_6; - v->promotionPieceTypes = {MET}; + v->promotionRegion[WHITE] = Rank6BB | Rank7BB | Rank8BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; + v->promotionPieceTypes[WHITE] = {MET}; + v->promotionPieceTypes[BLACK] = {MET}; v->doubleStep = false; v->castling = false; v->nMoveRule = 0; @@ -143,7 +206,8 @@ namespace { v->add_piece(KHON, 'b'); v->add_piece(MET, 'q'); v->startFen = "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w - - 0 1"; - v->promotionPieceTypes = {ROOK, KNIGHT, KHON, MET}; + v->promotionPieceTypes[WHITE] = {ROOK, KNIGHT, KHON, MET}; + v->promotionPieceTypes[BLACK] = {ROOK, KNIGHT, KHON, MET}; v->doubleStep = false; v->castling = false; v->countingRule = ASEAN_COUNTING; @@ -157,7 +221,8 @@ namespace { v->remove_piece(MET); v->add_piece(AIWOK, 'a'); v->startFen = "rnsaksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKASNR w - - 0 1"; - v->promotionPieceTypes = {AIWOK}; + v->promotionPieceTypes[WHITE] = {AIWOK}; + v->promotionPieceTypes[BLACK] = {AIWOK}; return v; } // Shatranj @@ -172,7 +237,8 @@ namespace { v->add_piece(ALFIL, 'b'); v->add_piece(FERS, 'q'); v->startFen = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w - - 0 1"; - v->promotionPieceTypes = {FERS}; + v->promotionPieceTypes[WHITE] = {FERS}; + v->promotionPieceTypes[BLACK] = {FERS}; v->doubleStep = false; v->castling = false; v->extinctionValue = -VALUE_MATE; @@ -203,7 +269,8 @@ namespace { v->remove_piece(QUEEN); v->add_piece(AMAZON, 'a'); v->startFen = "rnbakbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBAKBNR w KQkq - 0 1"; - v->promotionPieceTypes = {AMAZON, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {AMAZON, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {AMAZON, ROOK, BISHOP, KNIGHT}; return v; } // Nightrider chess @@ -213,7 +280,8 @@ namespace { Variant* v = chess_variant_base()->init(); v->remove_piece(KNIGHT); v->add_piece(CUSTOM_PIECES, 'n', "NN"); - v->promotionPieceTypes = {QUEEN, ROOK, BISHOP, CUSTOM_PIECES}; + v->promotionPieceTypes[WHITE] = {QUEEN, ROOK, BISHOP, CUSTOM_PIECES}; + v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, BISHOP, CUSTOM_PIECES}; return v; } // Grasshopper chess @@ -221,7 +289,8 @@ namespace { Variant* grasshopper_variant() { Variant* v = chess_variant_base()->init(); v->add_piece(CUSTOM_PIECES, 'g', "gQ"); - v->promotionPieceTypes.insert(CUSTOM_PIECES); + v->promotionPieceTypes[WHITE].insert(CUSTOM_PIECES); + v->promotionPieceTypes[BLACK].insert(CUSTOM_PIECES); v->startFen = "rnbqkbnr/gggggggg/pppppppp/8/8/PPPPPPPP/GGGGGGGG/RNBQKBNR w KQkq - 0 1"; v->doubleStep = false; return v; @@ -235,7 +304,8 @@ namespace { v->remove_piece(BISHOP); v->add_piece(KNIBIS, 'n'); v->add_piece(BISKNI, 'b'); - v->promotionPieceTypes = {QUEEN, ROOK, BISKNI, KNIBIS}; + v->promotionPieceTypes[WHITE] = {QUEEN, ROOK, BISKNI, KNIBIS}; + v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, BISKNI, KNIBIS}; return v; } // New Zealand @@ -247,7 +317,8 @@ namespace { v->add_piece(ROOKNI, 'r'); v->add_piece(KNIROO, 'n'); v->castlingRookPiece = ROOKNI; - v->promotionPieceTypes = {QUEEN, ROOKNI, BISHOP, KNIROO}; + v->promotionPieceTypes[WHITE] = {QUEEN, ROOKNI, BISHOP, KNIROO}; + v->promotionPieceTypes[BLACK] = {QUEEN, ROOKNI, BISHOP, KNIROO}; return v; } // King of the Hill @@ -255,8 +326,8 @@ namespace { Variant* kingofthehill_variant() { Variant* v = chess_variant_base()->init(); v->flagPiece = KING; - v->whiteFlag = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); - v->blackFlag = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); + v->flagRegion[WHITE] = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); + v->flagRegion[BLACK] = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); v->flagMove = false; return v; } @@ -266,8 +337,8 @@ namespace { Variant* v = chess_variant_base()->init(); v->startFen = "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1"; v->flagPiece = KING; - v->whiteFlag = Rank8BB; - v->blackFlag = Rank8BB; + v->flagRegion[WHITE] = Rank8BB; + v->flagRegion[BLACK] = Rank8BB; v->flagMove = true; v->castling = false; v->checking = false; @@ -282,7 +353,8 @@ namespace { v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1"; v->kingType = KNIGHT; v->castlingKingPiece = KING; - v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP}; + v->promotionPieceTypes[WHITE] = {COMMONER, QUEEN, ROOK, BISHOP}; + v->promotionPieceTypes[BLACK] = {COMMONER, QUEEN, ROOK, BISHOP}; return v; } // Losers chess @@ -306,7 +378,8 @@ namespace { v->remove_piece(KING); v->add_piece(COMMONER, 'k'); v->castlingKingPiece = COMMONER; - v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; v->stalemateValue = VALUE_MATE; v->extinctionValue = VALUE_MATE; v->extinctionPieceTypes = {ALL_PIECES}; @@ -336,7 +409,8 @@ namespace { // http://www.binnewirtz.com/Schlagschach1.htm Variant* codrus_variant() { Variant* v = giveaway_variant()->init(); - v->promotionPieceTypes = {QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, BISHOP, KNIGHT}; v->extinctionPieceTypes = {COMMONER}; return v; } @@ -347,7 +421,8 @@ namespace { v->remove_piece(KING); v->add_piece(COMMONER, 'k'); v->castlingKingPiece = COMMONER; - v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT, PAWN}; return v; @@ -356,7 +431,8 @@ namespace { // https://en.wikipedia.org/wiki/V._R._Parton#Kinglet_chess Variant* kinglet_variant() { Variant* v = extinction_variant()->init(); - v->promotionPieceTypes = {COMMONER}; + v->promotionPieceTypes[WHITE] = {COMMONER}; + v->promotionPieceTypes[BLACK] = {COMMONER}; v->extinctionPieceTypes = {PAWN}; return v; } @@ -378,7 +454,7 @@ namespace { Variant* horde_variant() { Variant* v = chess_variant_base()->init(); v->startFen = "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1"; - v->doubleStepRankMin = RANK_1; + v->doubleStepRegion[WHITE] |= Rank1BB; v->enPassantRegion = Rank3BB | Rank6BB; // exclude en passant on second rank v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {ALL_PIECES}; @@ -418,7 +494,7 @@ namespace { v->stalemateValue = VALUE_MATE; return v; } - + Variant* isolation_variant() { //https://boardgamegeek.com/boardgame/1875/isolation Variant* v = chess_variant_base()->init(); v->maxRank = RANK_8; @@ -572,7 +648,6 @@ namespace { v->variantTemplate = "bughouse"; v->pieceToCharTable = "PN.R.F....SKpn.r.f....sk"; v->startFen = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1"; - v->remove_piece(MET); v->add_piece(MET, 'f'); v->mustDrop = true; v->pieceDrops = true; @@ -581,6 +656,8 @@ namespace { v->blackDropRegion = Rank8BB | Rank7BB | Rank6BB; v->sittuyinRookDrop = true; v->sittuyinPromotion = true; + v->promotionRegion[WHITE] = make_bitboard(SQ_A8, SQ_B7, SQ_C6, SQ_D5, SQ_E5, SQ_F6, SQ_G7, SQ_H8); + v->promotionRegion[BLACK] = make_bitboard(SQ_A1, SQ_B2, SQ_C3, SQ_D4, SQ_E4, SQ_F3, SQ_G2, SQ_H1); v->promotionLimit[FERS] = 1; v->immobilityIllegal = false; v->countingRule = ASEAN_COUNTING; @@ -599,7 +676,8 @@ namespace { v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"; v->gating = true; v->seirawanGating = true; - v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // S-House @@ -637,7 +715,8 @@ namespace { Variant *v = chess_variant_base()->init(); v->remove_piece(BISHOP); v->add_piece(CUSTOM_PIECES, 'b', "BnN"); - v->promotionPieceTypes = {QUEEN, CUSTOM_PIECES, ROOK, KNIGHT}; + v->promotionPieceTypes[WHITE] = {QUEEN, CUSTOM_PIECES, ROOK, KNIGHT}; + v->promotionPieceTypes[BLACK] = {QUEEN, CUSTOM_PIECES, ROOK, KNIGHT}; return v; } // Base used for most shogi variants @@ -658,8 +737,8 @@ namespace { v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1"; v->pieceDrops = true; v->capturesToHand = true; - v->promotionRank = RANK_5; - v->promotionPieceTypes = {}; + v->promotionRegion[WHITE] = Rank5BB; + v->promotionRegion[BLACK] = Rank1BB; v->doubleStep = false; v->castling = false; v->promotedPieceType[SHOGI_PAWN] = GOLD; @@ -695,7 +774,8 @@ namespace { v->add_piece(LANCE, 'l'); v->add_piece(SHOGI_KNIGHT, 'n'); v->startFen = "p+nks+l/5/5/5/+LSK+NP[-] w 0 1"; - v->promotionRank = RANK_1; + v->promotionRegion[WHITE] = AllSquares; + v->promotionRegion[BLACK] = AllSquares; v->mandatoryPiecePromotion = true; v->pieceDemotion = true; v->dropPromoted = true; @@ -718,7 +798,8 @@ namespace { Variant* v = kyotoshogi_variant()->init(); v->maxFile = FILE_D; v->startFen = "kb+r+l/p3/4/3P/+L+RBK[-] w 0 1"; - v->promotionRank = RANK_1; + v->promotionRegion[WHITE] = AllSquares; + v->promotionRegion[BLACK] = AllSquares; v->piecePromotionOnCapture = true; v->promotedPieceType[LANCE] = SILVER; v->promotedPieceType[BISHOP] = GOLD; @@ -745,13 +826,14 @@ namespace { v->add_piece(WAZIR, 'g'); v->add_piece(KING, 'l'); v->startFen = "gle/1c1/1C1/ELG[-] w 0 1"; - v->promotionRank = RANK_4; + v->promotionRegion[WHITE] = Rank4BB; + v->promotionRegion[BLACK] = Rank1BB; v->mandatoryPiecePromotion = true; v->immobilityIllegal = false; v->shogiPawnDropMateIllegal = false; v->flagPiece = KING; - v->whiteFlag = Rank4BB; - v->blackFlag = Rank1BB; + v->flagRegion[WHITE] = Rank4BB; + v->flagRegion[BLACK] = Rank1BB; v->dropNoDoubled = NO_PIECE_TYPE; v->nFoldValue = VALUE_DRAW; v->perpetualCheckIllegal = false; @@ -766,7 +848,8 @@ namespace { v->maxRank = RANK_6; v->maxFile = FILE_E; v->startFen = "sgkgs/5/1ppp1/1PPP1/5/SGKGS[-] w 0 1"; - v->promotionRank = RANK_5; + v->promotionRegion[WHITE] = Rank5BB | Rank6BB; + v->promotionRegion[BLACK] = Rank2BB | Rank1BB; return v; } // Judkins shogi @@ -778,7 +861,8 @@ namespace { v->maxFile = FILE_F; v->add_piece(SHOGI_KNIGHT, 'n'); v->startFen = "rbnsgk/5p/6/6/P5/KGSNBR[-] w 0 1"; - v->promotionRank = RANK_5; + v->promotionRegion[WHITE] = Rank5BB | Rank6BB; + v->promotionRegion[BLACK] = Rank2BB | Rank1BB; v->promotedPieceType[SHOGI_KNIGHT] = GOLD; return v; } @@ -803,8 +887,8 @@ namespace { v->startFen = "rpckcpl/3f3/sssssss/2s1S2/SSSSSSS/3F3/LPCKCPR[-] w 0 1"; v->pieceDrops = true; v->capturesToHand = true; - v->promotionRank = RANK_6; - v->promotionPieceTypes = {}; + v->promotionRegion[WHITE] = Rank6BB | Rank7BB; + v->promotionRegion[BLACK] = Rank2BB | Rank1BB; v->doubleStep = false; v->castling = false; v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECES + 5; // swallow promotes to goose @@ -830,7 +914,8 @@ namespace { v->maxFile = FILE_H; v->add_piece(CUSTOM_PIECES, 'n', "fNsW"); v->startFen = "1nbgkgn1/1r4b1/pppppppp/8/8/PPPPPPPP/1B4R1/1NGKGBN1[-] w 0 1"; - v->promotionRank = RANK_6; + v->promotionRegion[WHITE] = Rank6BB | Rank7BB | Rank8BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; v->promotedPieceType[CUSTOM_PIECES] = GOLD; v->mandatoryPiecePromotion = true; return v; @@ -844,8 +929,10 @@ namespace { v->maxFile = FILE_F; v->remove_piece(BISHOP); v->startFen = "rnqknr/pppppp/6/6/PPPPPP/RNQKNR w - - 0 1"; - v->promotionRank = RANK_6; - v->promotionPieceTypes = {QUEEN, ROOK, KNIGHT}; + v->promotionRegion[WHITE] = Rank6BB; + v->promotionRegion[BLACK] = Rank1BB; + v->promotionPieceTypes[WHITE] = {QUEEN, ROOK, KNIGHT}; + v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, KNIGHT}; v->doubleStep = false; v->castling = false; return v; @@ -857,7 +944,8 @@ namespace { v->maxRank = RANK_5; v->maxFile = FILE_E; v->startFen = "rnbqk/ppppp/5/PPPPP/RNBQK w - - 0 1"; - v->promotionRank = RANK_5; + v->promotionRegion[WHITE] = Rank5BB; + v->promotionRegion[BLACK] = Rank1BB; v->doubleStep = false; v->castling = false; return v; @@ -871,7 +959,8 @@ namespace { v->remove_piece(QUEEN); v->add_piece(CHANCELLOR, 'c'); v->startFen = "rnbckbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBCKBNR w KQkq - 0 1"; - v->promotionPieceTypes = {CHANCELLOR, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {CHANCELLOR, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {CHANCELLOR, ROOK, BISHOP, KNIGHT}; return v; } // Chigorin chess @@ -882,7 +971,29 @@ namespace { v->pieceToCharTable = "PNBR............CKpnbrq............k"; v->add_piece(CHANCELLOR, 'c'); v->startFen = "rbbqkbbr/pppppppp/8/8/8/8/PPPPPPPP/RNNCKNNR w KQkq - 0 1"; - v->promotionPieceTypes = {QUEEN, CHANCELLOR, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {CHANCELLOR, ROOK, KNIGHT}; + v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, BISHOP}; + return v; + } + // Spartan chess + // https://www.chessvariants.com/rules/spartan-chess + Variant* spartan_variant() { + Variant* v = threekings_variant()->init(); + v->add_piece(DRAGON, 'g'); + v->add_piece(ARCHBISHOP, 'w'); + v->add_piece(CUSTOM_PIECES, 'h', "fmFfcWimA"); + v->add_piece(CUSTOM_PIECES + 1, 'l', "FAsmW"); + v->add_piece(CUSTOM_PIECES + 2, 'c', "WD"); + v->startFen = "lgkcckwl/hhhhhhhh/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1"; + v->promotionPawnType[BLACK] = CUSTOM_PIECES; + v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES); + v->promotionPieceTypes[BLACK] = {COMMONER, DRAGON, ARCHBISHOP, CUSTOM_PIECES + 1, CUSTOM_PIECES + 2}; + v->promotionLimit[COMMONER] = 2; + v->enPassantRegion = 0; + v->extinctionPieceCount = 0; + v->extinctionPseudoRoyal = true; + v->dupleCheck = true; return v; } // Shatar (Mongolian chess) @@ -893,7 +1004,8 @@ namespace { v->remove_piece(QUEEN); v->add_piece(BERS, 'j'); v->startFen = "rnbjkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBJKBNR w - - 0 1"; - v->promotionPieceTypes = {BERS}; + v->promotionPieceTypes[WHITE] = {BERS}; + v->promotionPieceTypes[BLACK] = {BERS}; v->doubleStep = false; v->castling = false; v->extinctionValue = VALUE_DRAW; // Robado @@ -923,7 +1035,6 @@ namespace { v->reset_pieces(); v->add_piece(CLOBBER_PIECE, 'p'); v->startFen = "PpPpP/pPpPp/PpPpP/pPpPp/PpPpP/pPpPp w 0 1"; - v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; @@ -938,13 +1049,12 @@ namespace { v->reset_pieces(); v->add_piece(BREAKTHROUGH_PIECE, 'p'); v->startFen = "pppppppp/pppppppp/8/8/8/8/PPPPPPPP/PPPPPPPP w 0 1"; - v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; v->flagPiece = BREAKTHROUGH_PIECE; - v->whiteFlag = Rank8BB; - v->blackFlag = Rank1BB; + v->flagRegion[WHITE] = Rank8BB; + v->flagRegion[BLACK] = Rank1BB; return v; } // Ataxx @@ -957,7 +1067,6 @@ namespace { v->reset_pieces(); v->add_piece(CUSTOM_PIECES, 'p', "mDmNmA"); v->startFen = "P5p/7/7/7/7/7/p5P w 0 1"; - v->promotionPieceTypes = {}; v->pieceDrops = true; v->doubleStep = false; v->castling = false; @@ -982,7 +1091,6 @@ namespace { v->reset_pieces(); v->add_piece(IMMOBILE_PIECE, 'p'); v->startFen = "8/8/8/8/8/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1"; - v->promotionPieceTypes = {}; v->pieceDrops = true; v->doubleStep = false; v->castling = false; @@ -1022,7 +1130,6 @@ namespace { v->mobilityRegion[WHITE][KING] = (Rank1BB | Rank2BB | Rank3BB) & (FileCBB | FileDBB | FileEBB); v->mobilityRegion[BLACK][KING] = (Rank5BB | Rank6BB | Rank7BB) & (FileCBB | FileDBB | FileEBB); v->kingType = WAZIR; - v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; @@ -1041,7 +1148,8 @@ namespace { v->add_piece(LANCE, 'l'); v->add_piece(SHOGI_KNIGHT, 'n'); v->startFen = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w 0 1"; - v->promotionRank = RANK_7; + v->promotionRegion[WHITE] = Rank7BB | Rank8BB | Rank9BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; v->promotedPieceType[LANCE] = GOLD; v->promotedPieceType[SHOGI_KNIGHT] = GOLD; return v; @@ -1084,14 +1192,14 @@ namespace { v->add_piece(CUSTOM_PIECES + 3, 'g', "WfFbR"); // Yari gold v->add_piece(CUSTOM_PIECES + 4, 's', "fKbR"); // Yari silver v->startFen = "rnnkbbr/7/ppppppp/7/7/7/PPPPPPP/7/RBBKNNR[-] w 0 1"; - v->promotionRank = RANK_7; + v->promotionRegion[WHITE] = Rank7BB | Rank8BB | Rank9BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECES + 4; v->promotedPieceType[CUSTOM_PIECES] = CUSTOM_PIECES + 3; v->promotedPieceType[CUSTOM_PIECES + 1] = CUSTOM_PIECES + 3; v->promotedPieceType[CUSTOM_PIECES + 2] = ROOK; v->pieceDrops = true; v->capturesToHand = true; - v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->dropNoDoubled = SHOGI_PAWN; @@ -1113,7 +1221,8 @@ namespace { v->add_piece(KNIGHT, 'n'); v->add_piece(QUEEN, 'q'); v->startFen = "lnsgkqgsnl/1r6b1/pppppppppp/10/10/10/10/PPPPPPPPPP/1B6R1/LNSGQKGSNL[-] w 0 1"; - v->promotionRank = RANK_8; + v->promotionRegion[WHITE] = Rank8BB | Rank9BB | Rank10BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; v->promotedPieceType[CUSTOM_PIECES] = GOLD; v->promotedPieceType[KNIGHT] = GOLD; return v; @@ -1130,7 +1239,8 @@ namespace { v->add_piece(ARCHBISHOP, 'a'); v->add_piece(CHANCELLOR, 'c'); v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR w KQkq - 0 1"; - v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Capahouse @@ -1173,7 +1283,8 @@ namespace { v->castlingQueensideFile = FILE_B; v->add_piece(ARCHBISHOP, 'j'); v->startFen = "rjnbkqbnjr/pppppppppp/10/10/10/10/PPPPPPPPPP/RJNBKQBNJR w KQkq - 0 1"; - v->promotionPieceTypes = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Modern chess @@ -1184,12 +1295,16 @@ namespace { v->pieceToCharTable = "PNBRQ..M.............Kpnbrq..m.............k"; v->maxRank = RANK_9; v->maxFile = FILE_I; - v->promotionRank = RANK_9; + v->promotionRegion[WHITE] = Rank9BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStepRegion[WHITE] = Rank2BB; + v->doubleStepRegion[BLACK] = Rank8BB; v->castlingKingsideFile = FILE_G; v->castlingQueensideFile = FILE_C; v->add_piece(ARCHBISHOP, 'm'); v->startFen = "rnbqkmbnr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBMKQBNR w KQkq - 0 1"; - v->promotionPieceTypes = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Chancellor chess @@ -1200,12 +1315,16 @@ namespace { v->pieceToCharTable = "PNBRQ...........CKpnbrq...........ck"; v->maxRank = RANK_9; v->maxFile = FILE_I; - v->promotionRank = RANK_9; + v->promotionRegion[WHITE] = Rank9BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStepRegion[WHITE] = Rank2BB; + v->doubleStepRegion[BLACK] = Rank8BB; v->castlingKingsideFile = FILE_G; v->castlingQueensideFile = FILE_C; v->add_piece(CHANCELLOR, 'c'); v->startFen = "rnbqkcnbr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBQKCNBR w KQkq - 0 1"; - v->promotionPieceTypes = {CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Embassy chess @@ -1231,7 +1350,8 @@ namespace { v->castlingQueensideFile = FILE_C; v->add_piece(CENTAUR, 'c'); v->startFen = "rcnbqkbncr/pppppppppp/10/10/10/10/PPPPPPPPPP/RCNBQKBNCR w KQkq - 0 1"; - v->promotionPieceTypes = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Gustav III chess @@ -1246,7 +1366,8 @@ namespace { v->castlingQueensideFile = FILE_D; v->add_piece(AMAZON, 'a'); v->startFen = "arnbqkbnra/*pppppppp*/*8*/*8*/*8*/*8*/*PPPPPPPP*/ARNBQKBNRA w KQkq - 0 1"; - v->promotionPieceTypes = {AMAZON, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[WHITE] = {AMAZON, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {AMAZON, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Jeson mor @@ -1259,13 +1380,12 @@ namespace { v->reset_pieces(); v->add_piece(KNIGHT, 'n'); v->startFen = "nnnnnnnnn/9/9/9/9/9/9/9/NNNNNNNNN w - - 0 1"; - v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; v->flagPiece = KNIGHT; - v->whiteFlag = make_bitboard(SQ_E5); - v->blackFlag = make_bitboard(SQ_E5); + v->flagRegion[WHITE] = make_bitboard(SQ_E5); + v->flagRegion[BLACK] = make_bitboard(SQ_E5); v->flagMove = true; return v; } @@ -1282,7 +1402,8 @@ namespace { v->add_piece(COMMONER, 'm'); v->add_piece(WAZIR, 'w'); v->startFen = "rnebmk1wbenr/1ppppp1pppp1/6f5/p5p4p/P5P4P/6F5/1PPPPP1PPPP1/RNEBMK1WBENR w - - 0 1"; - v->promotionPieceTypes = {FERS}; + v->promotionPieceTypes[WHITE] = {FERS}; + v->promotionPieceTypes[BLACK] = {FERS}; v->doubleStep = false; v->castling = false; v->extinctionValue = -VALUE_MATE; @@ -1305,8 +1426,10 @@ namespace { v->add_piece(ARCHBISHOP, 'a'); v->add_piece(CHANCELLOR, 'c'); v->startFen = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1"; - v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; - v->promotionRank = RANK_8; + v->promotionPieceTypes[WHITE] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionRegion[WHITE] = Rank8BB | Rank9BB | Rank10BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; v->promotionLimit[ARCHBISHOP] = 1; v->promotionLimit[CHANCELLOR] = 1; v->promotionLimit[QUEEN] = 1; @@ -1315,8 +1438,8 @@ namespace { v->promotionLimit[KNIGHT] = 2; v->mandatoryPawnPromotion = false; v->immobilityIllegal = true; - v->doubleStepRank = RANK_3; - v->doubleStepRankMin = RANK_3; + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; v->castling = false; return v; } @@ -1331,10 +1454,11 @@ namespace { v->add_piece(CUSTOM_PIECES + 1, 'w', "CF"); v->add_piece(CUSTOM_PIECES + 2, 'l', "FDH"); v->startFen = "rw6wr/clbnqknbla/pppppppppp/10/10/10/10/PPPPPPPPPP/CLBNQKNBLA/RW6WR w - - 0 1"; - v->promotionPieceTypes.erase(KNIGHT); - v->promotionPieceTypes.insert(CUSTOM_PIECES); - v->promotionPieceTypes.insert(CUSTOM_PIECES + 1); - v->promotionPieceTypes.insert(CUSTOM_PIECES + 2); + v->promotionPieceTypes[WHITE].erase(KNIGHT); + v->promotionPieceTypes[WHITE].insert(CUSTOM_PIECES); + v->promotionPieceTypes[WHITE].insert(CUSTOM_PIECES + 1); + v->promotionPieceTypes[WHITE].insert(CUSTOM_PIECES + 2); + v->promotionPieceTypes[BLACK] = v->promotionPieceTypes[WHITE]; v->promotionLimit[CUSTOM_PIECES] = 2; v->promotionLimit[CUSTOM_PIECES + 1] = 2; v->promotionLimit[CUSTOM_PIECES + 2] = 2; @@ -1352,10 +1476,12 @@ namespace { v->add_piece(CHANCELLOR, 'm'); v->add_piece(CUSTOM_PIECES, 'c', "DAW"); // Champion v->add_piece(CUSTOM_PIECES + 1, 'w', "CF"); // Wizard - v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN}; - v->promotionRank = RANK_10; - v->doubleStepRank = RANK_3; - v->doubleStepRankMin = RANK_3; + v->promotionPieceTypes[WHITE] = {ARCHBISHOP, CHANCELLOR, QUEEN}; + v->promotionPieceTypes[BLACK] = {ARCHBISHOP, CHANCELLOR, QUEEN}; + v->promotionRegion[WHITE] = Rank10BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; v->castling = false; return v; } @@ -1373,10 +1499,52 @@ namespace { v->castlingKingsideFile = FILE_I; v->castlingQueensideFile = FILE_E; v->castlingRank = RANK_2; - v->promotionRank = RANK_9; - v->promotionPieceTypes = {CUSTOM_PIECES + 1, CUSTOM_PIECES, QUEEN, ROOK, BISHOP, KNIGHT}; - v->doubleStepRank = RANK_3; - v->doubleStepRankMin = RANK_3; + v->promotionRegion[WHITE] = Rank9BB | Rank10BB; + v->promotionRegion[BLACK] = Rank2BB | Rank1BB; + v->promotionPieceTypes[WHITE] = {CUSTOM_PIECES + 1, CUSTOM_PIECES, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionPieceTypes[BLACK] = {CUSTOM_PIECES + 1, CUSTOM_PIECES, QUEEN, ROOK, BISHOP, KNIGHT}; + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; + return v; + } + // Troitzky Chess + // https://www.chessvariants.com/play/troitzky-chess + Variant* troitzky_variant() { + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->startFen = "****qk****/**rnbbnr**/*pppppppp*/*8*/10/10/*8*/*PPPPPPPP*/**RNBBNR**/****QK**** w - - 0 1"; + v->promotionRegion[WHITE] = make_bitboard(SQ_A6, SQ_B8, SQ_C9, SQ_D9, SQ_E10, SQ_F10, SQ_G9, SQ_H9, SQ_I8, SQ_J6); + v->promotionRegion[BLACK] = make_bitboard(SQ_A5, SQ_B3, SQ_C2, SQ_D2, SQ_E1, SQ_F1, SQ_G2, SQ_H2, SQ_I3, SQ_J5); + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; + v->castling = false; + return v; + } + // Wolf chess + // https://en.wikipedia.org/wiki/Wolf_chess + Variant* wolf_variant() { + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_10; + v->remove_piece(KNIGHT); + v->add_piece(CHANCELLOR, 'w'); // wolf + v->add_piece(ARCHBISHOP, 'f'); // fox + v->add_piece(CUSTOM_PIECES, 's', "fKifmnD"); // seargent + v->add_piece(CUSTOM_PIECES + 1, 'n', "NN"); // nightrider + v->add_piece(CUSTOM_PIECES + 2, 'e', "NNQ"); // elephant + v->startFen = "qwfrbbnk/pssppssp/1pp2pp1/8/8/8/8/1PP2PP1/PSSPPSSP/KNBBRFWQ w - - 0 1"; + v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = PAWN; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(PAWN) | piece_set(CUSTOM_PIECES); + v->promotionPieceTypes[WHITE] = {QUEEN, CHANCELLOR, ARCHBISHOP, ROOK, BISHOP}; + v->promotionPieceTypes[BLACK] = {QUEEN, CHANCELLOR, ARCHBISHOP, ROOK, BISHOP}; + v->promotedPieceType[PAWN] = CUSTOM_PIECES + 2; + v->promotionRegion[WHITE] = Rank10BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStepRegion[WHITE] = Rank2BB | make_bitboard(SQ_B3, SQ_C3, SQ_F3, SQ_G3); + v->doubleStepRegion[BLACK] = Rank9BB | make_bitboard(SQ_B8, SQ_C8, SQ_F8, SQ_G8); + v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(PAWN); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(PAWN) | piece_set(CUSTOM_PIECES); + v->castling = false; return v; } // Shako @@ -1390,13 +1558,15 @@ namespace { v->add_piece(FERS_ALFIL, 'e'); v->add_piece(CANNON, 'c'); v->startFen = "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/ERNBQKBNRE/C8C w KQkq - 0 1"; - v->promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT, CANNON, FERS_ALFIL }; - v->promotionRank = RANK_10; + v->promotionPieceTypes[WHITE] = { QUEEN, ROOK, BISHOP, KNIGHT, CANNON, FERS_ALFIL }; + v->promotionPieceTypes[BLACK] = { QUEEN, ROOK, BISHOP, KNIGHT, CANNON, FERS_ALFIL }; + v->promotionRegion[WHITE] = Rank10BB; + v->promotionRegion[BLACK] = Rank1BB; v->castlingKingsideFile = FILE_H; v->castlingQueensideFile = FILE_D; v->castlingRank = RANK_2; - v->doubleStepRank = RANK_3; - v->doubleStepRankMin = RANK_3; + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; return v; } // Clobber 10x10 @@ -1554,6 +1724,11 @@ void VariantMap::init() { add("fischerandom", chess960_variant()); add("nocastle", nocastle_variant()); add("armageddon", armageddon_variant()); + add("torpedo", torpedo_variant()); + add("berolina", berolina_variant()); + add("pawnsideways", pawnsideways_variant()); + add("pawnback", pawnback_variant()); + add("legan", legan_variant()); add("fairy", fairy_variant()); // fairy variant used for endgame code initialization add("makruk", makruk_variant()); add("makpong", makpong_variant()); @@ -1616,6 +1791,7 @@ void VariantMap::init() { add("gardner", gardner_variant()); add("almost", almost_variant()); add("chigorin", chigorin_variant()); + add("spartan", spartan_variant()); add("shatar", shatar_variant()); add("coregal", coregal_variant()); add("clobber", clobber_variant()); @@ -1646,6 +1822,8 @@ void VariantMap::init() { add("opulent", opulent_variant()); add("tencubed", tencubed_variant()); add("omicron", omicron_variant()); + add("troitzky", troitzky_variant()); + add("wolf", wolf_variant()); add("shako", shako_variant()); add("clobber10", clobber10_variant()); add("flipello10", flipello10_variant()); diff --git a/src/variant.h b/src/variant.h index 3ac95de..2a9d277 100644 --- a/src/variant.h +++ b/src/variant.h @@ -50,8 +50,11 @@ struct Variant { std::string pieceToCharSynonyms = std::string(PIECE_NB, ' '); std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; Bitboard mobilityRegion[COLOR_NB][PIECE_TYPE_NB] = {}; - Rank promotionRank = RANK_8; - std::set > promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT }; + Bitboard promotionRegion[COLOR_NB] = {Rank8BB, Rank1BB}; + PieceType promotionPawnType[COLOR_NB] = {PAWN, PAWN}; + PieceSet promotionPawnTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)}; + std::set > promotionPieceTypes[COLOR_NB] = {{ QUEEN, ROOK, BISHOP, KNIGHT }, + { QUEEN, ROOK, BISHOP, KNIGHT }}; bool sittuyinPromotion = false; int promotionLimit[PIECE_TYPE_NB] = {}; // 0 means unlimited PieceType promotedPieceType[PIECE_TYPE_NB] = {}; @@ -62,9 +65,10 @@ struct Variant { bool blastOnCapture = false; bool petrifyOnCapture = false; bool doubleStep = true; - Rank doubleStepRank = RANK_2; - Rank doubleStepRankMin = RANK_2; + Bitboard doubleStepRegion[COLOR_NB] = {Rank2BB, Rank7BB}; + Bitboard tripleStepRegion[COLOR_NB] = {}; Bitboard enPassantRegion = AllSquares; + PieceSet enPassantTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)}; bool castling = true; bool castlingDroppedPiece = false; File castlingKingsideFile = FILE_G; @@ -113,6 +117,7 @@ struct Variant { bool freeDrops = false; // game end + PieceSet nMoveRuleTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)}; int nMoveRule = 50; int nFoldRule = 3; Value nFoldValue = VALUE_DRAW; @@ -129,12 +134,12 @@ struct Variant { Value extinctionValue = VALUE_NONE; bool extinctionClaim = false; bool extinctionPseudoRoyal = false; + bool dupleCheck = false; std::set extinctionPieceTypes = {}; int extinctionPieceCount = 0; int extinctionOpponentPieceCount = 0; PieceType flagPiece = NO_PIECE_TYPE; - Bitboard whiteFlag = 0; - Bitboard blackFlag = 0; + Bitboard flagRegion[COLOR_NB] = {}; bool flagMove = false; bool checkCounting = false; int connectN = 0; @@ -156,6 +161,11 @@ struct Variant { bool shogiStylePromotions = false; void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') { + // Avoid ambiguous definition by removing existing piece with same letter + size_t idx; + if ((idx = pieceToChar.find(toupper(c))) != std::string::npos) + remove_piece(PieceType(idx)); + // Now add new piece pieceToChar[make_piece(WHITE, pt)] = toupper(c); pieceToChar[make_piece(BLACK, pt)] = tolower(c); pieceToCharSynonyms[make_piece(WHITE, pt)] = toupper(c2); @@ -176,12 +186,18 @@ struct Variant { pieceToCharSynonyms[make_piece(WHITE, pt)] = ' '; pieceToCharSynonyms[make_piece(BLACK, pt)] = ' '; pieceTypes.erase(pt); + // erase from promotion types to ensure consistency + promotionPieceTypes[WHITE].erase(pt); + promotionPieceTypes[BLACK].erase(pt); } void reset_pieces() { pieceToChar = std::string(PIECE_NB, ' '); pieceToCharSynonyms = std::string(PIECE_NB, ' '); pieceTypes.clear(); + // clear promotion types to ensure consistency + promotionPieceTypes[WHITE].clear(); + promotionPieceTypes[BLACK].clear(); } // Reset values that always need to be redefined @@ -192,6 +208,12 @@ struct Variant { // Pre-calculate derived properties Variant* conclude() { + // Enforce consistency to allow runtime optimizations + if (!doubleStep) + doubleStepRegion[WHITE] = doubleStepRegion[BLACK] = 0; + if (!doubleStepRegion[WHITE] && !doubleStepRegion[BLACK]) + doubleStep = false; + fastAttacks = std::all_of(pieceTypes.begin(), pieceTypes.end(), [this](PieceType pt) { return ( pt < FAIRY_PIECES || pt == COMMONER || pt == IMMOBILE_PIECE diff --git a/src/variants.ini b/src/variants.ini index d7b3099..a0710fc 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -123,6 +123,7 @@ # [File]: denotes a file of the board [1-12, a-i] # [int]: any natural number [0, 1, ...] # [PieceType]: a piece type [letters defined for pieces, e.g., p] +# [PieceSet]: multiple piece types [letters defined for pieces, e.g., nbrq] # [Bitboard]: list of squares [e.g., d4 e4 d5 e5]. * can be used as wildcard for files (e.g., *1 is the first rank) # [Value]: game result for the side to move [win, loss, draw] # [MaterialCounting]: material couting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] @@ -147,8 +148,16 @@ # startFen: FEN of starting position (default: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1) # mobilityRegion: the mobility area can be defined via options specific to color and piece, # .e.g., mobilityRegionWhiteRook, mobilityRegionBlackJanggiElephant, etc. [Bitboard] -# promotionRank: relative rank required to reach for promotion [Rank] (default: 8) -# promotionPieceTypes: pawn promotion options using their one-letter representations (default: nbrq) +# pawnTypes: define pieces considered as "pawns" for promotion, en passant, and n move rule [PieceSet] (default: p) +# see promotionPawnTypes, enPassantTypes, and nMoveRuleTypes for more specific overrides. +# promotionRegionWhite: region where promotions are allowed for white [Bitboard] (default: *8) +# promotionRegionBlack: region where promotions are allowed for black [Bitboard] (default: *1) +# promotionPawnTypes: promotion pawn types for both colors [PieceSet] (default: p) +# promotionPawnTypesWhite: white promotion pawn types [PieceSet] (default: p) +# promotionPawnTypesBlack: black promotion pawn types [PieceSet] (default: p) +# promotionPieceTypes: pawn promotion options [PieceSet] (default: nbrq) +# promotionPieceTypesWhite: white pawn promotion options [PieceSet] (default: nbrq) +# promotionPieceTypesBlack: black pawn promotion options [PieceSet] (default: nbrq) # sittuyinPromotion: enable Sittuyin-style pawn promotion [bool] (default: false) # promotionLimit: maximum number of pieces of a type, e.g., q:1 r:2 (default: ) # promotedPieceType: mapping between unpromoted and promoted non-pawn piece types, e.g., p:g s:g (default: ) @@ -159,9 +168,14 @@ # blastOnCapture: captures explode all adjacent non-pawn pieces (e.g., atomic chess) [bool] (default: false) # petrifyOnCapture: non-pawn pieces are turned into wall squares when capturing [bool] (default: false) # doubleStep: enable pawn double step [bool] (default: true) -# doubleStepRank: relative rank from where pawn double steps are allowed [Rank] (default: 2) -# doubleStepRankMin: earlist relative rank from where pawn double steps are allowed [Rank] (default: 2) +# 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) +# tripleStepRegionWhite: region where pawn triple steps are allowed for white [Bitboard] (default: -) +# tripleStepRegionBlack: region where pawn triple steps are allowed for black [Bitboard] (default: -) # enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] +# enPassantTypes: define pieces able to capture en passant [PieceSet] (default: p) +# enPassantTypesWhite: define white pieces able to capture en passant [PieceSet] (default: p) +# enPassantTypesBlack: define black pieces able to capture en passant [PieceSet] (default: p) # castling: enable castling [bool] (default: true) # castlingDroppedPiece: enable castling with dropped rooks/kings [bool] (default: false) # castlingKingsideFile: destination file of king after kingside castling [File] (default: g) @@ -189,7 +203,7 @@ # dropOppositeColoredBishop: dropped bishops have to be on opposite-colored squares [bool] (default: false) # 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 [PieceType] (default: 1) +# dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [int] (default: 1) # 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) # arrowGating: gating of wall squares in Game of the Amazons style [bool] (default: false) @@ -206,6 +220,9 @@ # flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false) # soldierPromotionRank: restrict soldier to shogi pawn movements until reaching n-th rank [bool] (default: 1) # flipEnclosedPieces: change color of pieces that are enclosed by a drop [EnclosingRule] (default: none) +# nMoveRuleTypes: define pieces resetting n move rule [PieceSet] (default: p) +# nMoveRuleTypesWhite: define white pieces resetting n move rule [PieceSet] (default: p) +# nMoveRuleTypesBlack: define black pieces resetting n move rule [PieceSet] (default: p) # nMoveRule: move count for 50/n-move rule [int] (default: 50) # nFoldRule: move count for 3/n-fold repetition rule [int] (default: 3) # nFoldValue: result in case of 3/n-fold repetition [Value] (default: draw) @@ -222,12 +239,13 @@ # extinctionValue: result when one of extinctionPieceTypes is extinct [Value] (default: none) # extinctionClaim: extinction of opponent pieces can only be claimed as side to move [bool] (default: false) # extinctionPseudoRoyal: treat the last extinction piece like a royal piece [bool] (default: false) +# dupleCheck: when all pseudo-royal pieces are attacked, it counts as a check [bool] (default: false) # extinctionPieceTypes: list of piece types for extinction rules, e.g., pnbrq (* means all) (default: ) # extinctionPieceCount: piece count at which the game is decided by extinction rule (default: 0) # extinctionOpponentPieceCount: opponent piece count required to adjudicate by extinction rule (default: 0) # flagPiece: piece type for capture the flag win rule [PieceType] (default: -) -# whiteFlag: white's target region for capture the flag win rule [Bitboard] (default: ) -# blackFlag: black's target region for capture the flag win rule [Bitboard] (default: ) +# flagRegionWhite: white's target region for capture the flag win rule [Bitboard] (default: ) +# flagRegionBlack: black's target region for capture the flag win rule [Bitboard] (default: ) # flagMove: black gets one more move after white captures the flag [bool] (default: false) # checkCounting: enable check count win rule (check count is communicated via FEN, see 3check) [bool] (default: false) # connectN: number of aligned pieces for win [int] (default: 0) @@ -252,7 +270,8 @@ # startFen = rbsgk/4p/5/P4/KGSBR[-] w 0 1 # pieceDrops = true # capturesToHand = true -# promotionRank = 5 +# promotionRegionWhite = *5 +# promotionRegionBlack = *1 # doubleStep = false # castling = false # promotedPieceType = p:g s:g b:h r:d @@ -267,6 +286,13 @@ # nFoldValue = loss # nFoldValueAbsolute = true +# pawns with extra sideways and backwards movement +# example for defining a custom pawn type +# resetting the original "p" piece with "pawn = -" is optional as already done implicitly +[allwayspawns:chess] +customPiece1 = p:mWfceFifmnD +pawnTypes = p + # Hybrid variant of three-check chess and crazyhouse, using crazyhouse as a template [3check-crazyhouse:crazyhouse] startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1 @@ -280,8 +306,8 @@ blastOnCapture = true [atomic-giveaway-hill:giveaway] blastOnCapture = true flagPiece = k -whiteFlag = d4 e4 d5 e5 -blackFlag = d4 e4 d5 e5 +flagRegionWhite = d4 e4 d5 e5 +flagRegionBlack = d4 e4 d5 e5 # Crazyhouse with mandatory captures, using crazyhouse as a template [coffeehouse:crazyhouse] @@ -337,7 +363,8 @@ amazon = g maxRank = 10 maxFile = 10 startFen = rnbqkgvbnr/ppppwwpppp/4pp4/10/10/10/10/4PP4/PPPPWWPPPP/RNBVGKQBNR w - - 0 1 -promotionRank = 10 +promotionRegionWhite = *10 +promotionRegionBlack = *1 promotionPieceTypes = q doubleStep = false castling = false @@ -387,7 +414,8 @@ startFen = nnnnknnn/pppppppp/2p2p2/1pppppp1/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 # Semi-torpedo chess [semitorpedo:chess] -doubleStepRank = 3 +doubleStepRegionWhite = *2 *3 +doubleStepRegionBlack = *7 *6 # This variant is similar to Capablanca Chess, but with two archbishops and no chancellor piece. [gemini:janus] @@ -406,8 +434,8 @@ promotionPieceTypes = q castling = false stalemateValue = loss flagPiece = q -whiteFlag = *8 -blackFlag = *1 +flagRegionWhite = *8 +flagRegionBlack = *1 [tictactoe] maxRank = 3 @@ -456,7 +484,8 @@ centaur = g archbishop = a chancellor = m fers = f -promotionRank = 6 +promotionRegionWhite = *6 *7 *8 +promotionRegionBlack = *3 *2 *1 promotionLimit = g:1 a:1 m:1 q:1 promotionPieceTypes = - promotedPieceType = p:c n:g b:a r:m f:q @@ -477,8 +506,8 @@ silver = y promotionPieceTypes = qh startFen = lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 flagPiece = k -whiteFlag = *8 -blackFlag = *1 +flagRegionWhite = *8 +flagRegionBlack = *1 # Ordamirror # https://vchess.club/#/variants/Ordamirror @@ -491,8 +520,8 @@ customPiece1 = f:mQcN promotionPieceTypes = lhaf startFen = lhafkahl/8/pppppppp/8/8/PPPPPPPP/8/LHAFKAHL w - - 0 1 flagPiece = k -whiteFlag = *8 -blackFlag = *1 +flagRegionWhite = *8 +flagRegionBlack = *1 # Hybrid variant of Gothic-chess and crazyhouse, using Capablanca as a template [gothhouse:capablanca] @@ -517,8 +546,8 @@ flyingGeneral = true capturesToHand = false blackDropRegion = *5 flagPiece = k -whiteFlag = *8 -blackFlag = *1 +flagRegionWhite = *8 +flagRegionBlack = *1 # Capture chess # https://vchess.club/#/variants/Capture @@ -548,8 +577,8 @@ blackDropRegion = *8 *7 *6 *5 # Crossing chess # https://vchess.club/#/variants/Crossing [crossing:kingofthehill] -whiteFlag = *5 -blackFlag = *4 +flagRegionWhite = *5 +flagRegionBlack = *4 # 4x5 Chess # https://greenchess.net/rules.php?v=4x5-chess --> Solved draw @@ -563,7 +592,8 @@ startFen = rnbk/pppp/4/PPPP/RNBK w - - 0 1 [4x6chess:gardner] maxRank = 6 maxFile = d -promotionRank = 6 +promotionRegionWhite = *6 +promotionRegionBlack = *1 startFen = rnbk/pppp/4/4/PPPP/RNBK w - - 0 1 # 5x6 chess @@ -571,7 +601,8 @@ startFen = rnbk/pppp/4/4/PPPP/RNBK w - - 0 1 [5x6chess:gardner] maxRank = 6 maxFile = e -promotionRank = 6 +promotionRegionWhite = *6 +promotionRegionBlack = *1 startFen = rnbqk/ppppp/5/5/PPPPP/RNBQK w - - 0 1 # Active chess @@ -585,7 +616,8 @@ startFen = rnbkqbnrq/ppppppppp/9/9/9/9/PPPPPPPPP/RNBKQBNRQ w KQkq - 0 1 extinctionPseudoRoyal = true maxRank = 6 maxFile = f -promotionRank = 6 +promotionRegionWhite = *6 +promotionRegionBlack = *1 doubleStep = false startFen = rbqkbr/pppppp/6/6/PPPPPP/RBQKBR w - - 0 1 @@ -639,8 +671,8 @@ startFen = rnbqkbnr/pppppppp/8/8/8/PPPSSPPP/8/TECDKCET w kq - 0 1 stalemateValue = loss nFoldValue = loss flagPiece = k -whiteFlag = *8 -blackFlag = *1 +flagRegionWhite = *8 +flagRegionBlack = *1 flyingGeneral = true # Shinobi Chess @@ -655,7 +687,8 @@ archbishop = j fers = m shogiKnight = h lance = l -promotionRank = 7 +promotionRegionWhite = *7 *8 +promotionRegionBlack = *2 *1 promotionPieceTypes = - promotedPieceType = p:c m:b h:n l:r mandatoryPiecePromotion = true @@ -667,8 +700,8 @@ whiteDropRegion = *1 *2 *3 *4 blackDropRegion = *5 *6 *7 *8 immobilityIllegal = true flagPiece = k -whiteFlag = *8 -blackFlag = *1 +flagRegionWhite = *8 +flagRegionBlack = *1 # Wildebeest # https://vchess.club/#/variants/Wildebeest @@ -678,10 +711,15 @@ maxRank = 10 maxFile = k customPiece1 = c:C customPiece2 = w:NC +doubleStepRegionWhite = *2 *3 +doubleStepRegionBlack = *9 *8 +tripleStepRegionWhite = *2 +tripleStepRegionBlack = *9 pieceToCharTable = PNBRQ.......C....WKpnbrq.......c....wk startFen = rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w KQkq - 0 1 promotionPieceTypes = qw -promotionRank = 9 +promotionRegionWhite = *9 *10 +promotionRegionBlack = *2 *1 mandatoryPawnPromotion = false castling = false @@ -718,7 +756,8 @@ chancellor = m fers = f wazir = v centaur = t -promotionRank = 7 +promotionRegionWhite = *7 *8 *9 +promotionRegionBlack = *3 *2 *1 promotedPieceType = p:g n:o b:h r:d a:c v:m f:q s:w u:t doubleStep = false perpetualCheckIllegal = true @@ -726,32 +765,32 @@ nMoveRule = 0 nFoldValue = loss stalemateValue = loss flagPiece = k -whiteFlag = *9 -blackFlag = *1 +flagRegionWhite = *9 +flagRegionBlack = *1 # 5x5 breakthrough [breakthrough5:breakthrough] maxFile = 5 maxRank = 5 startFen = ppppp/ppppp/5/PPPPP/PPPPP w 0 1 -whiteFlag = *5 -blackFlag = *1 +flagRegionWhite = *5 +flagRegionBlack = *1 # 6x6 breakthrough [breakthrough6:breakthrough] maxFile = 6 maxRank = 6 startFen = pppppp/pppppp/6/6/PPPPPP/PPPPPP w 0 1 -whiteFlag = *6 -blackFlag = *1 +flagRegionWhite = *6 +flagRegionBlack = *1 # 7x7 breakthrough [breakthrough7:breakthrough] maxFile = 7 maxRank = 7 startFen = ppppppp/ppppppp/7/7/7/PPPPPPP/PPPPPPP w 0 1 -whiteFlag = *7 -blackFlag = *1 +flagRegionWhite = *7 +flagRegionBlack = *1 # Mansindam (Pantheon tale) # A variant that combines drop rule and powerful pieces, and there is no draw @@ -779,7 +818,8 @@ dragonHorse = h bers = t customPiece1 = i:BNW customPiece2 = s:RNF -promotionRank = 7 +promotionRegionWhite = *7 *8 *9 +promotionRegionBlack = *3 *2 *1 doubleStep = false castling = false promotedPieceType = p:g n:e b:h r:t c:i m:s @@ -788,8 +828,8 @@ stalemateValue = loss nMoveRule = 0 nFoldValue = loss flagPiece = k -whiteFlag = *9 -blackFlag = *1 +flagRegionWhite = *9 +flagRegionBlack = *1 immobilityIllegal = true mandatoryPiecePromotion = true diff --git a/test.py b/test.py index c8101dc..3294bb5 100644 --- a/test.py +++ b/test.py @@ -38,7 +38,8 @@ centaur = g archbishop = a chancellor = m fers = f -promotionRank = 6 +promotionRegionWhite = *6 *7 *8 +promotionRegionBlack = *3 *2 *1 promotionLimit = g:1 a:1 m:1 q:1 promotionPieceTypes = - promotedPieceType = p:c n:g b:a r:m f:q @@ -58,8 +59,8 @@ kniroo = l silver = y promotionPieceTypes = qh flagPiece = k -whiteFlag = *8 -blackFlag = *1 +flagRegionWhite = *8 +flagRegionBlack = *1 [diana:losalamos] pieceToCharTable = PNBRQ................Kpnbrq................k diff --git a/tests/perft.sh b/tests/perft.sh index 085bc02..e11d01e 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -39,6 +39,14 @@ if [[ $1 == "all" || $1 == "variant" ]]; then expect perft.exp losalamos startpos 5 191846 > /dev/null expect perft.exp losalamos "fen 6/2P3/6/1K1k2/6/6 w - - 0 1" 6 187431 > /dev/null # fairy + expect perft.exp torpedo startpos 4 209719 > /dev/null + expect perft.exp torpedo "fen rnbqkbnr/1ppppppp/8/6P1/p7/8/PPPPPP1P/RNBQKBNR w KQkq - 0 1" 4 232819 > /dev/null + expect perft.exp berolina "fen rnbqkbnr/pppp1ppp/8/2p5/5P2/8/PPP1PPPP/RNBQKBNR w KQkq c5d6 2 2" 3 46643 > /dev/null + expect perft.exp berolina "fen k7/6P1/8/8/8/2K2p2/4p3/8 w - - 0 1" 3 1983 > /dev/null + expect perft.exp berolina "fen rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PP1PPPPP/RNBQKBNR w KQkq d6c5 0 1" 2 1047 > /dev/null + expect perft.exp pawnsideways startpos 3 10022 > /dev/null + expect perft.exp pawnback startpos 3 9222 > /dev/null + expect perft.exp legan startpos 4 8138 > /dev/null expect perft.exp makruk startpos 4 273026 > /dev/null expect perft.exp cambodian startpos 4 361719 > /dev/null expect perft.exp cambodian "fen r1s1ks1r/3nm3/pppNpppp/3n4/5P2/PPPPPNPP/8/R1SKMS1R b DEe 0 0 5" 2 72 > /dev/null @@ -52,6 +60,12 @@ if [[ $1 == "all" || $1 == "variant" ]]; then expect perft.exp sittuyin "fen 2r5/6k1/6p1/3s2P1/3npR2/8/p2N2F1/3K4[] w - - 1 50" 4 373984 > /dev/null expect perft.exp sittuyin "fen 8/6s1/5P2/3n4/pR2K2S/1P6/1k4p1/8[] w - - 1 50" 4 268869 > /dev/null expect perft.exp sittuyin "fen 1k5K/3r2P1/8/8/8/8/8/8[] w - - 0 1" 5 68662 > /dev/null + expect perft.exp chigorin "fen 8/7P/2k5/8/8/5K2/p7/8 w - - 0 1" 2 120 > /dev/null + expect perft.exp spartan startpos 3 14244 > /dev/null + # duple check & mate + expect perft.exp spartan "fen k6k/hh2Q2h/8/8/8/8/8/4K3 w - - 0 1" 3 6130 > /dev/null + # self duple check with promotions + expect perft.exp spartan "fen 8/8/8/8/6Q1/8/2h3h1/4K1k1 b - - 0 1" 3 3456 > /dev/null expect perft.exp shatar startpos 4 177344 > /dev/null expect perft.exp shatranj startpos 4 68122 > /dev/null expect perft.exp amazon startpos 4 318185 > /dev/null @@ -90,6 +104,7 @@ if [[ $1 == "all" || $1 == "variant" ]]; then expect perft.exp losers startpos 4 152955 > /dev/null expect perft.exp kinglet startpos 4 197742 > /dev/null expect perft.exp threekings startpos 4 199514 > /dev/null + # pockets expect perft.exp crazyhouse startpos 4 197281 > /dev/null expect perft.exp crazyhouse "fen 2k5/8/8/8/8/8/8/4K3[QRBNPqrbnp] w - - 0 1" 2 75353 > /dev/null @@ -149,6 +164,9 @@ if [[ $1 == "all" || $1 == "largeboard" ]]; then expect perft.exp centaur startpos 3 24490 > /dev/null expect perft.exp gustav3 startpos 4 331659 > /dev/null expect perft.exp omicron startpos 4 967381 > /dev/null + expect perft.exp troitzky startpos 3 8766 > /dev/null + expect perft.exp wolf startpos 3 13722 > /dev/null + expect perft.exp wolf "fen 8/k5SP/8/8/8/8/8/8/8/7K w - - 0 1" 4 10587 > /dev/null expect perft.exp shako "fen 4kc3c/ernbq1b1re/ppp3p1pp/3p2pp2/4p5/5P4/2PN2P3/PP1PP2PPP/ER1BQKBNR1/5C3C w KQ - 0 9" 3 26325 > /dev/null expect perft.exp shako "fen 4ncr1k1/1cr2P4/pp2p2pp1/P7PN/2Ep1p4/B3P1eN2/2P1n1P3/1B1P1K4/9p/5C2CR w - - 0 1" 3 180467 > /dev/null expect perft.exp shako "fen r5k3/4q2c2/1ebppnp3/1pp3BeEQ/10/2PE2P3/1P3P4/5NP2P/rR3KB3/7C2 w Q - 3 35" 2 4940 > /dev/null -- 1.7.0.4