Endgame evaluation for fairy pieces
authorFabian Fichter <ianfab@users.noreply.github.com>
Mon, 4 Feb 2019 21:40:57 +0000 (22:40 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Mon, 4 Feb 2019 21:40:57 +0000 (22:40 +0100)
No functional change for standard chess.

src/endgame.cpp
src/endgame.h
src/material.cpp
src/position.cpp
src/variant.cpp

index fad1d65..83cb941 100644 (file)
@@ -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>("KQKP");
   add<KQKR>("KQKR");
 
+  // Fairy piece endgames
+  add<KNSK>("KNSK");
+  add<KNFK>("KNFK");
+  add<KNSFKR>("KNSFKR");
+  add<KSFK>("KSFK");
+
   add<KNPK>("KNPK");
   add<KNPKB>("KNPKB");
   add<KRPKR>("KRPKR");
@@ -329,6 +348,130 @@ Value Endgame<KQKR>::operator()(const Position& pos) const {
 template<> Value Endgame<KNNK>::operator()(const Position&) const { return VALUE_DRAW; }
 
 
+/// KFsPs vs K.
+template<>
+Value Endgame<KFsPsK>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+  Square winnerKSq = pos.square<KING>(strongSide);
+  Square loserKSq = pos.square<KING>(weakSide);
+
+  Value result =  pos.non_pawn_material(strongSide)
+                + pos.count<PAWN>(strongSide) * PawnValueEg
+                + PushToEdges[loserKSq]
+                + PushClose[distance(winnerKSq, loserKSq)];
+
+  if (   pos.count<FERS>(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<FERS>(strongSide) + pos.count<PAWN>(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<KNSK>::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<KING>(strongSide);
+  Square loserKSq = pos.square<KING>(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<KNFK>::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<KING>(strongSide);
+  Square loserKSq = pos.square<KING>(weakSide);
+  Square fersSq = pos.square<FERS>(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<KNSFKR>::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<KING>(strongSide);
+  Square loserKSq = pos.square<KING>(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<KSFK>::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<KING>(strongSide);
+  Square loserKSq = pos.square<KING>(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
index b5255a2..cb982ec 100644 (file)
@@ -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
index 6ff6873..6d9f72c 100644 (file)
@@ -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<KFsPsK> EvaluateKFsPsK[] = { Endgame<KFsPsK>(WHITE), Endgame<KFsPsK>(BLACK) };
   Endgame<KXK>    EvaluateKXK[] = { Endgame<KXK>(WHITE),    Endgame<KXK>(BLACK) };
 
   Endgame<KBPsK>  ScaleKBPsK[]  = { Endgame<KBPsK>(WHITE),  Endgame<KBPsK>(BLACK) };
@@ -63,6 +64,14 @@ namespace {
   Endgame<KPKP>   ScaleKPKP[]   = { Endgame<KPKP>(WHITE),   Endgame<KPKP>(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<FERS>(us) || pos.count<PAWN>(us))
+          && !(pos.count<ALL_PIECES>(us) - pos.count<FERS>(us) - pos.count<PAWN>(us) - pos.count<KING>(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];
index f7860fe..63b98dc 100644 (file)
@@ -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);
 }
 
 
index 91c520b..e57165a 100644 (file)
@@ -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());