diff --git a/src/endgame.cpp b/src/endgame.cpp index a95ff3731..06f18d3a7 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -94,6 +94,31 @@ namespace Endgames { add("KSFK"); add("KSFKF"); add("KRKS"); + add("KCKR"); + add("KAKR"); + + // Anti + add("RvM"); + add("MvN"); + add("NvN"); + + // Atomic + add("MPvM"); + add("MNvM"); + add("MBvM"); + add("MRvM"); + add("MQvM"); + add("MNNvM"); + + // Duck + add("MBvM"); + add("MNvM"); + add("MPvM"); + + // Racing kings + add("KQK"); + add("KRK"); + add("KK"); add("KRPKB"); add("KBPKB"); @@ -319,6 +344,42 @@ Value Endgame::operator()(const Position& pos) const { } +/// KC vs KR. Drawish, but good winning chances if king and rook are close. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, ChancellorValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square weakRook = pos.square(weakSide); + + Value result = Value(push_to_edge(weakKing, pos)) + + push_close(strongKing, weakKing) + + push_close(weakRook, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KA vs KR. Very drawish. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, ArchbishopValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = Value(push_to_edge(weakKing, pos)) + + push_close(strongKing, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + /// KNN vs KP. Very drawish, but there are some mate opportunities if we can /// press the weakSide King to a corner before the pawn advances too much. template<> @@ -945,4 +1006,305 @@ ScaleFactor Endgame::operator()(const Position& pos) const { return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; } + +/// Endgame evals for special variants +/// R vs K. The rook side always wins if there is no immediate forced capture. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ANTI); + + Square RSq = pos.square(strongSide); + Square KSq = pos.square(weakSide); + + Value result = Value(push_to_edge(KSq, pos)) + push_close(RSq, KSq); + + int dist_min = std::min(distance(RSq, KSq), distance(RSq, KSq)); + int dist_max = std::max(distance(RSq, KSq), distance(RSq, KSq)); + + if (dist_min == 0) + result += strongSide == pos.side_to_move() || dist_max > 1 ? -VALUE_KNOWN_WIN : VALUE_KNOWN_WIN; + else if (dist_min == 1) + result += weakSide == pos.side_to_move() && dist_max > 1 ? -VALUE_KNOWN_WIN : VALUE_KNOWN_WIN; + else + result += VALUE_KNOWN_WIN; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// K vs N. The king usally wins, but there are a few exceptions. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ANTI); + + Square KSq = pos.square(strongSide); + Square NSq = pos.square(weakSide); + + // wins for knight + if (pos.side_to_move() == strongSide && (attacks_bb(NSq) & KSq)) + return -VALUE_KNOWN_WIN; + if (pos.side_to_move() == weakSide && (attacks_bb(NSq) & attacks_bb(KSq))) + return VALUE_KNOWN_WIN; + + Value result = VALUE_KNOWN_WIN + push_to_edge(NSq, pos) - push_to_edge(KSq, pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + +/// N vs N. The side to move always wins/loses if the knights are on +/// same/opposite colored squares. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ANTI); + + Square N1Sq = pos.square(pos.side_to_move()); + Square N2Sq = pos.square(~pos.side_to_move()); + + Value result = VALUE_KNOWN_WIN + push_close(N1Sq, N2Sq); + + return !opposite_colors(N1Sq, N2Sq) ? result : -result; +} + + +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ATOMIC); + + // Stalemate detection with lone king + if (pos.side_to_move() == weakSide && !MoveList(pos).size()) + return VALUE_DRAW; + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + push_to_edge(loserKSq, pos) + + push_away(winnerKSq, loserKSq); + + // We need at least a major and a minor, or three minors to force checkmate + if ( ((pos.count(strongSide) || pos.count(strongSide)) && pos.count(strongSide) >= 3) + || (pos.count(strongSide) + pos.count(strongSide) >= 3 + && (pos.count(strongSide) >= 2 || ((pos.pieces(strongSide, BISHOP) & DarkSquares) + && (pos.pieces(strongSide, BISHOP) & ~DarkSquares))))) + result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_IN_MAX_PLY - 1); + + return strongSide == pos.side_to_move() ? result : -result; +} + +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ATOMIC); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + int dist = distance(winnerKSq, loserKSq); + // Draw in case of adjacent kings + if (dist <= (strongSide == pos.side_to_move() ? 1 : 2)) + return VALUE_DRAW; + + Value result = PawnValueEg + + 20 * relative_rank(strongSide, pos.square(strongSide), pos.max_rank()) - 20 + + push_away(winnerKSq, loserKSq); + + return strongSide == pos.side_to_move() ? result : -result; +} + +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ATOMIC); + + // Stalemate detection with lone king + if (pos.side_to_move() == weakSide && !MoveList(pos).size()) + return VALUE_DRAW; + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + int dist = distance(winnerKSq, loserKSq); + // Draw in case of adjacent kings + // In the case of dist == 2, the square adjacent to both kings is ensured + // not be occupied by the queen, since eval is not called when in check. + if (dist <= (strongSide == pos.side_to_move() ? 1 : 2)) + return VALUE_DRAW; + + Value result = pos.non_pawn_material(strongSide) + + push_to_edge(loserKSq, pos) + + push_away(winnerKSq, loserKSq); + + if (dist >= (strongSide == pos.side_to_move() ? 3 : 4)) + result += VALUE_KNOWN_WIN; + + return strongSide == pos.side_to_move() ? result : -result; +} + +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + + +/// Self-mate with KX vs KX. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(!pos.checkers()); // Eval is never called when in check + + // Stalemate detection with lone king + if (pos.side_to_move() == weakSide && !MoveList(pos).size()) + return VALUE_DRAW; + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) * int(VALUE_KNOWN_WIN) / int(VALUE_KNOWN_WIN + pos.non_pawn_material(strongSide)) + - pos.non_pawn_material(weakSide) + + pos.count(weakSide) * PawnValueEg + + push_to_opposing_edge(relative_square(weakSide, strongKing, pos.max_rank()), pos) * 2 + + push_close(strongKing, weakKing) * 2; + + for (Bitboard b = pos.pieces(PAWN); b;) + { + Square s = pop_lsb(b); + result += (push_close(strongKing, s) + push_close(weakKing, s)) / 2; + } + + if (!pos.count(weakSide)) + result = VALUE_DRAW; + else if (pos.count(weakSide) == 1) + result = result / 2; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KX vs K. This function is used to evaluate positions with +/// king and plenty of material vs a lone king. It simply gives the +/// attacking side a bonus for driving the defending king towards the edge +/// of the board, and for keeping the distance between the two kings small. +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + push_to_edge(weakKing, pos) + + push_close(strongKing, weakKing); + + result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Drawish, but king should stay away from the edge +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = Value(push_to_edge(weakKing, pos)) + + push_close(strongKing, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Drawish, but king should stay away from the edge +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = Value(push_to_edge(weakKing, pos)) + + push_close(strongKing, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Winning as long as pawn is safe +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongPawn = pos.square(strongSide); + + Value result = PawnValueEg + 50 * relative_rank(strongSide, strongPawn, pos.max_rank()) + + push_to_edge(weakKing, pos) + + push_close(strongKing, weakKing) + + push_close(strongKing, strongPawn) / 2 + + push_away(weakKing, strongPawn) / 2; + + return strongSide == pos.side_to_move() ? result : -result; +} + +/// Winning as long as last rank can be blocked +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongQueen = pos.square(strongSide); + + Value result; + + if ( rank_of(weakKing) < rank_of(strongQueen) + || rank_of(weakKing) + (weakSide == pos.side_to_move()) < RANK_7 + || (Rank8BB & attacks_bb(strongQueen, pos.pieces()) & ~(attacks_bb(weakKing) | attacks_bb(weakKing)))) + result = VALUE_KNOWN_WIN + 100 * rank_of(strongKing); + else + result = -VALUE_KNOWN_WIN; + + return strongSide == pos.side_to_move() ? result : -result; +} + +/// Winning as long as last rank can be blocked +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongRook = pos.square(strongSide); + + Value result; + + if ( rank_of(weakKing) < rank_of(strongRook) + || rank_of(weakKing) + (weakSide == pos.side_to_move()) < RANK_7 + || (Rank8BB & attacks_bb(strongRook, pos.pieces()) & ~(attacks_bb(weakKing) | attacks_bb(weakKing)))) + result = VALUE_KNOWN_WIN + 100 * rank_of(strongKing); + else + result = -VALUE_KNOWN_WIN; + + return strongSide == pos.side_to_move() ? result : -result; +} + +/// KvK. Pure race +template<> +Value Endgame::operator()(const Position& pos) const { + + Square whiteKing = pos.square(WHITE); + Square blackKing = pos.square(BLACK); + + Value result = (VALUE_KNOWN_WIN + 100 * std::max(rank_of(whiteKing), rank_of(blackKing))) + * std::clamp(rank_of(whiteKing) - rank_of(blackKing) - (pos.side_to_move() == BLACK), -1, 1); + + return pos.side_to_move() == WHITE ? result : -result; +} + } // namespace Stockfish diff --git a/src/endgame.h b/src/endgame.h index 52aaba663..aec3dcdba 100644 --- a/src/endgame.h +++ b/src/endgame.h @@ -54,6 +54,19 @@ enum EndgameCode { KSFK, // KSF vs K KSFKF, // KSF vs KF KRKS, // KR vs KS + KCKR, // KC vs KR + KAKR, // KA vs KR + + // Special + KXKX, + RK, + KN, + NN, + KQK, + KRK, + KBK, + KNK, + KK, SCALING_FUNCTIONS, KBPsK, // KB and pawns vs K @@ -72,7 +85,7 @@ enum EndgameCode { /// Endgame functions can be of two types depending on whether they return a /// Value or a ScaleFactor. -template using +template using eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; @@ -89,7 +102,7 @@ struct EndgameBase { }; -template> +template> struct Endgame : public EndgameBase { explicit Endgame(Color c) : EndgameBase(c) {} @@ -115,12 +128,12 @@ namespace Endgames { return std::get::value>(maps); } - template> + template> void add(const std::string& code) { StateInfo st; - map()[Position().set(code, WHITE, &st).material_key()] = Ptr(new Endgame(WHITE)); - map()[Position().set(code, BLACK, &st).material_key()] = Ptr(new Endgame(BLACK)); + map()[Position().set(code, WHITE, &st).material_key(V)] = Ptr(new Endgame(WHITE)); + map()[Position().set(code, BLACK, &st).material_key(V)] = Ptr(new Endgame(BLACK)); } template diff --git a/src/material.cpp b/src/material.cpp index 36f664e92..bea9d4676 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -61,6 +61,9 @@ namespace { // the function maps because they correspond to more than one material hash key. Endgame EvaluateKFsPsK[] = { Endgame(WHITE), Endgame(BLACK) }; Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame EvaluateKXKAtomic[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame EvaluateKXKDuck[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame EvaluateKXKXMisere[] = { Endgame(WHITE), Endgame(BLACK) }; Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; @@ -80,6 +83,15 @@ namespace { && pos.non_pawn_material(us) >= std::min(RookValueMg, 2 * SilverValueMg); } + bool is_KXK_atomic(const Position& pos, Color us) { + return !more_than_one(pos.pieces(~us)) + && pos.non_pawn_material(us) >= RookValueMg + KnightValueMg; + } + + bool is_KXKX(const Position& pos, Color us) { + return pos.non_pawn_material(us) - pos.non_pawn_material(~us) > QueenValueMg; + } + bool is_KBPsK(const Position& pos, Color us) { return pos.non_pawn_material(us) == BishopValueMg && pos.count(us) >= 1; @@ -146,7 +158,7 @@ namespace Material { Entry* probe(const Position& pos) { - Key key = pos.material_key(); + Key key = pos.material_key(pos.endgame_eval()); Entry* e = pos.this_thread()->materialTable[key]; if (e->key == key) @@ -176,83 +188,119 @@ Entry* probe(const Position& pos) { else e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); - if (pos.endgame_eval()) - { // Let's look if we have a specialized evaluation function for this particular // material configuration. Firstly we look for a fixed configuration one, then // for a generic one if the previous search failed. - if ((e->evaluationFunction = Endgames::probe(key)) != nullptr) + if (pos.endgame_eval() && (e->evaluationFunction = Endgames::probe(key)) != nullptr) return e; - for (Color c : { WHITE, BLACK }) - if (is_KFsPsK(pos, c)) + switch (pos.endgame_eval()) + { + case EG_EVAL_CHESS: + for (Color c : { WHITE, BLACK }) + if (is_KFsPsK(pos, c)) + { + e->evaluationFunction = &EvaluateKFsPsK[c]; + return e; + } + + for (Color c : { WHITE, BLACK }) + if (is_KXK(pos, c)) + { + e->evaluationFunction = &EvaluateKXK[c]; + return e; + } + + // OK, we didn't find any special evaluation function for the current material + // configuration. Is there a suitable specialized scaling function? { - e->evaluationFunction = &EvaluateKFsPsK[c]; - return e; - } + const auto* sf = Endgames::probe(key); - for (Color c : { WHITE, BLACK }) - if (is_KXK(pos, c)) - { - e->evaluationFunction = &EvaluateKXK[c]; - return e; + if (sf) + { + e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned + return e; + } } - // OK, we didn't find any special evaluation function for the current material - // configuration. Is there a suitable specialized scaling function? - const auto* sf = Endgames::probe(key); - - if (sf) - { - e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned - return e; - } - - // We didn't find any specialized scaling function, so fall back on generic - // ones that refer to more than one material distribution. Note that in this - // case we don't return after setting the function. - for (Color c : { WHITE, BLACK }) - { - if (is_KBPsK(pos, c)) - e->scalingFunction[c] = &ScaleKBPsK[c]; - - else if (is_KQKRPs(pos, c)) - e->scalingFunction[c] = &ScaleKQKRPs[c]; - } - - if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board - { - if (!pos.count(BLACK)) + // We didn't find any specialized scaling function, so fall back on generic + // ones that refer to more than one material distribution. Note that in this + // case we don't return after setting the function. + for (Color c : { WHITE, BLACK }) { - assert(pos.count(WHITE) >= 2); + if (is_KBPsK(pos, c)) + e->scalingFunction[c] = &ScaleKBPsK[c]; - e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; + else if (is_KQKRPs(pos, c)) + e->scalingFunction[c] = &ScaleKQKRPs[c]; } - else if (!pos.count(WHITE)) - { - assert(pos.count(BLACK) >= 2); - e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; - } - else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) + if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board { - // This is a special case because we set scaling functions - // for both colors instead of only one. - e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; - e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; + if (!pos.count(BLACK)) + { + assert(pos.count(WHITE) >= 2); + + e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; + } + else if (!pos.count(WHITE)) + { + assert(pos.count(BLACK) >= 2); + + e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; + } + else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) + { + // This is a special case because we set scaling functions + // for both colors instead of only one. + e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; + e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; + } } - } - - // Zero or just one pawn makes it difficult to win, even with a small material - // advantage. This catches some trivial draws like KK, KBK and KNK and gives a - // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). - if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) - e->factor[WHITE] = uint8_t(npm_w < RookValueMg && pos.count(WHITE) <= 2 ? SCALE_FACTOR_DRAW : - npm_b <= BishopValueMg && pos.count(WHITE) <= 3 ? 4 : 14); - if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) - e->factor[BLACK] = uint8_t(npm_b < RookValueMg && pos.count(BLACK) <= 2 ? SCALE_FACTOR_DRAW : - npm_w <= BishopValueMg && pos.count(BLACK) <= 3 ? 4 : 14); + // Zero or just one pawn makes it difficult to win, even with a small material + // advantage. This catches some trivial draws like KK, KBK and KNK and gives a + // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). + if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) + e->factor[WHITE] = uint8_t(npm_w < RookValueMg && pos.count(WHITE) <= 2 ? SCALE_FACTOR_DRAW : + npm_b <= BishopValueMg && pos.count(WHITE) <= 3 ? 4 : 14); + + if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) + e->factor[BLACK] = uint8_t(npm_b < RookValueMg && pos.count(BLACK) <= 2 ? SCALE_FACTOR_DRAW : + npm_w <= BishopValueMg && pos.count(BLACK) <= 3 ? 4 : 14); + break; + case EG_EVAL_ANTI: + break; + case EG_EVAL_ATOMIC: + for (Color c : { WHITE, BLACK }) + if (is_KXK_atomic(pos, c)) + { + e->evaluationFunction = &EvaluateKXKAtomic[c]; + return e; + } + break; + case EG_EVAL_DUCK: + for (Color c : { WHITE, BLACK }) + if (is_KXK(pos, c)) + { + e->evaluationFunction = &EvaluateKXKDuck[c]; + return e; + } + break; + case EG_EVAL_MISERE: + for (Color c : { WHITE, BLACK }) + if (is_KXKX(pos, c)) + { + e->evaluationFunction = &EvaluateKXKXMisere[c]; + return e; + } + break; + case EG_EVAL_RK: + break; + case NO_EG_EVAL: + break; + default: + assert(false); } // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder diff --git a/src/position.cpp b/src/position.cpp index df9aa6157..5278c0157 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -45,6 +45,7 @@ namespace Zobrist { Key inHand[PIECE_NB][SQUARE_NB]; Key checks[COLOR_NB][CHECKS_NB]; Key wall[SQUARE_NB]; + Key endgame[EG_EVAL_NB]; } @@ -178,6 +179,9 @@ void Position::init() { for (Square s = SQ_A1; s <= SQ_MAX; ++s) Zobrist::wall[s] = rng.rand(); + for (int i = NO_EG_EVAL; i < EG_EVAL_NB; ++i) + Zobrist::endgame[i] = rng.rand(); + // Prepare the cuckoo tables std::memset(cuckoo, 0, sizeof(cuckoo)); std::memset(cuckooMove, 0, sizeof(cuckooMove)); @@ -211,6 +215,10 @@ void Position::init() { #endif } +Key Position::material_key(EndgameEval e) const { + return st->materialKey ^ Zobrist::endgame[e]; +} + /// Position::set() initializes the position object with the given FEN string. /// This function is not very robust - make sure that input FENs are correct, @@ -655,9 +663,7 @@ void Position::set_state(StateInfo* si) const { Position& Position::set(const string& code, Color c, StateInfo* si) { - assert(code[0] == 'K'); - - string sides[] = { code.substr(code.find('K', 1)), // Weak + string sides[] = { code.substr(code.find('v') != string::npos ? code.find('v') + 1 : code.find('K', 1)), // Weak code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong assert(sides[0].length() > 0 && sides[0].length() < 8); @@ -665,9 +671,8 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); - string n = std::to_string(FILE_NB); - string fenStr = n + "/" + sides[0] + char(FILE_NB - sides[0].length() + '0') + "/" + n + "/" + n + "/" + n + "/" - + n + "/" + sides[1] + char(FILE_NB - sides[1].length() + '0') + "/" + n + " w - - 0 10"; + string n = std::to_string(8); + string fenStr = sides[0] + "///////" + sides[1] + " w - - 0 10"; return set(variants.find("fairy")->second, fenStr, false, si, nullptr); } @@ -1642,7 +1647,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { k ^= Zobrist::psq[captured][capsq]; st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; #ifndef NO_THREADS - prefetch(thisThread->materialTable[st->materialKey]); + prefetch(thisThread->materialTable[material_key(var->endgameEval)]); #endif // Reset rule 50 counter st->rule50 = 0; diff --git a/src/position.h b/src/position.h index 712c21eea..8868d7646 100644 --- a/src/position.h +++ b/src/position.h @@ -142,7 +142,7 @@ class Position { bool blast_on_capture() const; PieceSet blast_immune_types() const; PieceSet mutually_immune_types() const; - bool endgame_eval() const; + EndgameEval endgame_eval() const; Bitboard double_step_region(Color c) const; Bitboard triple_step_region(Color c) const; bool castling_enabled() const; @@ -301,7 +301,7 @@ class Position { // Accessing hash keys Key key() const; Key key_after(Move m) const; - Key material_key() const; + Key material_key(EndgameEval e = EG_EVAL_CHESS) const; Key pawn_key() const; // Other properties of the position @@ -507,9 +507,9 @@ inline PieceSet Position::mutually_immune_types() const { return var->mutuallyImmuneTypes; } -inline bool Position::endgame_eval() const { +inline EndgameEval Position::endgame_eval() const { assert(var != nullptr); - return var->endgameEval && !count_in_hand(ALL_PIECES) && count() == 2; + return !count_in_hand(ALL_PIECES) && (var->endgameEval != EG_EVAL_CHESS || count() == 2) ? var->endgameEval : NO_EG_EVAL; } inline Bitboard Position::double_step_region(Color c) const { @@ -1326,10 +1326,6 @@ inline Key Position::pawn_key() const { return st->pawnKey; } -inline Key Position::material_key() const { - return st->materialKey; -} - inline Score Position::psq_score() const { return psq; } diff --git a/src/types.h b/src/types.h index b049736b1..b5812abf0 100644 --- a/src/types.h +++ b/src/types.h @@ -318,6 +318,10 @@ enum WallingRule { NO_WALLING, ARROW, DUCK, EDGE, PAST, STATIC }; +enum EndgameEval { + NO_EG_EVAL, EG_EVAL_CHESS, EG_EVAL_ANTI, EG_EVAL_ATOMIC, EG_EVAL_DUCK, EG_EVAL_MISERE, EG_EVAL_RK, EG_EVAL_NB +}; + enum OptBool { NO_VALUE, VALUE_FALSE, VALUE_TRUE }; diff --git a/src/variant.cpp b/src/variant.cpp index db69f55ad..f6a83456b 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -138,6 +138,9 @@ namespace { Variant* v = chess_variant_base()->init(); v->add_piece(SILVER, 's'); v->add_piece(FERS, 'f'); + v->add_piece(ARCHBISHOP, 'a'); + v->add_piece(CHANCELLOR, 'c'); + v->add_piece(COMMONER, 'm'); return v; } // Raazuva (Maldivian Chess) @@ -342,6 +345,7 @@ namespace { v->flagMove = true; v->castling = false; v->checking = false; + v->endgameEval = EG_EVAL_RK; return v; } // Knightmate @@ -357,6 +361,15 @@ namespace { v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP; return v; } + // Misere chess + // Get checkmated to win. + // Variant used to run some selfmate analysis http://www.kotesovec.cz/gustav/gustav_alybadix.htm + Variant* misere_variant() { + Variant* v = chess_variant_base()->init(); + v->checkmateValue = VALUE_MATE; + v->endgameEval = EG_EVAL_MISERE; + return v; + } // Losers chess // https://www.chessclub.com/help/Wild17 Variant* losers_variant() { @@ -385,6 +398,7 @@ namespace { v->extinctionPieceTypes = piece_set(ALL_PIECES); v->mustCapture = true; v->nnueAlias = "antichess"; + v->endgameEval = EG_EVAL_ANTI; return v; } // Antichess @@ -393,6 +407,7 @@ namespace { Variant* v = giveaway_variant()->init(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; v->castling = false; + v->endgameEval = EG_EVAL_ANTI; return v; } // Suicide chess @@ -402,6 +417,7 @@ namespace { Variant* v = antichess_variant()->init(); v->stalematePieceCount = true; v->nnueAlias = "antichess"; + v->endgameEval = EG_EVAL_ANTI; return v; } // Codrus @@ -493,6 +509,7 @@ namespace { Variant* atomic_variant() { Variant* v = nocheckatomic_variant()->init(); v->extinctionPseudoRoyal = true; + v->endgameEval = EG_EVAL_ATOMIC; return v; } @@ -517,6 +534,7 @@ namespace { v->extinctionPieceTypes = piece_set(COMMONER); v->wallingRule = DUCK; v->stalemateValue = VALUE_MATE; + v->endgameEval = EG_EVAL_DUCK; return v; } #endif @@ -1819,6 +1837,7 @@ void VariantMap::init() { add("kingofthehill", kingofthehill_variant()); add("racingkings", racingkings_variant()); add("knightmate", knightmate_variant()); + add("misere", misere_variant()); add("losers", losers_variant()); add("giveaway", giveaway_variant()); add("antichess", antichess_variant()); @@ -2027,21 +2046,26 @@ Variant* Variant::conclude() { // For endgame evaluation to be applicable, no special win rules must apply. // Furthermore, rules significantly changing game mechanics also invalidate it. - endgameEval = extinctionValue == VALUE_NONE - && checkmateValue == -VALUE_MATE - && stalemateValue == VALUE_DRAW - && !materialCounting - && !(flagRegion[WHITE] || flagRegion[BLACK]) - && !mustCapture - && !checkCounting - && !makpongRule - && !connectN - && !blastOnCapture - && !petrifyOnCaptureTypes - && !capturesToHand - && !twoBoards - && !restrictedMobility - && kingType == KING; + endgameEval = endgameEval != EG_EVAL_CHESS + || + ( endgameEval == EG_EVAL_CHESS + && extinctionValue == VALUE_NONE + && checkmateValue == -VALUE_MATE + && stalemateValue == VALUE_DRAW + && !materialCounting + && !(flagRegion[WHITE] || flagRegion[BLACK]) + && !mustCapture + && !checkCounting + && !makpongRule + && !connectN + && !blastOnCapture + && !petrifyOnCaptureTypes + && !capturesToHand + && !twoBoards + && !restrictedMobility + && kingType == KING + ) + ? endgameEval : NO_EG_EVAL; shogiStylePromotions = false; for (PieceType current: promotedPieceType) diff --git a/src/variant.h b/src/variant.h index 03faa51fb..98fe314bc 100644 --- a/src/variant.h +++ b/src/variant.h @@ -175,7 +175,7 @@ struct Variant { int pieceHandIndex[COLOR_NB][PIECE_NB]; int kingSquareIndex[SQUARE_NB]; int nnueMaxPieces; - bool endgameEval = false; + EndgameEval endgameEval = EG_EVAL_CHESS; bool shogiStylePromotions = false; std::vector connect_directions; PieceSet connectPieceTypesTrimmed = ~NO_PIECE_SET; @@ -222,6 +222,7 @@ struct Variant { // Reset values that always need to be redefined Variant* init() { nnueAlias = ""; + endgameEval = EG_EVAL_CHESS; return this; } diff --git a/src/variants.ini b/src/variants.ini index f00787e2c..d905dcb03 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -961,12 +961,6 @@ capturesToHand = true pocketSize = 6 castling = false -# Misere Chess -# Get checkmated to win. -# Variant used to run some selfmate analysis http://www.kotesovec.cz/gustav/gustav_alybadix.htm -[misere:chess] -checkmateValue = win - # Chak # Variant invented by Couch Tomato and inspired in the Mayan civilization # https://www.pychess.org/variants/chak