]> git.sesse.net Git - stockfish/blobdiff - src/movepick.cpp
Use an enum instead of a table as MovePicker sequencer
[stockfish] / src / movepick.cpp
index b35d1012cb817f718f6700d495fd9f08a5276ee1..3da4247a29e3cdff10de95c06838b7ddb173ae1c 100644 (file)
@@ -1,7 +1,7 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
   Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad
+  Copyright (C) 2008-2012 Marco Costalba, Joona Kiiski, 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,6 +18,7 @@
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <algorithm>
 #include <cassert>
 
 #include "movegen.h"
 
 namespace {
 
-  enum MovegenPhase {
-    PH_TT_MOVE,       // Transposition table move
-    PH_GOOD_CAPTURES, // Queen promotions and captures with SEE values >= captureThreshold (captureThreshold <= 0)
-    PH_GOOD_PROBCUT,  // Queen promotions and captures with SEE values > captureThreshold (captureThreshold >= 0)
-    PH_KILLERS,       // Killer moves from the current ply
-    PH_NONCAPTURES,   // Non-captures and underpromotions
-    PH_BAD_CAPTURES,  // Queen promotions and captures with SEE values < captureThreshold (captureThreshold <= 0)
-    PH_EVASIONS,      // Check evasions
-    PH_QCAPTURES,     // Captures in quiescence search
-    PH_QCHECKS,       // Non-capture checks in quiescence search
-    PH_STOP
+  enum Sequencer {
+    MAIN_SEARCH,         TT_MOVE_S1, GOOD_CAPTURES_S1, KILLERS_S1, NONCAPTURES_1_S1,
+                         NONCAPTURES_2_S1, BAD_CAPTURES_S1, STOP_S1,
+    EVASIONS,            TT_MOVE_S2, EVASIONS_S2, STOP_S2,
+    CAPTURES_AND_CHECKS, TT_MOVE_S3, QCAPTURES_S3, QCHECKS_S3, STOP_S3,
+    CAPTURES,            TT_MOVE_S4, QCAPTURES_S4, STOP_S4,
+    RECAPTURES,          TT_MOVE_S5, RECAPTURES_S5, STOP_S5,
+    PROBCUT,             TT_MOVE_S6, GOOD_CAPTURES_S6, STOP_S6
   };
 
-  CACHE_LINE_ALIGNMENT
-  const uint8_t MainSearchTable[] = { PH_TT_MOVE, PH_GOOD_CAPTURES, PH_KILLERS, PH_NONCAPTURES, PH_BAD_CAPTURES, PH_STOP };
-  const uint8_t EvasionTable[] = { PH_TT_MOVE, PH_EVASIONS, PH_STOP };
-  const uint8_t QsearchWithChecksTable[] = { PH_TT_MOVE, PH_QCAPTURES, PH_QCHECKS, PH_STOP };
-  const uint8_t QsearchWithoutChecksTable[] = { PH_TT_MOVE, PH_QCAPTURES, PH_STOP };
-  const uint8_t ProbCutTable[] = { PH_TT_MOVE, PH_GOOD_PROBCUT, PH_STOP };
+  // Unary predicate used by std::partition to split positive scores from remaining
+  // ones so to sort separately the two sets, and with the second sort delayed.
+  inline bool has_positive_score(const MoveStack& move) { return move.score > 0; }
+
+  // Picks and pushes to the front the best move in range [firstMove, lastMove),
+  // it is faster than sorting all the moves in advance when moves are few, as
+  // normally are the possible captures.
+  inline MoveStack* pick_best(MoveStack* firstMove, MoveStack* lastMove)
+  {
+      std::swap(*firstMove, *std::max_element(firstMove, lastMove));
+      return firstMove;
+  }
 }
 
 /// Constructors for the MovePicker class. As arguments we pass information
@@ -55,7 +59,7 @@ namespace {
 /// move ordering is at the current node.
 
 MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h,
-                       SearchStack* ss, Value beta) : pos(p), H(h) {
+                       Search::Stack* ss, Value beta) : pos(p), H(h), depth(d) {
   captureThreshold = 0;
   badCaptures = moves + MAX_MOVES;
 
@@ -64,66 +68,77 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h,
   if (p.in_check())
   {
       killers[0].move = killers[1].move = MOVE_NONE;
-      phasePtr = EvasionTable;
+      phase = EVASIONS;
   }
   else
   {
       killers[0].move = ss->killers[0];
       killers[1].move = ss->killers[1];
 
-      // Consider sligtly negative captures as good if at low
-      // depth and far from beta.
+      // Consider sligtly negative captures as good if at low depth and far from beta
       if (ss && ss->eval < beta - PawnValueMidgame && d < 3 * ONE_PLY)
           captureThreshold = -PawnValueMidgame;
 
-      phasePtr = MainSearchTable;
+      // Consider negative captures as good if still enough to reach beta
+      else if (ss && ss->eval > beta)
+          captureThreshold = beta - ss->eval;
+
+      phase = MAIN_SEARCH;
   }
 
-  ttMove = (ttm && pos.move_is_pl(ttm) ? ttm : MOVE_NONE);
-  phasePtr += int(ttMove == MOVE_NONE) - 1;
+  ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE);
+  phase += int(ttMove == MOVE_NONE);
   go_next_phase();
 }
 
-MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h)
+MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h, Square recaptureSq)
                       : pos(p), H(h) {
 
   assert(d <= DEPTH_ZERO);
 
   if (p.in_check())
-      phasePtr = EvasionTable;
+      phase = EVASIONS;
+
   else if (d >= DEPTH_QS_CHECKS)
-      phasePtr = QsearchWithChecksTable;
-  else
+      phase = CAPTURES_AND_CHECKS;
+
+  else if (d >= DEPTH_QS_RECAPTURES)
   {
-      phasePtr = QsearchWithoutChecksTable;
+      phase = CAPTURES;
 
       // Skip TT move if is not a capture or a promotion, this avoids
       // qsearch tree explosion due to a possible perpetual check or
       // similar rare cases when TT table is full.
-      if (ttm != MOVE_NONE && !pos.move_is_capture(ttm) && !move_is_promotion(ttm))
+      if (ttm != MOVE_NONE && !pos.is_capture_or_promotion(ttm))
           ttm = MOVE_NONE;
   }
+  else
+  {
+      phase = RECAPTURES;
+      recaptureSquare = recaptureSq;
+      ttm = MOVE_NONE;
+  }
 
-  ttMove = (ttm && pos.move_is_pl(ttm) ? ttm : MOVE_NONE);
-  phasePtr += int(ttMove == MOVE_NONE) - 1;
+  ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE);
+  phase += int(ttMove == MOVE_NONE);
   go_next_phase();
 }
 
-MovePicker::MovePicker(const Position& p, Move ttm, const History& h, int parentCapture)
+MovePicker::MovePicker(const Position& p, Move ttm, const History& h, PieceType parentCapture)
                        : pos(p), H(h) {
 
   assert (!pos.in_check());
 
   // In ProbCut we consider only captures better than parent's move
-  captureThreshold = parentCapture;
-  phasePtr = ProbCutTable;
+  captureThreshold = PieceValueMidgame[Piece(parentCapture)];
+  phase = PROBCUT;
 
   if (   ttm != MOVE_NONE
-      && (!pos.move_is_capture(ttm) ||  pos.see(ttm) <= captureThreshold))
+      && (!pos.is_capture(ttm) ||  pos.see(ttm) <= captureThreshold))
       ttm = MOVE_NONE;
 
-  ttMove = (ttm && pos.move_is_pl(ttm) ? ttm : MOVE_NONE);
-  phasePtr += int(ttMove == MOVE_NONE) - 1;
+  ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE);
+  phase += int(ttMove == MOVE_NONE);
   go_next_phase();
 }
 
