]> git.sesse.net Git - stockfish/blobdiff - src/search.cpp
Retire ok_to_do_nullmove()
[stockfish] / src / search.cpp
index 292c35b0c38fb6934eb2002895002fc29a4928d3..f11388c595a162a1d77ddddb046d192bfd01945e 100644 (file)
@@ -301,9 +301,8 @@ namespace {
   bool connected_moves(const Position& pos, Move m1, Move m2);
   bool value_is_mate(Value value);
   bool move_is_killer(Move m, SearchStack* ss);
-  bool ok_to_do_nullmove(const Position& pos);
-  bool ok_to_prune(const Position& pos, Move m, Move threat);
   bool ok_to_use_TT(const TTEntry* tte, Depth depth, Value beta, int ply);
+  bool connected_threat(const Position& pos, Move m, Move threat);
   Value refine_eval(const TTEntry* tte, Value defaultEval, int ply);
   void update_history(const Position& pos, Move move, Depth depth, Move movesSearched[], int moveCount);
   void update_killers(Move m, SearchStack* ss);
@@ -1056,9 +1055,6 @@ namespace {
     refinedValue = bestValue = value = -VALUE_INFINITE;
     oldAlpha = alpha;
 
-    if (depth < OnePly)
-        return qsearch<PvNode>(pos, ss, alpha, beta, Depth(0), threadID);
-
     // Step 1. Initialize node and poll. Polling can abort search
     TM.incrementNodeCounter(threadID);
     ss->init(ply);
@@ -1067,7 +1063,7 @@ namespace {
     if (threadID == 0 && ++NodesSincePoll > NodesBetweenPolls)
     {
         NodesSincePoll = 0;
-        poll();  
+        poll();
     }
 
     // Step 2. Check for aborted search and immediate draw
@@ -1128,11 +1124,11 @@ namespace {
 
     // Step 6. Razoring (is omitted in PV nodes)
     if (   !PvNode
+        &&  depth < RazorDepth
+        && !isCheck
         &&  refinedValue < beta - razor_margin(depth)
         &&  ttMove == MOVE_NONE
         &&  (ss-1)->currentMove != MOVE_NULL
-        &&  depth < RazorDepth
-        && !isCheck
         && !value_is_mate(beta)
         && !pos.has_pawn_on_7th(pos.side_to_move()))
     {
@@ -1150,10 +1146,10 @@ namespace {
     if (   !PvNode
         &&  allowNullmove
         &&  depth < RazorDepth
+        &&  refinedValue >= beta + futility_margin(depth, 0)
         && !isCheck
         && !value_is_mate(beta)
-        &&  ok_to_do_nullmove(pos)
-        &&  refinedValue >= beta + futility_margin(depth, 0))
+        &&  pos.non_pawn_material(pos.side_to_move()))
         return refinedValue - futility_margin(depth, 0);
 
     // Step 8. Null move search with verification search (is omitted in PV nodes)
@@ -1163,10 +1159,10 @@ namespace {
     if (   !PvNode
         &&  allowNullmove
         &&  depth > OnePly
+        &&  refinedValue >= beta - (depth >= 4 * OnePly ? NullMoveMargin : 0)
         && !isCheck
         && !value_is_mate(beta)
-        &&  ok_to_do_nullmove(pos)
-        &&  refinedValue >= beta - (depth >= 4 * OnePly ? NullMoveMargin : 0))
+        &&  pos.non_pawn_material(pos.side_to_move()))
     {
         ss->currentMove = MOVE_NULL;
 
@@ -1179,8 +1175,8 @@ namespace {
 
         pos.do_null_move(st);
 
-        nullValue = -search<NonPV>(pos, ss+1, -beta, -alpha, depth-R*OnePly, false, threadID);
-
+        nullValue = depth-R*OnePly < OnePly ? -qsearch<NonPV>(pos, ss+1, -beta, -alpha, Depth(0), threadID)
+                                            : - search<NonPV>(pos, ss+1, -beta, -alpha, depth-R*OnePly, false, threadID);
         pos.undo_null_move();
 
         if (nullValue >= beta)
@@ -1189,14 +1185,13 @@ namespace {
             if (nullValue >= value_mate_in(PLY_MAX))
                 nullValue = beta;
 
-            if (depth < 6 * OnePly)
-                return nullValue;
-
-            // Do zugzwang verification search
-            Value v = search<NonPV>(pos, ss, alpha, beta, depth-5*OnePly, false, threadID);
-            if (v >= beta)
+            // Do zugzwang verification search at high depths
+            if (   depth < 6 * OnePly
+                || search<NonPV>(pos, ss, alpha, beta, depth-5*OnePly, false, threadID) >= beta)
                 return nullValue;
-        } else {
+        }
+        else
+        {
             // The null move failed low, which means that we may be faced with
             // some kind of threat. If the previous move was reduced, check if
             // the move that refuted the null move was somehow connected to the
@@ -1232,6 +1227,11 @@ namespace {
     // Initialize a MovePicker object for the current position
     MovePicker mp = MovePicker(pos, ttMove, depth, H, ss, (PvNode ? -VALUE_INFINITE : beta));
     CheckInfo ci(pos);
+    bool singularExtensionNode =   depth >= SingularExtensionDepth[PvNode]
+                                && tte && tte->move()
+                                && !excludedMove // Do not allow recursive singular extension search
+                                && is_lower_bound(tte->type())
+                                && tte->depth() >= depth - 3 * OnePly;
 
     // Step 10. Loop through moves
     // Loop through all legal moves until no moves remain or a beta cutoff occurs
@@ -1254,13 +1254,9 @@ namespace {
       // Singular extension search. We extend the TT move if its value is much better than
       // its siblings. To verify this we do a reduced search on all the other moves but the
       // ttMove, if result is lower then ttValue minus a margin then we extend ttMove.
-      if (   depth >= SingularExtensionDepth[PvNode]
-          && tte
+      if (   singularExtensionNode
           && move == tte->move()
-          && !excludedMove // Do not allow recursive singular extension search
-          && ext < OnePly
-          && is_lower_bound(tte->type())
-          && tte->depth() >= depth - 3 * OnePly)
+          && ext < OnePly)
       {
           Value ttValue = value_from_tt(tte->value(), ply);
 
@@ -1281,15 +1277,15 @@ namespace {
 
       // Step 12. Futility pruning (is omitted in PV nodes)
       if (   !PvNode
+          && !captureOrPromotion
           && !isCheck
           && !dangerous
-          && !captureOrPromotion
-          && !move_is_castle(move)
-          &&  move != ttMove)
+          &&  move != ttMove
+          && !move_is_castle(move))
       {
           // Move count based pruning
           if (   moveCount >= futility_move_count(depth)
-              && ok_to_prune(pos, move, ss->threatMove)
+              && !(ss->threatMove && connected_threat(pos, move, ss->threatMove))
               && bestValue > value_mated_in(PLY_MAX))
               continue;
 
@@ -1314,7 +1310,8 @@ namespace {
       // Step extra. pv search (only in PV nodes)
       // The first move in list is the expected PV
       if (PvNode && moveCount == 1)
-          value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false, threadID);
+          value = newDepth < OnePly ? -qsearch<PV>(pos, ss+1, -beta, -alpha, Depth(0), threadID)
+                                    : - search<PV>(pos, ss+1, -beta, -alpha, newDepth, false, threadID);
       else
       {
           // Step 14. Reduced depth search
@@ -1322,15 +1319,18 @@ namespace {
           bool doFullDepthSearch = true;
 
           if (    depth >= 3 * OnePly
-              && !dangerous
               && !captureOrPromotion
+              && !dangerous
               && !move_is_castle(move)
               && !move_is_killer(move, ss))
           {
               ss->reduction = reduction<PvNode>(depth, moveCount);
               if (ss->reduction)
               {
-                  value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth-ss->reduction, true, threadID);
+                  Depth d = newDepth - ss->reduction;
+                  value = d < OnePly ? -qsearch<NonPV>(pos, ss+1, -(alpha+1), -alpha, Depth(0), threadID)
+                                     : - search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true, threadID);
+
                   doFullDepthSearch = (value > alpha);
               }
 
@@ -1339,23 +1339,27 @@ namespace {
               // if the move fails high again then go with full depth search.
               if (doFullDepthSearch && ss->reduction > 2 * OnePly)
               {
+                  assert(newDepth - OnePly >= OnePly);
+
                   ss->reduction = OnePly;
                   value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth-ss->reduction, true, threadID);
                   doFullDepthSearch = (value > alpha);
               }
+              ss->reduction = Depth(0); // Restore original reduction
           }
 
           // Step 15. Full depth search
           if (doFullDepthSearch)
           {
-              ss->reduction = Depth(0);
-              value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, true, threadID);
+              value = newDepth < OnePly ? -qsearch<NonPV>(pos, ss+1, -(alpha+1), -alpha, Depth(0), threadID)
+                                        : - search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, true, threadID);
 
               // Step extra. pv search (only in PV nodes)
               // Search only for possible new PV nodes, if instead value >= beta then
               // parent node fails low with value <= alpha and tries another move.
               if (PvNode && value > alpha && value < beta)
-                  value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false, threadID);
+                  value = newDepth < OnePly ? -qsearch<PV>(pos, ss+1, -beta, -alpha, Depth(0), threadID)
+                                            : - search<PV>(pos, ss+1, -beta, -alpha, newDepth, false, threadID);
           }
       }
 
@@ -1381,13 +1385,13 @@ namespace {
       }
 
       // Step 18. Check for split
-      if (   TM.active_threads() > 1
+      if (   depth >= MinimumSplitDepth
+          && TM.active_threads() > 1
           && bestValue < beta
-          && depth >= MinimumSplitDepth
-          && Iteration <= 99
           && TM.available_thread_exists(threadID)
           && !AbortSearch
-          && !TM.thread_should_stop(threadID))
+          && !TM.thread_should_stop(threadID)
+          && Iteration <= 99)
           TM.split<FakeSplit>(pos, ss, &alpha, beta, &bestValue, depth,
                               mateThreat, &moveCount, &mp, threadID, PvNode);
     }
@@ -1676,7 +1680,7 @@ namespace {
       {
           // Move count based pruning
           if (   moveCount >= futility_move_count(sp->depth)
-              && ok_to_prune(pos, move, ss->threatMove)
+              && !(ss->threatMove && connected_threat(pos, move, ss->threatMove))
               && sp->bestValue > value_mated_in(PLY_MAX))
           {
               lock_grab(&(sp->lock));
@@ -1959,38 +1963,19 @@ namespace {
   }
 
 
-  // ok_to_do_nullmove() looks at the current position and decides whether
-  // doing a 'null move' should be allowed. In order to avoid zugzwang
-  // problems, null moves are not allowed when the side to move has very
-  // little material left. Currently, the test is a bit too simple: Null
-  // moves are avoided only when the side to move has only pawns left.
-  // It's probably a good idea to avoid null moves in at least some more
-  // complicated endgames, e.g. KQ vs KR.  FIXME
+  // connected_threat() tests whether it is safe to forward prune a move or if
+  // is somehow coonected to the threat move returned by null search.
 
-  bool ok_to_do_nullmove(const Position& pos) {
-
-    return pos.non_pawn_material(pos.side_to_move()) != Value(0);
-  }
-
-
-  // ok_to_prune() tests whether it is safe to forward prune a move. Only
-  // non-tactical moves late in the move list close to the leaves are
-  // candidates for pruning.
-
-  bool ok_to_prune(const Position& pos, Move m, Move threat) {
+  bool connected_threat(const Position& pos, Move m, Move threat) {
 
     assert(move_is_ok(m));
-    assert(threat == MOVE_NONE || move_is_ok(threat));
+    assert(threat && move_is_ok(threat));
     assert(!pos.move_is_check(m));
     assert(!pos.move_is_capture_or_promotion(m));
     assert(!pos.move_is_passed_pawn_push(m));
 
     Square mfrom, mto, tfrom, tto;
 
-    // Prune if there isn't any threat move
-    if (threat == MOVE_NONE)
-        return true;
-
     mfrom = move_from(m);
     mto = move_to(m);
     tfrom = move_from(threat);
@@ -1998,7 +1983,7 @@ namespace {
 
     // Case 1: Don't prune moves which move the threatened piece
     if (mfrom == tto)
-        return false;
+        return true;
 
     // Case 2: If the threatened piece has value less than or equal to the
     // value of the threatening piece, don't prune move which defend it.
@@ -2006,16 +1991,16 @@ namespace {
         && (   pos.midgame_value_of_piece_on(tfrom) >= pos.midgame_value_of_piece_on(tto)
             || pos.type_of_piece_on(tfrom) == KING)
         && pos.move_attacks_square(m, tto))
-        return false;
+        return true;
 
     // Case 3: If the moving piece in the threatened move is a slider, don't
     // prune safe moves which block its ray.
     if (   piece_is_slider(pos.piece_on(tfrom))
         && bit_is_set(squares_between(tfrom, tto), mto)
         && pos.see_sign(m) >= 0)
-        return false;
+        return true;
 
-    return true;
+    return false;
   }