Add variant-specific endgame evaluation for antichess, atomic, duck chess, misere, and racing kings.
Closes #820
add<KSFK>("KSFK");
add<KSFKF>("KSFKF");
add<KRKS>("KRKS");
+ add<KCKR>("KCKR");
+ add<KAKR>("KAKR");
+
+ // Anti
+ add<RK, EG_EVAL_ANTI>("RvM");
+ add<KN, EG_EVAL_ANTI>("MvN");
+ add<NN, EG_EVAL_ANTI>("NvN");
+
+ // Atomic
+ add<KPK, EG_EVAL_ATOMIC>("MPvM");
+ add<KNK, EG_EVAL_ATOMIC>("MNvM");
+ add<KBK, EG_EVAL_ATOMIC>("MBvM");
+ add<KRK, EG_EVAL_ATOMIC>("MRvM");
+ add<KQK, EG_EVAL_ATOMIC>("MQvM");
+ add<KNNK, EG_EVAL_ATOMIC>("MNNvM");
+
+ // Duck
+ add<KBK, EG_EVAL_DUCK>("MBvM");
+ add<KNK, EG_EVAL_DUCK>("MNvM");
+ add<KPK, EG_EVAL_DUCK>("MPvM");
+
+ // Racing kings
+ add<KQK, EG_EVAL_RK>("KQK");
+ add<KRK, EG_EVAL_RK>("KRK");
+ add<KK, EG_EVAL_RK>("KK");
add<KRPKB>("KRPKB");
add<KBPKB>("KBPKB");
}
+/// KC vs KR. Drawish, but good winning chances if king and rook are close.
+template<>
+Value Endgame<KCKR>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, ChancellorValueMg, 0));
+ assert(verify_material(pos, weakSide, RookValueMg, 0));
+
+ Square strongKing = pos.square<KING>(strongSide);
+ Square weakKing = pos.square<KING>(weakSide);
+ Square weakRook = pos.square<ROOK>(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<KAKR>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, ArchbishopValueMg, 0));
+ assert(verify_material(pos, weakSide, RookValueMg, 0));
+
+ Square strongKing = pos.square<KING>(strongSide);
+ Square weakKing = pos.square<KING>(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<>
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<RK, EG_EVAL_ANTI>::operator()(const Position& pos) const {
+
+ assert(pos.endgame_eval() == EG_EVAL_ANTI);
+
+ Square RSq = pos.square<ROOK>(strongSide);
+ Square KSq = pos.square<COMMONER>(weakSide);
+
+ Value result = Value(push_to_edge(KSq, pos)) + push_close(RSq, KSq);
+
+ int dist_min = std::min(distance<Rank>(RSq, KSq), distance<File>(RSq, KSq));
+ int dist_max = std::max(distance<Rank>(RSq, KSq), distance<File>(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<KN, EG_EVAL_ANTI>::operator()(const Position& pos) const {
+
+ assert(pos.endgame_eval() == EG_EVAL_ANTI);
+
+ Square KSq = pos.square<COMMONER>(strongSide);
+ Square NSq = pos.square<KNIGHT>(weakSide);
+
+ // wins for knight
+ if (pos.side_to_move() == strongSide && (attacks_bb<KNIGHT>(NSq) & KSq))
+ return -VALUE_KNOWN_WIN;
+ if (pos.side_to_move() == weakSide && (attacks_bb<KNIGHT>(NSq) & attacks_bb<KING>(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<NN, EG_EVAL_ANTI>::operator()(const Position& pos) const {
+
+ assert(pos.endgame_eval() == EG_EVAL_ANTI);
+
+ Square N1Sq = pos.square<KNIGHT>(pos.side_to_move());
+ Square N2Sq = pos.square<KNIGHT>(~pos.side_to_move());
+
+ Value result = VALUE_KNOWN_WIN + push_close(N1Sq, N2Sq);
+
+ return !opposite_colors(N1Sq, N2Sq) ? result : -result;
+}
+
+
+template<>
+Value Endgame<KXK, EG_EVAL_ATOMIC>::operator()(const Position& pos) const {
+
+ assert(pos.endgame_eval() == EG_EVAL_ATOMIC);
+
+ // Stalemate detection with lone king
+ if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size())
+ return VALUE_DRAW;
+
+ Square winnerKSq = pos.square<COMMONER>(strongSide);
+ Square loserKSq = pos.square<COMMONER>(weakSide);
+
+ Value result = pos.non_pawn_material(strongSide)
+ + pos.count<PAWN>(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<QUEEN>(strongSide) || pos.count<ROOK>(strongSide)) && pos.count<ALL_PIECES>(strongSide) >= 3)
+ || (pos.count<BISHOP>(strongSide) + pos.count<KNIGHT>(strongSide) >= 3
+ && (pos.count<KNIGHT>(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<KPK, EG_EVAL_ATOMIC>::operator()(const Position& pos) const {
+
+ assert(pos.endgame_eval() == EG_EVAL_ATOMIC);
+
+ Square winnerKSq = pos.square<COMMONER>(strongSide);
+ Square loserKSq = pos.square<COMMONER>(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<PAWN>(strongSide), pos.max_rank()) - 20
+ + push_away(winnerKSq, loserKSq);
+
+ return strongSide == pos.side_to_move() ? result : -result;
+}
+
+template<> Value Endgame<KNK, EG_EVAL_ATOMIC>::operator()(const Position&) const { return VALUE_DRAW; }
+
+template<> Value Endgame<KBK, EG_EVAL_ATOMIC>::operator()(const Position&) const { return VALUE_DRAW; }
+
+template<> Value Endgame<KRK, EG_EVAL_ATOMIC>::operator()(const Position&) const { return VALUE_DRAW; }
+
+template<>
+Value Endgame<KQK, EG_EVAL_ATOMIC>::operator()(const Position& pos) const {
+
+ assert(pos.endgame_eval() == EG_EVAL_ATOMIC);
+
+ // Stalemate detection with lone king
+ if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size())
+ return VALUE_DRAW;
+
+ Square winnerKSq = pos.square<COMMONER>(strongSide);
+ Square loserKSq = pos.square<COMMONER>(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<KNNK, EG_EVAL_ATOMIC>::operator()(const Position&) const { return VALUE_DRAW; }
+
+
+/// Self-mate with KX vs KX.
+template<>
+Value Endgame<KXKX, EG_EVAL_MISERE>::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<LEGAL>(pos).size())
+ return VALUE_DRAW;
+
+ Square strongKing = pos.square<KING>(strongSide);
+ Square weakKing = pos.square<KING>(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<PAWN>(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<PAWN>(weakSide))
+ result = VALUE_DRAW;
+ else if (pos.count<PAWN>(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<KXK, EG_EVAL_DUCK>::operator()(const Position& pos) const {
+
+ Square strongKing = pos.square<COMMONER>(strongSide);
+ Square weakKing = pos.square<COMMONER>(weakSide);
+
+ Value result = pos.non_pawn_material(strongSide)
+ + pos.count<PAWN>(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<KNK, EG_EVAL_DUCK>::operator()(const Position& pos) const {
+
+ Square strongKing = pos.square<COMMONER>(strongSide);
+ Square weakKing = pos.square<COMMONER>(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<KBK, EG_EVAL_DUCK>::operator()(const Position& pos) const {
+
+ Square strongKing = pos.square<COMMONER>(strongSide);
+ Square weakKing = pos.square<COMMONER>(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<KPK, EG_EVAL_DUCK>::operator()(const Position& pos) const {
+
+ Square strongKing = pos.square<COMMONER>(strongSide);
+ Square weakKing = pos.square<COMMONER>(weakSide);
+ Square strongPawn = pos.square<PAWN>(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<KQK, EG_EVAL_RK>::operator()(const Position& pos) const {
+
+ Square strongKing = pos.square<KING>(strongSide);
+ Square weakKing = pos.square<KING>(weakSide);
+ Square strongQueen = pos.square<QUEEN>(strongSide);
+
+ Value result;
+
+ if ( rank_of(weakKing) < rank_of(strongQueen)
+ || rank_of(weakKing) + (weakSide == pos.side_to_move()) < RANK_7
+ || (Rank8BB & attacks_bb<QUEEN>(strongQueen, pos.pieces()) & ~(attacks_bb<QUEEN>(weakKing) | attacks_bb<SHOGI_KNIGHT>(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<KRK, EG_EVAL_RK>::operator()(const Position& pos) const {
+
+ Square strongKing = pos.square<KING>(strongSide);
+ Square weakKing = pos.square<KING>(weakSide);
+ Square strongRook = pos.square<ROOK>(strongSide);
+
+ Value result;
+
+ if ( rank_of(weakKing) < rank_of(strongRook)
+ || rank_of(weakKing) + (weakSide == pos.side_to_move()) < RANK_7
+ || (Rank8BB & attacks_bb<ROOK>(strongRook, pos.pieces()) & ~(attacks_bb<QUEEN>(weakKing) | attacks_bb<SHOGI_KNIGHT>(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<KK, EG_EVAL_RK>::operator()(const Position& pos) const {
+
+ Square whiteKing = pos.square<KING>(WHITE);
+ Square blackKing = pos.square<KING>(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
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
/// Endgame functions can be of two types depending on whether they return a
/// Value or a ScaleFactor.
-template<EndgameCode E> using
+template<EndgameCode E, EndgameEval V = EG_EVAL_CHESS> using
eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type;
};
-template<EndgameCode E, typename T = eg_type<E>>
+template<EndgameCode E, EndgameEval V = EG_EVAL_CHESS, typename T = eg_type<E, V>>
struct Endgame : public EndgameBase<T> {
explicit Endgame(Color c) : EndgameBase<T>(c) {}
return std::get<std::is_same<T, ScaleFactor>::value>(maps);
}
- template<EndgameCode E, typename T = eg_type<E>>
+ template<EndgameCode E, EndgameEval V = EG_EVAL_CHESS, typename T = eg_type<E, V>>
void add(const std::string& code) {
StateInfo st;
- map<T>()[Position().set(code, WHITE, &st).material_key()] = Ptr<T>(new Endgame<E>(WHITE));
- map<T>()[Position().set(code, BLACK, &st).material_key()] = Ptr<T>(new Endgame<E>(BLACK));
+ map<T>()[Position().set(code, WHITE, &st).material_key(V)] = Ptr<T>(new Endgame<E, V>(WHITE));
+ map<T>()[Position().set(code, BLACK, &st).material_key(V)] = Ptr<T>(new Endgame<E, V>(BLACK));
}
template<typename T>
// the function maps because they correspond to more than one material hash key.
Endgame<KFsPsK> EvaluateKFsPsK[] = { Endgame<KFsPsK>(WHITE), Endgame<KFsPsK>(BLACK) };
Endgame<KXK> EvaluateKXK[] = { Endgame<KXK>(WHITE), Endgame<KXK>(BLACK) };
+ Endgame<KXK, EG_EVAL_ATOMIC> EvaluateKXKAtomic[] = { Endgame<KXK, EG_EVAL_ATOMIC>(WHITE), Endgame<KXK, EG_EVAL_ATOMIC>(BLACK) };
+ Endgame<KXK, EG_EVAL_DUCK> EvaluateKXKDuck[] = { Endgame<KXK, EG_EVAL_DUCK>(WHITE), Endgame<KXK, EG_EVAL_DUCK>(BLACK) };
+ Endgame<KXKX, EG_EVAL_MISERE> EvaluateKXKXMisere[] = { Endgame<KXKX, EG_EVAL_MISERE>(WHITE), Endgame<KXKX, EG_EVAL_MISERE>(BLACK) };
Endgame<KBPsK> ScaleKBPsK[] = { Endgame<KBPsK>(WHITE), Endgame<KBPsK>(BLACK) };
Endgame<KQKRPs> ScaleKQKRPs[] = { Endgame<KQKRPs>(WHITE), Endgame<KQKRPs>(BLACK) };
&& 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<PAWN>(us) >= 1;
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)
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<Value>(key)) != nullptr)
+ if (pos.endgame_eval() && (e->evaluationFunction = Endgames::probe<Value>(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<ScaleFactor>(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<ScaleFactor>(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<PAWN>(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<PAWN>(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<PAWN>(WHITE))
- {
- assert(pos.count<PAWN>(BLACK) >= 2);
- e->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
- }
- else if (pos.count<PAWN>(WHITE) == 1 && pos.count<PAWN>(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<PAWN>(BLACK))
+ {
+ assert(pos.count<PAWN>(WHITE) >= 2);
+
+ e->scalingFunction[WHITE] = &ScaleKPsK[WHITE];
+ }
+ else if (!pos.count<PAWN>(WHITE))
+ {
+ assert(pos.count<PAWN>(BLACK) >= 2);
+
+ e->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
+ }
+ else if (pos.count<PAWN>(WHITE) == 1 && pos.count<PAWN>(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<PAWN>(WHITE) && npm_w - npm_b <= BishopValueMg)
- e->factor[WHITE] = uint8_t(npm_w < RookValueMg && pos.count<ALL_PIECES>(WHITE) <= 2 ? SCALE_FACTOR_DRAW :
- npm_b <= BishopValueMg && pos.count<ALL_PIECES>(WHITE) <= 3 ? 4 : 14);
- if (!pos.count<PAWN>(BLACK) && npm_b - npm_w <= BishopValueMg)
- e->factor[BLACK] = uint8_t(npm_b < RookValueMg && pos.count<ALL_PIECES>(BLACK) <= 2 ? SCALE_FACTOR_DRAW :
- npm_w <= BishopValueMg && pos.count<ALL_PIECES>(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<PAWN>(WHITE) && npm_w - npm_b <= BishopValueMg)
+ e->factor[WHITE] = uint8_t(npm_w < RookValueMg && pos.count<ALL_PIECES>(WHITE) <= 2 ? SCALE_FACTOR_DRAW :
+ npm_b <= BishopValueMg && pos.count<ALL_PIECES>(WHITE) <= 3 ? 4 : 14);
+
+ if (!pos.count<PAWN>(BLACK) && npm_b - npm_w <= BishopValueMg)
+ e->factor[BLACK] = uint8_t(npm_b < RookValueMg && pos.count<ALL_PIECES>(BLACK) <= 2 ? SCALE_FACTOR_DRAW :
+ npm_w <= BishopValueMg && pos.count<ALL_PIECES>(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
Key inHand[PIECE_NB][SQUARE_NB];
Key checks[COLOR_NB][CHECKS_NB];
Key wall[SQUARE_NB];
+ Key endgame[EG_EVAL_NB];
}
for (Square s = SQ_A1; s <= SQ_MAX; ++s)
Zobrist::wall[s] = rng.rand<Key>();
+ for (int i = NO_EG_EVAL; i < EG_EVAL_NB; ++i)
+ Zobrist::endgame[i] = rng.rand<Key>();
+
// Prepare the cuckoo tables
std::memset(cuckoo, 0, sizeof(cuckoo));
std::memset(cuckooMove, 0, sizeof(cuckooMove));
#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,
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);
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);
}
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;
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;
// 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
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<KING>() == 2;
+ return !count_in_hand(ALL_PIECES) && (var->endgameEval != EG_EVAL_CHESS || count<KING>() == 2) ? var->endgameEval : NO_EG_EVAL;
}
inline Bitboard Position::double_step_region(Color c) const {
return st->pawnKey;
}
-inline Key Position::material_key() const {
- return st->materialKey;
-}
-
inline Score Position::psq_score() const {
return psq;
}
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
};
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)
v->flagMove = true;
v->castling = false;
v->checking = false;
+ v->endgameEval = EG_EVAL_RK;
return v;
}
// Knightmate
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() {
v->extinctionPieceTypes = piece_set(ALL_PIECES);
v->mustCapture = true;
v->nnueAlias = "antichess";
+ v->endgameEval = EG_EVAL_ANTI;
return v;
}
// Antichess
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
Variant* v = antichess_variant()->init();
v->stalematePieceCount = true;
v->nnueAlias = "antichess";
+ v->endgameEval = EG_EVAL_ANTI;
return v;
}
// Codrus
Variant* atomic_variant() {
Variant* v = nocheckatomic_variant()->init();
v->extinctionPseudoRoyal = true;
+ v->endgameEval = EG_EVAL_ATOMIC;
return v;
}
v->extinctionPieceTypes = piece_set(COMMONER);
v->wallingRule = DUCK;
v->stalemateValue = VALUE_MATE;
+ v->endgameEval = EG_EVAL_DUCK;
return v;
}
#endif
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());
// 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)
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<Direction> connect_directions;
PieceSet connectPieceTypesTrimmed = ~NO_PIECE_SET;
// Reset values that always need to be redefined
Variant* init() {
nnueAlias = "";
+ endgameEval = EG_EVAL_CHESS;
return this;
}
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