X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fendgame.cpp;h=53c9ec8364c4b6a010ff96fec56e1ebc6ac2a814;hp=ca17f6ac8fcccb2672dcc932e4dbb46f60b048f5;hb=9690cd6295fbed93ee434e7b2e16181e475755ac;hpb=5837228aa0c0fdd4fed6dc76c04d3fbecd096f60 diff --git a/src/endgame.cpp b/src/endgame.cpp index ca17f6ac..53c9ec83 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include "bitboard.h" @@ -31,7 +30,7 @@ namespace { // Table used to drive the king towards the edge of the board // in KX vs K and KQ vs KR endgames. - const int PushToEdges[SQUARE_NB] = { + constexpr int PushToEdges[SQUARE_NB] = { 100, 90, 80, 70, 70, 80, 90, 100, 90, 70, 60, 50, 50, 60, 70, 90, 80, 60, 40, 30, 30, 40, 60, 80, @@ -44,23 +43,20 @@ namespace { // Table used to drive the king towards a corner square of the // right color in KBN vs K endgames. - const int PushToCorners[SQUARE_NB] = { - 200, 190, 180, 170, 160, 150, 140, 130, - 190, 180, 170, 160, 150, 140, 130, 140, - 180, 170, 155, 140, 140, 125, 140, 150, - 170, 160, 140, 120, 110, 140, 150, 160, - 160, 150, 140, 110, 120, 140, 160, 170, - 150, 140, 125, 140, 140, 155, 170, 180, - 140, 130, 140, 150, 160, 170, 180, 190, - 130, 140, 150, 160, 170, 180, 190, 200 + constexpr int PushToCorners[SQUARE_NB] = { + 6400, 6080, 5760, 5440, 5120, 4800, 4480, 4160, + 6080, 5760, 5440, 5120, 4800, 4480, 4160, 4480, + 5760, 5440, 4960, 4480, 4480, 4000, 4480, 4800, + 5440, 5120, 4480, 3840, 3520, 4480, 4800, 5120, + 5120, 4800, 4480, 3520, 3840, 4480, 5120, 5440, + 4800, 4480, 4000, 4480, 4480, 4960, 5440, 5760, + 4480, 4160, 4480, 4800, 5120, 5440, 5760, 6080, + 4160, 4480, 4800, 5120, 5440, 5760, 6080, 6400 }; - // Tables used to drive a piece towards or away from another piece - const int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 }; - const int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 }; - - // Pawn Rank based scaling factors used in KRPPKRP endgame - const int KRPPKRPScaleFactors[RANK_NB] = { 0, 9, 10, 14, 21, 44, 0, 0 }; + // Drive a piece close to or away from another piece + inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); } + inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); } #ifndef NDEBUG bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) { @@ -75,38 +71,39 @@ namespace { assert(pos.count(strongSide) == 1); if (file_of(pos.square(strongSide)) >= FILE_E) - sq = Square(sq ^ 7); // Mirror SQ_H1 -> SQ_A1 - - if (strongSide == BLACK) - sq = ~sq; + sq = flip_file(sq); - return sq; + return strongSide == WHITE ? sq : flip_rank(sq); } } // namespace -/// Endgames members definitions +namespace Endgames { -Endgames::Endgames() { + std::pair, Map> maps; - add("KPK"); - add("KNNK"); - add("KBNK"); - add("KRKP"); - add("KRKB"); - add("KRKN"); - add("KQKP"); - add("KQKR"); + void init() { - add("KNPK"); - add("KNPKB"); - add("KRPKR"); - add("KRPKB"); - add("KBPKB"); - add("KBPKN"); - add("KBPPKB"); - add("KRPPKRP"); + add("KPK"); + add("KNNK"); + add("KBNK"); + add("KRKP"); + add("KRKB"); + add("KRKN"); + add("KQKP"); + add("KQKR"); + add("KNNKP"); + + add("KNPK"); + add("KNPKB"); + add("KRPKR"); + add("KRPKB"); + add("KBPKB"); + add("KBPKN"); + add("KBPPKB"); + add("KRPPKRP"); + } } @@ -130,21 +127,21 @@ Value Endgame::operator()(const Position& pos) const { Value result = pos.non_pawn_material(strongSide) + pos.count(strongSide) * PawnValueEg + PushToEdges[loserKSq] - + PushClose[distance(winnerKSq, loserKSq)]; + + push_close(winnerKSq, loserKSq); if ( pos.count(strongSide) || pos.count(strongSide) ||(pos.count(strongSide) && pos.count(strongSide)) || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) && (pos.pieces(strongSide, BISHOP) & DarkSquares))) - result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_IN_MAX_PLY - 1); + result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); return strongSide == pos.side_to_move() ? result : -result; } /// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the -/// defending king towards a corner square of the right color. +/// defending king towards a corner square that our bishop attacks. template<> Value Endgame::operator()(const Position& pos) const { @@ -155,24 +152,19 @@ Value Endgame::operator()(const Position& pos) const { Square loserKSq = pos.square(weakSide); Square bishopSq = pos.square(strongSide); - // kbnk_mate_table() tries to drive toward corners A1 or H8. If we have a - // bishop that cannot reach the above squares, we flip the kings in order - // to drive the enemy toward corners A8 or H1. - if (opposite_colors(bishopSq, SQ_A1)) - { - winnerKSq = ~winnerKSq; - loserKSq = ~loserKSq; - } + // If our bishop does not attack A1/H8, we flip the enemy king square + // to drive to opposite corners (A8/H1). Value result = VALUE_KNOWN_WIN - + PushClose[distance(winnerKSq, loserKSq)] - + PushToCorners[loserKSq]; + + push_close(winnerKSq, loserKSq) + + PushToCorners[opposite_colors(bishopSq, SQ_A1) ? ~loserKSq : loserKSq]; + assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY); return strongSide == pos.side_to_move() ? result : -result; } -/// KP vs K. This endgame is evaluated with the help of a bitbase. +/// KP vs K. This endgame is evaluated with the help of a bitbase template<> Value Endgame::operator()(const Position& pos) const { @@ -214,7 +206,7 @@ Value Endgame::operator()(const Position& pos) const { Value result; // If the stronger side's king is in front of the pawn, it's a win - if (wksq < psq && file_of(wksq) == file_of(psq)) + if (forward_file_bb(WHITE, wksq) & psq) result = RookValueEg - distance(wksq, psq); // If the weaker side's king is too far from the pawn and the rook, @@ -240,7 +232,7 @@ Value Endgame::operator()(const Position& pos) const { } -/// KR vs KB. This is very simple, and always returns drawish scores. The +/// KR vs KB. This is very simple, and always returns drawish scores. The /// score is slightly bigger when the defending king is close to the edge. template<> Value Endgame::operator()(const Position& pos) const { @@ -263,7 +255,7 @@ Value Endgame::operator()(const Position& pos) const { Square bksq = pos.square(weakSide); Square bnsq = pos.square(weakSide); - Value result = Value(PushToEdges[bksq] + PushAway[distance(bksq, bnsq)]); + Value result = Value(PushToEdges[bksq] + push_away(bksq, bnsq)); return strongSide == pos.side_to_move() ? result : -result; } @@ -282,11 +274,11 @@ Value Endgame::operator()(const Position& pos) const { Square loserKSq = pos.square(weakSide); Square pawnSq = pos.square(weakSide); - Value result = Value(PushClose[distance(winnerKSq, loserKSq)]); + Value result = Value(push_close(winnerKSq, loserKSq)); if ( relative_rank(weakSide, pawnSq) != RANK_7 || distance(loserKSq, pawnSq) != 1 - || !((FileABB | FileCBB | FileFBB | FileHBB) & pawnSq)) + || ((FileBBB | FileDBB | FileEBB | FileGBB) & pawnSq)) result += QueenValueEg - PawnValueEg; return strongSide == pos.side_to_move() ? result : -result; @@ -309,7 +301,23 @@ Value Endgame::operator()(const Position& pos) const { Value result = QueenValueEg - RookValueEg + PushToEdges[loserKSq] - + PushClose[distance(winnerKSq, loserKSq)]; + + push_close(winnerKSq, loserKSq); + + 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<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + Value result = PawnValueEg + + 2 * PushToEdges[pos.square(weakSide)] + - 10 * relative_rank(weakSide, pos.square(weakSide)); return strongSide == pos.side_to_move() ? result : -result; } @@ -332,30 +340,28 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // No assertions about the material of weakSide, because we want draws to // be detected even when the weaker side has some pawns. - Bitboard pawns = pos.pieces(strongSide, PAWN); - File pawnsFile = file_of(lsb(pawns)); + Bitboard strongpawns = pos.pieces(strongSide, PAWN); + Bitboard allpawns = pos.pieces(PAWN); - // All pawns are on a single rook file? - if ( (pawnsFile == FILE_A || pawnsFile == FILE_H) - && !(pawns & ~file_bb(pawnsFile))) + // All strongSide pawns are on a single rook file? + if (!(strongpawns & ~FileABB) || !(strongpawns & ~FileHBB)) { Square bishopSq = pos.square(strongSide); - Square queeningSq = relative_square(strongSide, make_square(pawnsFile, RANK_8)); - Square kingSq = pos.square(weakSide); + Square queeningSq = relative_square(strongSide, make_square(file_of(lsb(strongpawns)), RANK_8)); + Square weakkingSq = pos.square(weakSide); if ( opposite_colors(queeningSq, bishopSq) - && distance(queeningSq, kingSq) <= 1) + && distance(queeningSq, weakkingSq) <= 1) return SCALE_FACTOR_DRAW; } // If all the pawns are on the same B or G file, then it's potentially a draw - if ( (pawnsFile == FILE_B || pawnsFile == FILE_G) - && !(pos.pieces(PAWN) & ~file_bb(pawnsFile)) + if ((!(allpawns & ~FileBBB) || !(allpawns & ~FileGBB)) && pos.non_pawn_material(weakSide) == 0 && pos.count(weakSide) >= 1) { - // Get weakSide pawn that is closest to the home rank - Square weakPawnSq = backmost_sq(weakSide, pos.pieces(weakSide, PAWN)); + // Get the least advanced weakSide pawn + Square weakPawnSq = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN)); Square strongKingSq = pos.square(strongSide); Square weakKingSq = pos.square(weakSide); @@ -364,8 +370,8 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // There's potential for a draw if our pawn is blocked on the 7th rank, // the bishop cannot attack it or they only have one pawn left if ( relative_rank(strongSide, weakPawnSq) == RANK_7 - && (pos.pieces(strongSide, PAWN) & (weakPawnSq + pawn_push(weakSide))) - && (opposite_colors(bishopSq, weakPawnSq) || pos.count(strongSide) == 1)) + && (strongpawns & (weakPawnSq + pawn_push(weakSide))) + && (opposite_colors(bishopSq, weakPawnSq) || !more_than_one(strongpawns))) { int strongKingDist = distance(weakPawnSq, strongKingSq); int weakKingDist = distance(weakPawnSq, weakKingSq); @@ -524,7 +530,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { Square bsq = pos.square(weakSide); Square psq = pos.square(strongSide); Rank rk = relative_rank(strongSide, psq); - Square push = pawn_push(strongSide); + Direction push = pawn_push(strongSide); // If the pawn is on the 5th rank and the pawn (currently) is on // the same color square as the bishop then there is a chance of @@ -578,7 +584,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { && relative_rank(strongSide, bksq) > r) { assert(r > RANK_1 && r < RANK_7); - return ScaleFactor(KRPPKRPScaleFactors[r]); + return ScaleFactor(7 * r); } return SCALE_FACTOR_NONE; } @@ -631,29 +637,8 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // Case 2: Opposite colored bishops if (opposite_colors(strongBishopSq, weakBishopSq)) - { - // We assume that the position is drawn in the following three situations: - // - // a. The pawn is on rank 5 or further back. - // b. The defending king is somewhere in the pawn's path. - // c. The defending bishop attacks some square along the pawn's path, - // and is at least three squares away from the pawn. - // - // These rules are probably not perfect, but in practice they work - // reasonably well. - - if (relative_rank(strongSide, pawnSq) <= RANK_5) - return SCALE_FACTOR_DRAW; - - Bitboard path = forward_file_bb(strongSide, pawnSq); - - if (path & pos.pieces(weakSide, KING)) - return SCALE_FACTOR_DRAW; + return SCALE_FACTOR_DRAW; - if ( (pos.attacks_from(weakBishopSq) & path) - && distance(weakBishopSq, pawnSq) >= 3) - return SCALE_FACTOR_DRAW; - } return SCALE_FACTOR_NONE; } @@ -674,8 +659,6 @@ ScaleFactor Endgame::operator()(const Position& pos) const { Square ksq = pos.square(weakSide); Square psq1 = pos.squares(strongSide)[0]; Square psq2 = pos.squares(strongSide)[1]; - Rank r1 = rank_of(psq1); - Rank r2 = rank_of(psq2); Square blockSq1, blockSq2; if (relative_rank(strongSide, psq1) > relative_rank(strongSide, psq2)) @@ -709,7 +692,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { && opposite_colors(ksq, wbsq) && ( bbsq == blockSq2 || (pos.attacks_from(blockSq2) & pos.pieces(weakSide, BISHOP)) - || distance(r1, r2) >= 2)) + || distance(psq1, psq2) >= 2)) return SCALE_FACTOR_DRAW; else if ( ksq == blockSq2 @@ -774,6 +757,9 @@ ScaleFactor Endgame::operator()(const Position& pos) const { template<> ScaleFactor Endgame::operator()(const Position& pos) const { + assert(verify_material(pos, strongSide, KnightValueMg, 1)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + Square pawnSq = pos.square(strongSide); Square bishopSq = pos.square(weakSide); Square weakKingSq = pos.square(weakSide);