/*
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) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
-
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "movepick.h"
+
+#include <algorithm>
#include <cassert>
+#include <iterator>
+#include <utility>
-#include "movegen.h"
-#include "movepick.h"
-#include "search.h"
-#include "types.h"
+#include "bitboard.h"
+#include "position.h"
+
+namespace Stockfish {
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_1, // Non-captures and underpromotions with positive score
- PH_NONCAPTURES_2, // Non-captures and underpromotions with non-positive score
- PH_BAD_CAPTURES, // Queen promotions and captures with SEE values < captureThreshold (captureThreshold <= 0)
- PH_EVASIONS, // Check evasions
- PH_QCAPTURES, // Captures in quiescence search
- PH_QRECAPTURES, // Recaptures in quiescence search
- PH_QCHECKS, // Non-capture checks in quiescence search
- PH_STOP
- };
-
- CACHE_LINE_ALIGNMENT
- const uint8_t MainSearchTable[] = { PH_TT_MOVE, PH_GOOD_CAPTURES, PH_KILLERS, PH_NONCAPTURES_1, PH_NONCAPTURES_2, 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 QsearchRecapturesTable[] = { PH_TT_MOVE, PH_QRECAPTURES, PH_STOP };
- const uint8_t ProbCutTable[] = { PH_TT_MOVE, PH_GOOD_PROBCUT, PH_STOP };
+enum Stages {
+ // generate main search moves
+ MAIN_TT,
+ CAPTURE_INIT,
+ GOOD_CAPTURE,
+ REFUTATION,
+ QUIET_INIT,
+ QUIET,
+ BAD_CAPTURE,
+
+ // generate evasion moves
+ EVASION_TT,
+ EVASION_INIT,
+ EVASION,
+
+ // generate probcut moves
+ PROBCUT_TT,
+ PROBCUT_INIT,
+ PROBCUT,
+
+ // generate qsearch moves
+ QSEARCH_TT,
+ QCAPTURE_INIT,
+ QCAPTURE,
+ QCHECK_INIT,
+ QCHECK
+};
+
+// Sort moves in descending order up to and including
+// a given limit. The order of moves smaller than the limit is left unspecified.
+void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
+
+ for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p)
+ if (p->value >= limit)
+ {
+ ExtMove tmp = *p, *q;
+ *p = *++sortedEnd;
+ for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q)
+ *q = *(q - 1);
+ *q = tmp;
+ }
}
-/// Constructors for the MovePicker class. As arguments we pass information
-/// to help it to return the presumably good moves first, to decide which
-/// moves to return (in the quiescence search, for instance, we only want to
-/// search captures, promotions and some checks) and about how important good
-/// 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), depth(d) {
- captureThreshold = 0;
- badCaptures = moves + MAX_MOVES;
-
- assert(d > DEPTH_ZERO);
-
- if (p.in_check())
- {
- killers[0].move = killers[1].move = MOVE_NONE;
- phasePtr = EvasionTable;
- }
- 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.
- if (ss && ss->eval < beta - PawnValueMidgame && d < 3 * ONE_PLY)
- captureThreshold = -PawnValueMidgame;
-
- phasePtr = MainSearchTable;
- }
-
- ttMove = (ttm && pos.move_is_pl(ttm) ? ttm : MOVE_NONE);
- phasePtr += int(ttMove == MOVE_NONE) - 1;
- go_next_phase();
+} // namespace
+
+
+// Constructors of the MovePicker class. As arguments, we pass information
+// to help it return the (presumably) good moves first, to decide which
+// moves to return (in the quiescence search, for instance, we only want to
+// search captures, promotions, and some checks) and how important a good
+// move ordering is at the current node.
+
+// MovePicker constructor for the main search
+MovePicker::MovePicker(const Position& p,
+ Move ttm,
+ Depth d,
+ const ButterflyHistory* mh,
+ const CapturePieceToHistory* cph,
+ const PieceToHistory** ch,
+ const PawnHistory* ph,
+ Move cm,
+ const Move* killers) :
+ pos(p),
+ mainHistory(mh),
+ captureHistory(cph),
+ continuationHistory(ch),
+ pawnHistory(ph),
+ ttMove(ttm),
+ refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}},
+ depth(d) {
+ assert(d > 0);
+
+ stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm));
}
-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;
- else if (d >= DEPTH_QS_CHECKS)
- phasePtr = QsearchWithChecksTable;
- else if (d >= DEPTH_QS_RECAPTURES)
- {
- phasePtr = QsearchWithoutChecksTable;
-
- // 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_or_promotion(ttm))
- ttm = MOVE_NONE;
- }
- else
- {
- phasePtr = QsearchRecapturesTable;
- recaptureSquare = recaptureSq;
- ttm = MOVE_NONE;
- }
-
- ttMove = (ttm && pos.move_is_pl(ttm) ? ttm : MOVE_NONE);
- phasePtr += int(ttMove == MOVE_NONE) - 1;
- go_next_phase();
+// Constructor for quiescence search
+MovePicker::MovePicker(const Position& p,
+ Move ttm,
+ Depth d,
+ const ButterflyHistory* mh,
+ const CapturePieceToHistory* cph,
+ const PieceToHistory** ch,
+ const PawnHistory* ph) :
+ pos(p),
+ mainHistory(mh),
+ captureHistory(cph),
+ continuationHistory(ch),
+ pawnHistory(ph),
+ ttMove(ttm),
+ depth(d) {
+ assert(d <= 0);
+
+ stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm));
}
-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 = piece_value_midgame(Piece(parentCapture));
- phasePtr = ProbCutTable;
-
- if ( ttm != MOVE_NONE
- && (!pos.move_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;
- go_next_phase();
+// Constructor for ProbCut: we generate captures with SEE greater
+// than or equal to the given threshold.
+MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) :
+ pos(p),
+ captureHistory(cph),
+ ttMove(ttm),
+ threshold(th) {
+ assert(!pos.checkers());
+
+ stage = PROBCUT_TT
+ + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold));
}
-
-/// MovePicker::go_next_phase() generates, scores and sorts the next bunch
-/// of moves when there are no more moves to try for the current phase.
-
-void MovePicker::go_next_phase() {
-
- curMove = moves;
- phase = *(++phasePtr);
- switch (phase) {
-
- case PH_TT_MOVE:
- lastMove = curMove + 1;
- return;
-
- case PH_GOOD_CAPTURES:
- case PH_GOOD_PROBCUT:
- lastMove = generate<MV_CAPTURE>(pos, moves);
- score_captures();
- return;
-
- case PH_KILLERS:
- curMove = killers;
- lastMove = curMove + 2;
- return;
-
- case PH_NONCAPTURES_1:
- lastNonCapture = lastMove = generate<MV_NON_CAPTURE>(pos, moves);
- score_noncaptures();
- sort_moves(moves, lastNonCapture, &lastMove);
- return;
-
- case PH_NONCAPTURES_2:
- curMove = lastMove;
- lastMove = lastNonCapture;
- if (depth >= 3 * ONE_PLY)
- insertion_sort<MoveStack>(curMove, lastMove);
- return;
-
- case PH_BAD_CAPTURES:
- // 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:
- assert(pos.in_check());
- lastMove = generate<MV_EVASION>(pos, moves);
- score_evasions();
- return;
-
- case PH_QCAPTURES:
- lastMove = generate<MV_CAPTURE>(pos, moves);
- score_captures();
- return;
-
- case PH_QRECAPTURES:
- lastMove = generate<MV_CAPTURE>(pos, moves);
- return;
-
- case PH_QCHECKS:
- lastMove = generate<MV_NON_CAPTURE_CHECK>(pos, moves);
- return;
-
- case PH_STOP:
- lastMove = curMove + 1; // Avoid another go_next_phase() call
- return;
-
- default:
- assert(false);
- return;
- }
+// Assigns a numerical value to each move in a list, used
+// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring
+// captures with a good history. Quiets moves are ordered using the history tables.
+template<GenType Type>
+void MovePicker::score() {
+
+ static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
+
+ [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook,
+ threatenedPieces;
+ if constexpr (Type == QUIETS)
+ {
+ Color us = pos.side_to_move();
+
+ threatenedByPawn = pos.attacks_by<PAWN>(~us);
+ threatenedByMinor =
+ pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
+ threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
+
+ // Pieces threatened by pieces of lesser material value
+ threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook)
+ | (pos.pieces(us, ROOK) & threatenedByMinor)
+ | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn);
+ }
+
+ for (auto& m : *this)
+ if constexpr (Type == CAPTURES)
+ m.value =
+ (7 * int(PieceValue[pos.piece_on(to_sq(m))])
+ + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))])
+ / 16;
+
+ else if constexpr (Type == QUIETS)
+ {
+ Piece pc = pos.moved_piece(m);
+ PieceType pt = type_of(pos.moved_piece(m));
+ Square from = from_sq(m);
+ Square to = to_sq(m);
+
+ // histories
+ m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)];
+ m.value += 2 * (*pawnHistory)[pawn_structure(pos)][pc][to];
+ m.value += 2 * (*continuationHistory[0])[pc][to];
+ m.value += (*continuationHistory[1])[pc][to];
+ m.value += (*continuationHistory[2])[pc][to] / 4;
+ m.value += (*continuationHistory[3])[pc][to];
+ m.value += (*continuationHistory[5])[pc][to];
+
+ // bonus for checks
+ m.value += bool(pos.check_squares(pt) & to) * 16384;
+
+ // bonus for escaping from capture
+ m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 50000
+ : pt == ROOK && !(to & threatenedByMinor) ? 25000
+ : !(to & threatenedByPawn) ? 15000
+ : 0)
+ : 0;
+
+ // malus for putting piece en prise
+ m.value -= !(threatenedPieces & from)
+ ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000
+ + bool(to & threatenedByMinor) * 10000
+ + bool(to & threatenedByPawn) * 20000
+ : pt == ROOK ? bool(to & threatenedByMinor) * 25000
+ + bool(to & threatenedByPawn) * 10000
+ : pt != PAWN ? bool(to & threatenedByPawn) * 15000
+ : 0)
+ : 0;
+ }
+
+ else // Type == EVASIONS
+ {
+ if (pos.capture_stage(m))
+ m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m)))
+ + (1 << 28);
+ else
+ m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
+ + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
+ + (*pawnHistory)[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)];
+ }
}
+// Returns the next move satisfying a predicate function.
+// It never returns the TT move.
+template<MovePicker::PickType T, typename Pred>
+Move MovePicker::select(Pred filter) {
-/// 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().
-
-void MovePicker::score_captures() {
- // Winning and equal captures in the main search are ordered by MVV/LVA.
- // Suprisingly, this appears to perform slightly better than SEE based
- // move ordering. The reason is probably that in a position with a winning
- // capture, capturing a more valuable (but sufficiently defended) piece
- // first usually doesn't hurt. The opponent will have to recapture, and
- // the hanging piece will still be hanging (except in the unusual cases
- // where it is possible to recapture with the hanging piece). Exchanging
- // big pieces before capturing a hanging piece probably helps to reduce
- // the subtree size.
- // In main search we want to push captures with negative SEE values to
- // badCaptures[] array, but instead of doing it now we delay till when
- // the move has been picked up in pick_move_from_list(), this way we save
- // some SEE calls in case we get a cutoff (idea from Pablo Vazquez).
- Move m;
-
- // Use MVV/LVA ordering
- for (MoveStack* cur = moves; cur != lastMove; cur++)
- {
- m = cur->move;
- cur->score = piece_value_midgame(pos.piece_on(move_to(m)))
- - pos.type_of_piece_on(move_from(m));
-
- if (move_is_promotion(m))
- cur->score += QueenValueMidgame;
- }
-}
-
-void MovePicker::score_noncaptures() {
+ while (cur < endMoves)
+ {
+ if constexpr (T == Best)
+ std::swap(*cur, *std::max_element(cur, endMoves));
- Move m;
- Square from;
+ if (*cur != ttMove && filter())
+ return *cur++;
- 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));
- }
+ cur++;
+ }
+ return MOVE_NONE;
}
-void MovePicker::score_evasions() {
- // Try good captures ordered by MVV/LVA, then non-captures if
- // destination square is not under attack, ordered by history
- // value, and at the end bad-captures and non-captures with a
- // negative SEE. This last group is ordered by the SEE score.
- Move m;
- int seeScore;
-
- // Skip if we don't have at least two moves to order
- if (lastMove < moves + 2)
- return;
-
- for (MoveStack* cur = moves; cur != lastMove; cur++)
- {
- 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 = piece_value_midgame(pos.piece_on(move_to(m)))
- - pos.type_of_piece_on(move_from(m)) + History::MaxValue;
- else
- cur->score = H.value(pos.piece_on(move_from(m)), move_to(m));
- }
+// 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, picking the move with the highest score from a list of generated moves.
+Move MovePicker::next_move(bool skipQuiets) {
+
+top:
+ switch (stage)
+ {
+
+ case MAIN_TT :
+ case EVASION_TT :
+ case QSEARCH_TT :
+ case PROBCUT_TT :
+ ++stage;
+ return ttMove;
+
+ case CAPTURE_INIT :
+ case PROBCUT_INIT :
+ case QCAPTURE_INIT :
+ cur = endBadCaptures = moves;
+ endMoves = generate<CAPTURES>(pos, cur);
+
+ score<CAPTURES>();
+ partial_insertion_sort(cur, endMoves, std::numeric_limits<int>::min());
+ ++stage;
+ goto top;
+
+ case GOOD_CAPTURE :
+ if (select<Next>([&]() {
+ // Move losing capture to endBadCaptures to be tried later
+ return pos.see_ge(*cur, Value(-cur->value)) ? true
+ : (*endBadCaptures++ = *cur, false);
+ }))
+ return *(cur - 1);
+
+ // Prepare the pointers to loop over the refutations array
+ cur = std::begin(refutations);
+ endMoves = std::end(refutations);
+
+ // If the countermove is the same as a killer, skip it
+ if (refutations[0].move == refutations[2].move
+ || refutations[1].move == refutations[2].move)
+ --endMoves;
+
+ ++stage;
+ [[fallthrough]];
+
+ case REFUTATION :
+ if (select<Next>([&]() {
+ return *cur != MOVE_NONE && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur);
+ }))
+ return *(cur - 1);
+ ++stage;
+ [[fallthrough]];
+
+ case QUIET_INIT :
+ if (!skipQuiets)
+ {
+ cur = endBadCaptures;
+ endMoves = generate<QUIETS>(pos, cur);
+
+ score<QUIETS>();
+ partial_insertion_sort(cur, endMoves, -1960 - 3130 * depth);
+ }
+
+ ++stage;
+ [[fallthrough]];
+
+ case QUIET :
+ if (!skipQuiets && select<Next>([&]() {
+ return *cur != refutations[0].move && *cur != refutations[1].move
+ && *cur != refutations[2].move;
+ }))
+ return *(cur - 1);
+
+ // Prepare the pointers to loop over the bad captures
+ cur = moves;
+ endMoves = endBadCaptures;
+
+ ++stage;
+ [[fallthrough]];
+
+ case BAD_CAPTURE :
+ return select<Next>([]() { return true; });
+
+ case EVASION_INIT :
+ cur = moves;
+ endMoves = generate<EVASIONS>(pos, cur);
+
+ score<EVASIONS>();
+ ++stage;
+ [[fallthrough]];
+
+ case EVASION :
+ return select<Best>([]() { return true; });
+
+ case PROBCUT :
+ return select<Next>([&]() { return pos.see_ge(*cur, threshold); });
+
+ case QCAPTURE :
+ if (select<Next>([]() { return true; }))
+ return *(cur - 1);
+
+ // If we did not find any move and we do not try checks, we have finished
+ if (depth != DEPTH_QS_CHECKS)
+ return MOVE_NONE;
+
+ ++stage;
+ [[fallthrough]];
+
+ case QCHECK_INIT :
+ cur = moves;
+ endMoves = generate<QUIET_CHECKS>(pos, cur);
+
+ ++stage;
+ [[fallthrough]];
+
+ case QCHECK :
+ return select<Next>([]() { return true; });
+ }
+
+ assert(false);
+ return MOVE_NONE; // Silence warning
}
-/// 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
-/// 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 move;
-
- while (true)
- {
- while (curMove == lastMove)
- go_next_phase();
-
- switch (phase) {
-
- case PH_TT_MOVE:
- curMove++;
- return ttMove;
- break;
-
- case PH_GOOD_CAPTURES:
- move = pick_best(curMove++, lastMove).move;
- if (move != ttMove)
- {
- assert(captureThreshold <= 0); // Otherwise we must use see instead of see_sign
-
- // Check for a non negative SEE now
- int seeValue = pos.see_sign(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.
- (--badCaptures)->move = move;
- badCaptures->score = seeValue;
- }
- break;
-
- case PH_GOOD_PROBCUT:
- move = pick_best(curMove++, lastMove).move;
- if ( move != ttMove
- && pos.see(move) > captureThreshold)
- return move;
- break;
-
- case PH_KILLERS:
- move = (curMove++)->move;
- if ( move != MOVE_NONE
- && pos.move_is_pl(move)
- && move != ttMove
- && !pos.move_is_capture(move))
- return move;
- break;
-
- case PH_NONCAPTURES_1:
- case PH_NONCAPTURES_2:
- move = (curMove++)->move;
- if ( move != ttMove
- && move != killers[0].move
- && move != killers[1].move)
- return move;
- break;
-
- case PH_BAD_CAPTURES:
- move = pick_best(curMove++, lastMove).move;
- return move;
-
- case PH_EVASIONS:
- case PH_QCAPTURES:
- move = pick_best(curMove++, lastMove).move;
- if (move != ttMove)
- return move;
- break;
-
- case PH_QRECAPTURES:
- move = (curMove++)->move;
- if (move_to(move) == recaptureSquare)
- return move;
- break;
-
- case PH_QCHECKS:
- move = (curMove++)->move;
- if (move != ttMove)
- return move;
- break;
-
- case PH_STOP:
- return MOVE_NONE;
-
- default:
- assert(false);
- break;
- }
- }
-}
+} // namespace Stockfish