From c5bb473c5f5bb307910f2db74e497430f79edb28 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Mon, 4 Feb 2019 22:40:57 +0100 Subject: [PATCH] Endgame evaluation for fairy pieces No functional change for standard chess. --- src/endgame.cpp | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/endgame.h | 7 +++ src/material.cpp | 16 ++++++ src/position.cpp | 2 +- src/variant.cpp | 7 +++ 5 files changed, 174 insertions(+), 1 deletions(-) diff --git a/src/endgame.cpp b/src/endgame.cpp index fad1d65..83cb941 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -55,6 +55,19 @@ namespace { 130, 140, 150, 160, 170, 180, 190, 200 }; + // Table used to drive the king towards the edge of the board + // in KSF vs K. + constexpr int PushToOpposingSideEdges[SQUARE_NB] = { + 30, 10, 5, 0, 0, 5, 10, 30, + 40, 20, 5, 0, 0, 5, 20, 40, + 50, 30, 10, 0, 0, 10, 30, 50, + 60, 40, 20, 10, 10, 20, 40, 60, + 70, 50, 30, 20, 20, 30, 50, 70, + 80, 60, 40, 30, 30, 40, 60, 80, + 90, 70, 60, 50, 50, 60, 70, 90, + 100, 90, 80, 70, 70, 80, 90, 100 + }; + // Tables used to drive a piece towards or away from another piece constexpr int PushClose[FILE_NB] = { 0, 0, 100, 80, 60, 40, 20, 10 }; constexpr int PushAway [FILE_NB] = { 0, 5, 20, 40, 60, 80, 90, 100 }; @@ -99,6 +112,12 @@ Endgames::Endgames() { add("KQKP"); add("KQKR"); + // Fairy piece endgames + add("KNSK"); + add("KNFK"); + add("KNSFKR"); + add("KSFK"); + add("KNPK"); add("KNPKB"); add("KRPKR"); @@ -329,6 +348,130 @@ Value Endgame::operator()(const Position& pos) const { template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } +/// KFsPs vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + PushToEdges[loserKSq] + + PushClose[distance(winnerKSq, loserKSq)]; + + if ( pos.count(strongSide) >= 3 + && ( DarkSquares & pos.pieces(strongSide, FERS)) + && (~DarkSquares & pos.pieces(strongSide, FERS))) + result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_IN_MAX_PLY - 1); + else if (pos.count(strongSide) + pos.count(strongSide) < 3) + return VALUE_DRAW; + else + { + bool dark = DarkSquares & pos.pieces(strongSide, FERS); + bool light = ~DarkSquares & pos.pieces(strongSide, FERS); + + // Determine the color of ferzes from promoting pawns + 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; + } + if (!dark || !light) + return VALUE_DRAW; // we can not checkmate with same colored ferzes + } + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KNS vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = VALUE_KNOWN_WIN + + PushClose[distance(winnerKSq, loserKSq)] + + PushToOpposingSideEdges[strongSide == WHITE ? loserKSq : ~loserKSq]; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KNF vs K. Can only be won if the weaker side's king +/// is close to a corner of the same color as the fers. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + Square fersSq = pos.square(strongSide); + + // tries to drive toward corners A1 or H8. If we have a + // fers that cannot reach the above squares, we flip the kings in order + // to drive the enemy toward corners A8 or H1. + if (opposite_colors(fersSq, SQ_A1)) + { + winnerKSq = ~winnerKSq; + loserKSq = ~loserKSq; + } + + Value result = Value(PushClose[distance(winnerKSq, loserKSq)]) + + PushToCorners[loserKSq]; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KNSFKR vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = KnightValueEg + SilverValueEg + FersValueEg - RookValueEg + + PushClose[distance(winnerKSq, loserKSq)] + + PushToOpposingSideEdges[strongSide == WHITE ? loserKSq : ~loserKSq]; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KSF vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = VALUE_KNOWN_WIN + + PushClose[distance(winnerKSq, loserKSq)] + + PushToOpposingSideEdges[strongSide == WHITE ? loserKSq : ~loserKSq]; + + return strongSide == pos.side_to_move() ? result : -result; +} + + /// KB and one or more pawns vs K. It checks for draws with rook pawns and /// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW /// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling diff --git a/src/endgame.h b/src/endgame.h index b5255a2..cb982ec 100644 --- a/src/endgame.h +++ b/src/endgame.h @@ -46,6 +46,13 @@ enum EndgameCode { KQKP, // KQ vs KP KQKR, // KQ vs KR + // Fairy piece endgames + KFsPsK, // KFsPsK vs K + KNSK, // KNS vs K + KNFK, // KNF vs K + KNSFKR, // KNSFKR vs K + KSFK, // KSF vs K + SCALING_FUNCTIONS, KBPsK, // KB and pawns vs K KQKRPs, // KQ vs KR and pawns diff --git a/src/material.cpp b/src/material.cpp index 6ff6873..6d9f72c 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -55,6 +55,7 @@ namespace { // Endgame evaluation and scaling functions are accessed directly and not through // 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 ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; @@ -63,6 +64,14 @@ namespace { Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; // 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() + && !more_than_one(pos.pieces(~us)) + && (pos.count(us) || pos.count(us)) + && !(pos.count(us) - pos.count(us) - pos.count(us) - pos.count(us)); + } + bool is_KXK(const Position& pos, Color us) { return !more_than_one(pos.pieces(~us)) && pos.non_pawn_material(us) >= RookValueMg; @@ -159,6 +168,13 @@ Entry* probe(const Position& pos) { return e; for (Color c = WHITE; c <= BLACK; ++c) + if (is_KFsPsK(pos, c)) + { + e->evaluationFunction = &EvaluateKFsPsK[c]; + return e; + } + + for (Color c = WHITE; c <= BLACK; ++c) if (is_KXK(pos, c)) { e->evaluationFunction = &EvaluateKXK[c]; diff --git a/src/position.cpp b/src/position.cpp index f7860fe..63b98dc 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -571,7 +571,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { 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"; - return set(variants.find("chess")->second, fenStr, false, si, nullptr); + return set(variants.find("fairy")->second, fenStr, false, si, nullptr); } diff --git a/src/variant.cpp b/src/variant.cpp index 91c520b..e57165a 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -35,6 +35,12 @@ VariantMap variants; // Global object v->endgameEval = true; return v; } + Variant* fairy_variant() { + Variant* v = chess_variant(); + v->add_piece(SILVER, 's'); + v->add_piece(FERS, 'f'); + return v; + } Variant* makruk_variant() { Variant* v = chess_variant(); v->remove_piece(BISHOP); @@ -547,6 +553,7 @@ void VariantMap::init() { // Add to UCI_Variant option add("chess", chess_variant()); add("standard", chess_variant()); + add("fairy", fairy_variant()); // fairy variant used for endgame code initialization add("makruk", makruk_variant()); add("asean", asean_variant()); add("ai-wok", aiwok_variant()); -- 1.7.0.4