@@ -134,59 +149,73 @@ MovePicker::MovePicker(const Position& p, Move ttm, const History& h, int parent
 void MovePicker::go_next_phase() {
 
   curMove = moves;
-  phase = *(++phasePtr);
-  switch (phase) {
 
-  case PH_TT_MOVE:
+  switch (++phase) {
+
+  case TT_MOVE_S1: case TT_MOVE_S2: case TT_MOVE_S3:
+  case TT_MOVE_S4: case TT_MOVE_S5: case TT_MOVE_S6:
       lastMove = curMove + 1;
       return;
 
-  case PH_GOOD_CAPTURES:
-  case PH_GOOD_PROBCUT:
+  case GOOD_CAPTURES_S1:
+  case GOOD_CAPTURES_S6:
       lastMove = generate<MV_CAPTURE>(pos, moves);
       score_captures();
       return;
 
-  case PH_KILLERS:
+  case KILLERS_S1:
       curMove = killers;
       lastMove = curMove + 2;
       return;
 
-  case PH_NONCAPTURES:
-      lastMove = generate<MV_NON_CAPTURE>(pos, moves);
+  case NONCAPTURES_1_S1:
+      lastNonCapture = lastMove = generate<MV_NON_CAPTURE>(pos, moves);
       score_noncaptures();
-      sort_moves(moves, lastMove, &lastGoodNonCapture);
+      lastMove = std::partition(curMove, lastMove, has_positive_score);
+      sort<MoveStack>(curMove, lastMove);
+      return;
+
+  case NONCAPTURES_2_S1:
+      curMove = lastMove;
+      lastMove = lastNonCapture;
+      if (depth >= 3 * ONE_PLY)
+          sort<MoveStack>(curMove, lastMove);
       return;
 
-  case PH_BAD_CAPTURES:
+  case BAD_CAPTURES_S1:
       // Bad captures SEE value is already calculated so just pick
       // them in order to get SEE move ordering.
       curMove = badCaptures;
       lastMove = moves + MAX_MOVES;
       return;
 
-  case PH_EVASIONS:
+  case EVASIONS_S2:
       assert(pos.in_check());
       lastMove = generate<MV_EVASION>(pos, moves);
       score_evasions();
       return;
 
-  case PH_QCAPTURES:
+  case QCAPTURES_S3:
+  case QCAPTURES_S4:
       lastMove = generate<MV_CAPTURE>(pos, moves);
       score_captures();
       return;
 
-  case PH_QCHECKS:
+  case RECAPTURES_S5:
+      lastMove = generate<MV_CAPTURE>(pos, moves);
+      return;
+
+  case QCHECKS_S3:
       lastMove = generate<MV_NON_CAPTURE_CHECK>(pos, moves);
       return;
 
-  case PH_STOP:
+  case STOP_S1: case STOP_S2: case STOP_S3:
+  case STOP_S4: case STOP_S5: case STOP_S6:
       lastMove = curMove + 1; // Avoid another go_next_phase() call
       return;
 
   default:
       assert(false);
-      return;
   }
 }
 
@@ -194,7 +223,7 @@ void MovePicker::go_next_phase() {
 /// MovePicker::score_captures(), MovePicker::score_noncaptures() and
 /// MovePicker::score_evasions() assign a numerical move ordering score
 /// to each move in a move list.  The moves with highest scores will be
-/// picked first by get_next_move().
+/// picked first by next_move().
 
 void MovePicker::score_captures() {
   // Winning and equal captures in the main search are ordered by MVV/LVA.
@@ -216,11 +245,11 @@ void MovePicker::score_captures() {
   for (MoveStack* cur = moves; cur != lastMove; cur++)
   {
       m = cur->move;
-      if (move_is_promotion(m))
-          cur->score = QueenValueMidgame;
-      else
-          cur->score =  pos.midgame_value_of_piece_on(move_to(m))
-                      - pos.type_of_piece_on(move_from(m));
+      cur->score =  PieceValueMidgame[pos.piece_on(to_sq(m))]
+                  - type_of(pos.piece_moved(m));
+
+      if (is_promotion(m))
+          cur->score += PieceValueMidgame[Piece(promotion_piece_type(m))];
   }
 }
 
@@ -232,8 +261,8 @@ void MovePicker::score_noncaptures() {
   for (MoveStack* cur = moves; cur != lastMove; cur++)
   {
       m = cur->move;
-      from = move_from(m);
-      cur->score = H.value(pos.piece_on(from), move_to(m));
+      from = from_sq(m);
+      cur->score = H.value(pos.piece_on(from), to_sq(m));
   }
 }
 
@@ -254,22 +283,22 @@ void MovePicker::score_evasions() {
       m = cur->move;
       if ((seeScore = pos.see_sign(m)) < 0)
           cur->score = seeScore - History::MaxValue; // Be sure we are at the bottom
-      else if (pos.move_is_capture(m))
-          cur->score =  pos.midgame_value_of_piece_on(move_to(m))
-                      - pos.type_of_piece_on(move_from(m)) + History::MaxValue;
+      else if (pos.is_capture(m))
+          cur->score =  PieceValueMidgame[pos.piece_on(to_sq(m))]
+                      - type_of(pos.piece_moved(m)) + History::MaxValue;
       else
-          cur->score = H.value(pos.piece_on(move_from(m)), move_to(m));
+          cur->score = H.value(pos.piece_moved(m), to_sq(m));
   }
 }
 
-/// MovePicker::get_next_move() is the most important method of the MovePicker
-/// class. It returns a new pseudo legal move every time it is called, until there
+/// MovePicker::next_move() is the most important method of the MovePicker class.
+/// It returns a new pseudo legal move every time it is called, until there
 /// are no more moves left. It picks the move with the biggest score from a list
 /// of generated moves taking care not to return the tt move if has already been
 /// searched previously. Note that this function is not thread safe so should be
 /// lock protected by caller when accessed through a shared MovePicker object.
 
-Move MovePicker::get_next_move() {
+Move MovePicker::next_move() {
 
   Move move;
 
@@ -280,13 +309,14 @@ Move MovePicker::get_next_move() {
 
       switch (phase) {
 
-      case PH_TT_MOVE:
+      case TT_MOVE_S1: case TT_MOVE_S2: case TT_MOVE_S3:
+      case TT_MOVE_S4: case TT_MOVE_S5: case TT_MOVE_S6:
           curMove++;
           return ttMove;
           break;
 
-      case PH_GOOD_CAPTURES:
-          move = pick_best(curMove++, lastMove).move;
+      case GOOD_CAPTURES_S1:
+          move = pick_best(curMove++, lastMove)->move;
           if (move != ttMove)
           {
               assert(captureThreshold <= 0); // Otherwise we must use see instead of see_sign
@@ -296,34 +326,30 @@ Move MovePicker::get_next_move() {
               if (seeValue >= captureThreshold)
                   return move;
 
-              // Losing capture, move it to the tail of the array, note
-              // that move has now been already checked for pseudo legality.
+              // Losing capture, move it to the tail of the array
               (--badCaptures)->move = move;
               badCaptures->score = seeValue;
           }
           break;
 
-     case PH_GOOD_PROBCUT:
-          move = pick_best(curMove++, lastMove).move;
+     case GOOD_CAPTURES_S6:
+          move = pick_best(curMove++, lastMove)->move;
           if (   move != ttMove
               && pos.see(move) > captureThreshold)
               return move;
           break;
 
-      case PH_KILLERS:
+      case KILLERS_S1:
           move = (curMove++)->move;
           if (   move != MOVE_NONE
-              && pos.move_is_pl(move)
+              && pos.is_pseudo_legal(move)
               && move != ttMove
-              && !pos.move_is_capture(move))
+              && !pos.is_capture(move))
               return move;
           break;
 
-      case PH_NONCAPTURES:
-          // Sort negative scored moves only when we get there
-          if (curMove == lastGoodNonCapture)
-              insertion_sort<MoveStack>(lastGoodNonCapture, lastMove);
-
+      case NONCAPTURES_1_S1:
+      case NONCAPTURES_2_S1:
           move = (curMove++)->move;
           if (   move != ttMove
               && move != killers[0].move
@@ -331,29 +357,36 @@ Move MovePicker::get_next_move() {
               return move;
           break;
 
-      case PH_BAD_CAPTURES:
-          move = pick_best(curMove++, lastMove).move;
+      case BAD_CAPTURES_S1:
+          move = pick_best(curMove++, lastMove)->move;
           return move;
 
-      case PH_EVASIONS:
-      case PH_QCAPTURES:
-          move = pick_best(curMove++, lastMove).move;
+      case EVASIONS_S2:
+      case QCAPTURES_S3:
+      case QCAPTURES_S4:
+          move = pick_best(curMove++, lastMove)->move;
           if (move != ttMove)
               return move;
           break;
 
-      case PH_QCHECKS:
+      case RECAPTURES_S5:
+          move = (curMove++)->move;
+          if (to_sq(move) == recaptureSquare)
+              return move;
+          break;
+
+      case QCHECKS_S3:
           move = (curMove++)->move;
           if (move != ttMove)
               return move;
           break;
 
-      case PH_STOP:
+      case STOP_S1: case STOP_S2: case STOP_S3:
+      case STOP_S4: case STOP_S5: case STOP_S6:
           return MOVE_NONE;
 
       default:
           assert(false);
-          break;
       }
   }
 }