]> git.sesse.net Git - stockfish/blobdiff - src/search.cpp
Added a new function build_pv(), which extends a PV by walking
[stockfish] / src / search.cpp
index 08e3a997c4d3c63365e24040e1803d298e8b3075..d922986b7fd7b3744441e92e352391925dd5d967 100644 (file)
@@ -28,6 +28,7 @@
 #include <iostream>
 #include <sstream>
 
+#include "bitcount.h"
 #include "book.h"
 #include "evaluate.h"
 #include "history.h"
@@ -195,6 +196,16 @@ namespace {
 
   /// Variables initialized by UCI options
 
+  // Adjustable playing strength
+  int Slowdown = 0;
+  const int SlowdownArray[32] = {
+    19, 41, 70, 110, 160, 230, 320, 430, 570, 756, 1000, 1300, 1690, 2197,
+    2834, 3600, 4573, 5809, 7700, 9863, 12633, 16181, 20726, 26584, 34005,
+    43557, 55792, 71463, 91536, 117247, 150180, 192363
+  };
+  int Strength;
+  const int MaxStrength = 25;
+
   // Minimum number of full depth (i.e. non-reduced) moves at PV and non-PV nodes
   int LMRPVMoves, LMRNonPVMoves; // heavy SMP read access for the latter
 
@@ -281,7 +292,7 @@ namespace {
   Value qsearch(Position& pos, SearchStack ss[], Value alpha, Value beta, Depth depth, int ply, int threadID);
   void sp_search(SplitPoint* sp, int threadID);
   void sp_search_pv(SplitPoint* sp, int threadID);
-  void init_node(SearchStack ss[], int ply, int threadID);
+  void init_node(const Position& pos, SearchStack ss[], int ply, int threadID);
   void update_pv(SearchStack ss[], int ply);
   void sp_update_pv(SearchStack* pss, SearchStack ss[], int ply);
   bool connected_moves(const Position& pos, Move m1, Move m2);
@@ -294,6 +305,8 @@ namespace {
   bool ok_to_history(const Position& pos, Move m);
   void update_history(const Position& pos, Move m, Depth depth, Move movesSearched[], int moveCount);
   void update_killers(Move m, SearchStack& ss);
+  void slowdown(const Position& pos);
+  void build_pv(const Position& pos, Move pv[]);
 
   bool fail_high_ply_1();
   int current_search_time();
@@ -414,7 +427,10 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move,
 
   read_weights(pos.side_to_move());
 
-  int newActiveThreads = get_option_value_int("Threads");
+  // Set the number of active threads. If UCI_LimitStrength is enabled, never
+  // use more than one thread.
+  int newActiveThreads =
+    get_option_value_bool("UCI_LimitStrength")? 1 : get_option_value_int("Threads");
   if (newActiveThreads != ActiveThreads)
   {
       ActiveThreads = newActiveThreads;
@@ -427,6 +443,19 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move,
   for (int i = 1; i < ActiveThreads; i++)
       assert(thread_is_available(i, 0));
 
+  // Set playing strength
+  if (get_option_value_bool("UCI_LimitStrength"))
+  {
+      Strength = (get_option_value_int("UCI_Elo") - 2100) / 25;
+      Slowdown =
+        (Strength == MaxStrength)? 0 : SlowdownArray[Max(0, 31-Strength)];
+  }
+  else
+  {
+      Strength = MaxStrength;
+      Slowdown = 0;
+  }
+
   // Set thinking time
   int myTime = time[side_to_move];
   int myIncrement = increment[side_to_move];
@@ -471,10 +500,16 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move,
       NodesBetweenPolls = Min(MaxNodes, 30000);
       InfiniteSearch = true; // HACK
   }
+  else if (Slowdown) {
+      if (Slowdown > 50000) NodesBetweenPolls = 30;
+      else if (Slowdown > 10000) NodesBetweenPolls = 100;
+      else if (Slowdown > 1000) NodesBetweenPolls = 500;
+      else if (Slowdown > 100) NodesBetweenPolls = 3000;
+      else NodesBetweenPolls = 15000;
+  }
   else
       NodesBetweenPolls = 30000;
 
-
   // Write information to search log file
   if (UseLogFile)
       LogFile << "Searching: " << pos.to_fen() << std::endl
@@ -858,6 +893,7 @@ namespace {
                       << " currmovenumber " << i + 1 << std::endl;
 
         // Decide search depth for this move
+        bool moveIsCapture = pos.move_is_capture(move);
         bool dangerous;
         ext = extension(pos, move, true, pos.move_is_capture(move), pos.move_is_check(move), false, false, &dangerous);
         newDepth = (Iteration - 2) * OnePly + ext + InitialDepth;
@@ -883,15 +919,30 @@ namespace {
         }
         else
         {
-            value = -search(pos, ss, -alpha, newDepth, 1, true, 0);
-            if (value > alpha)
+            if (newDepth >= 3*OnePly
+                && i + MultiPV >= LMRPVMoves
+                && !dangerous
+                && !moveIsCapture
+                && !move_is_promotion(move)
+                && !move_is_castle(move))
             {
-                // Fail high! Set the boolean variable FailHigh to true, and
-                // re-search the move with a big window. The variable FailHigh is
-                // used for time managment: We try to avoid aborting the search
-                // prematurely during a fail high research.
-                FailHigh = true;
-                value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0);
+                ss[0].reduction = OnePly;
+                value = -search(pos, ss, -alpha, newDepth-OnePly, 1, true, 0);
+            }
+            else
+                value = alpha + 1; // Just to trigger next condition
+            if(value > alpha)
+            {
+                value = -search(pos, ss, -alpha, newDepth, 1, true, 0);
+                if (value > alpha)
+                {
+                    // Fail high! Set the boolean variable FailHigh to true, and
+                    // re-search the move with a big window. The variable FailHigh is
+                    // used for time managment: We try to avoid aborting the search
+                    // prematurely during a fail high research.
+                    FailHigh = true;
+                    value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0);
+                }
             }
         }
 
@@ -925,6 +976,7 @@ namespace {
             // Update PV
             rml.set_move_score(i, value);
             update_pv(ss, 0);
+            build_pv(pos, ss[0].pv);
             rml.set_move_pv(i, ss[0].pv);
 
             if (MultiPV == 1)
@@ -1006,7 +1058,7 @@ namespace {
 
     // Initialize, and make an early exit in case of an aborted search,
     // an instant draw, maximum ply reached, etc.
-    init_node(ss, ply, threadID);
+    init_node(pos, ss, ply, threadID);
 
     // After init_node() that calls poll()
     if (AbortSearch || thread_should_stop(threadID))
@@ -1080,7 +1132,7 @@ namespace {
       {
         // Try to reduce non-pv search depth by one ply if move seems not problematic,
         // if the move fails high will be re-searched at full depth.
-        if (    depth >= 2*OnePly
+        if (    depth >= 3*OnePly
             &&  moveCount >= LMRPVMoves
             && !dangerous
             && !moveIsCapture
@@ -1196,7 +1248,7 @@ namespace {
 
     // Initialize, and make an early exit in case of an aborted search,
     // an instant draw, maximum ply reached, etc.
-    init_node(ss, ply, threadID);
+    init_node(pos, ss, ply, threadID);
 
     // After init_node() that calls poll()
     if (AbortSearch || thread_should_stop(threadID))
@@ -1243,16 +1295,13 @@ namespace {
 
         StateInfo st;
         pos.do_null_move(st);
+        int R = (depth >= 5 * OnePly ? 4 : 3); // Null move dynamic reduction
 
-        Value nullValue = -search(pos, ss, -(beta-1), depth-4*OnePly, ply+1, false, threadID);
+        Value nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID);
 
         pos.undo_null_move();
 
-        if (value_is_mate(nullValue))
-        {
-            /* Do not return unproven mates */
-        }
-        else if (nullValue >= beta)
+        if (nullValue >= beta)
         {
             if (depth < 6 * OnePly)
                 return beta;
@@ -1363,7 +1412,7 @@ namespace {
 
       // Try to reduce non-pv search depth by one ply if move seems not problematic,
       // if the move fails high will be re-searched at full depth.
-      if (    depth >= (ss[ply-1].currentMove == MOVE_NULL ? 3 : 2) * OnePly
+      if (    depth >= 3*OnePly
           &&  moveCount >= LMRNonPVMoves
           && !dangerous
           && !moveIsCapture
@@ -1455,7 +1504,7 @@ namespace {
 
     // Initialize, and make an early exit in case of an aborted search,
     // an instant draw, maximum ply reached, etc.
-    init_node(ss, ply, threadID);
+    init_node(pos, ss, ply, threadID);
 
     // After init_node() that calls poll()
     if (AbortSearch || thread_should_stop(threadID))
@@ -2042,11 +2091,14 @@ namespace {
   // NodesBetweenPolls nodes, init_node() also calls poll(), which polls
   // for user input and checks whether it is time to stop the search.
 
-  void init_node(SearchStack ss[], int ply, int threadID) {
+  void init_node(const Position& pos, SearchStack ss[], int ply, int threadID) {
 
     assert(ply >= 0 && ply < PLY_MAX);
     assert(threadID >= 0 && threadID < ActiveThreads);
 
+    if (Slowdown && Iteration >= 3)
+      slowdown(pos);
+
     Threads[threadID].nodes++;
 
     if (threadID == 0)
@@ -2201,16 +2253,19 @@ namespace {
     assert(m != MOVE_NONE);
 
     Depth result = Depth(0);
-    *dangerous = check || singleReply || mateThreat;
+    *dangerous = check | singleReply | mateThreat;
 
-    if (check)
-        result += CheckExtension[pvNode];
+    if (*dangerous)
+    {
+        if (check)
+            result += CheckExtension[pvNode];
 
-    if (singleReply)
-        result += SingleReplyExtension[pvNode];
+        if (singleReply)
+            result += SingleReplyExtension[pvNode];
 
-    if (mateThreat)
-        result += MateThreatExtension[pvNode];
+        if (mateThreat)
+            result += MateThreatExtension[pvNode];
+    }
 
     if (pos.type_of_piece_on(move_from(m)) == PAWN)
     {
@@ -2376,6 +2431,52 @@ namespace {
     ss.killers[0] = m;
   }
 
+
+  // slowdown() simply wastes CPU cycles doing nothing useful. It's used
+  // in strength handicap mode.
+
+  void slowdown(const Position &pos) {
+    int i, n;
+    n = Slowdown;
+    for (i = 0; i < n; i++)  {
+        Square s = Square(i&63);
+        if (count_1s(pos.attacks_to(s)) > 63)
+            std::cout << "This can't happen, but I put this string here anyway, in order to prevent the compiler from optimizing away the useless computation." << std::endl;
+    }
+  }
+
+
+  // build_pv() extends a PV by adding moves from the transposition table at
+  // the end. This should ensure that the PV is almost always at least two
+  // plies long, which is important, because otherwise we will often get
+  // single-move PVs when the search stops while failing high, and a
+  // single-move PV means that we don't have a ponder move.
+
+  void build_pv(const Position& pos, Move pv[]) {
+    int ply;
+    Position p(pos);
+    StateInfo st[100];
+
+    for (ply = 0; pv[ply] != MOVE_NONE; ply++)
+        p.do_move(pv[ply], st[ply]);
+
+    bool stop;
+    const TTEntry* tte;
+    for (stop = false, tte = TT.retrieve(p.get_key());
+         tte && tte->move() != MOVE_NONE && !stop;
+         tte = TT.retrieve(p.get_key()), ply++)
+    {
+        if (!move_is_legal(p, tte->move(), p.pinned_pieces(p.side_to_move())))
+            break;
+        pv[ply] = tte->move();
+        p.do_move(pv[ply], st[ply]);
+        for (int j = 0; j < ply; j++)
+            if (st[j].key == p.get_key()) stop = true;
+    }
+    pv[ply] = MOVE_NONE;
+  }
+
+
   // fail_high_ply_1() checks if some thread is currently resolving a fail
   // high at ply 1 at the node below the first root node.  This information
   // is used for time managment.