X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=6586144dfabceac40f269567244d192c98702d04;hp=8adbfd92ca240b418c09598f67847c2097a745d8;hb=b84af67f4c88f3e3f7b61bf2035475f79fb3e62e;hpb=b1cf1acb93532248fb10c2ca983d80389d5aeb84 diff --git a/src/search.cpp b/src/search.cpp index 8adbfd92..6586144d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -21,16 +21,15 @@ #include #include #include -#include #include #include #include "book.h" #include "evaluate.h" #include "history.h" -#include "misc.h" #include "movegen.h" #include "movepick.h" +#include "notation.h" #include "search.h" #include "timeman.h" #include "thread.h" @@ -43,11 +42,13 @@ namespace Search { LimitsType Limits; std::vector RootMoves; Position RootPosition; + Time SearchTime; } using std::string; using std::cout; using std::endl; +using Eval::evaluate; using namespace Search; namespace { @@ -66,7 +67,7 @@ namespace { const Depth RazorDepth = 4 * ONE_PLY; // Dynamic razoring margin based on depth - inline Value razor_margin(Depth d) { return Value(0x200 + 0x10 * int(d)); } + inline Value razor_margin(Depth d) { return Value(512 + 16 * int(d)); } // Maximum depth for use of dynamic threat detection when null move fails low const Depth ThreatDepth = 5 * ONE_PLY; @@ -76,13 +77,13 @@ namespace { // At Non-PV nodes we do an internal iterative deepening search // when the static evaluation is bigger then beta - IIDMargin. - const Value IIDMargin = Value(0x100); + const Value IIDMargin = Value(256); // Minimum depth for use of singular extension const Depth SingularExtensionDepth[] = { 8 * ONE_PLY, 6 * ONE_PLY }; // Futility margin for quiescence search - const Value FutilityMarginQS = Value(0x80); + const Value FutilityMarginQS = Value(128); // Futility lookup tables (initialized at startup) and their access functions Value FutilityMargins[16][64]; // [depth][moveNumber] @@ -130,57 +131,35 @@ namespace { Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth); void id_loop(Position& pos); - bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta, Value *bValue); + bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta); bool connected_moves(const Position& pos, Move m1, Move m2); Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply); - bool can_return_tt(const TTEntry* tte, Depth depth, Value beta, int ply); + bool can_return_tt(const TTEntry* tte, Depth depth, Value ttValue, Value beta); bool connected_threat(const Position& pos, Move m, Move threat); - Value refine_eval(const TTEntry* tte, Value defaultEval, int ply); + Value refine_eval(const TTEntry* tte, Value ttValue, Value defaultEval); Move do_skill_level(); - int elapsed_time(bool reset = false); - string score_to_uci(Value v, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE); - void pv_info_to_log(Position& pos, int depth, Value score, int time, Move pv[]); - void pv_info_to_uci(const Position& pos, int depth, Value alpha, Value beta); - - // MovePickerExt class template extends MovePicker and allows to choose at - // compile time the proper moves source according to the type of node. In the - // default case we simply create and use a standard MovePicker object. - template struct MovePickerExt : public MovePicker { - - MovePickerExt(const Position& p, Move ttm, Depth d, const History& h, Stack* ss, Value b) - : MovePicker(p, ttm, d, h, ss, b) {} - }; - - // In case of a SpNode we use split point's shared MovePicker object as moves source - template<> struct MovePickerExt : public MovePicker { - - MovePickerExt(const Position& p, Move ttm, Depth d, const History& h, Stack* ss, Value b) - : MovePicker(p, ttm, d, h, ss, b), mp(ss->sp->mp) {} - - Move next_move() { return mp->next_move(); } - MovePicker* mp; - }; + string uci_pv(const Position& pos, int depth, Value alpha, Value beta); // is_dangerous() checks whether a move belongs to some classes of known // 'dangerous' moves so that we avoid to prune it. FORCE_INLINE bool is_dangerous(const Position& pos, Move m, bool captureOrPromotion) { - // Test for a pawn pushed to 7th or a passed pawn move - if (type_of(pos.piece_moved(m)) == PAWN) - { - Color c = pos.side_to_move(); - if ( relative_rank(c, to_sq(m)) == RANK_7 - || pos.pawn_is_passed(c, to_sq(m))) - return true; - } + // Castle move? + if (type_of(m) == CASTLE) + return true; + + // Passed pawn move? + if ( type_of(pos.piece_moved(m)) == PAWN + && pos.pawn_is_passed(pos.side_to_move(), to_sq(m))) + return true; - // Test for a capture that triggers a pawn endgame - if ( captureOrPromotion - && type_of(pos.piece_on(to_sq(m))) != PAWN + // Entering a pawn endgame? + if ( captureOrPromotion + && type_of(pos.piece_on(to_sq(m))) != PAWN + && type_of(m) == NORMAL && ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) - - PieceValueMidgame[pos.piece_on(to_sq(m))] == VALUE_ZERO) - && !is_special(m)) + - PieceValue[Mg][pos.piece_on(to_sq(m))] == VALUE_ZERO)) return true; return false; @@ -219,24 +198,23 @@ void Search::init() { /// Search::perft() is our utility to verify move generation. All the leaf nodes /// up to the given depth are generated and counted and the sum returned. -int64_t Search::perft(Position& pos, Depth depth) { - - StateInfo st; - int64_t cnt = 0; +size_t Search::perft(Position& pos, Depth depth) { - MoveList ml(pos); - - // At the last ply just return the number of moves (leaf nodes) + // At the last ply just return the number of legal moves (leaf nodes) if (depth == ONE_PLY) - return ml.size(); + return MoveList(pos).size(); + StateInfo st; + size_t cnt = 0; CheckInfo ci(pos); - for ( ; !ml.end(); ++ml) + + for (MoveList ml(pos); !ml.end(); ++ml) { pos.do_move(ml.move(), st, ci, pos.move_gives_check(ml.move(), ci)); cnt += perft(pos, depth - ONE_PLY); pos.undo_move(ml.move()); } + return cnt; } @@ -251,8 +229,8 @@ void Search::think() { Position& pos = RootPosition; Chess960 = pos.is_chess960(); - elapsed_time(true); - TimeMgr.init(Limits, pos.startpos_ply_counter()); + Eval::RootColor = pos.side_to_move(); + TimeMgr.init(Limits, pos.startpos_ply_counter(), pos.side_to_move()); TT.new_search(); H.clear(); @@ -265,28 +243,17 @@ void Search::think() { goto finalize; } - if (Options["OwnBook"]) + if (Options["OwnBook"] && !Limits.infinite) { Move bookMove = book.probe(pos, Options["Book File"], Options["Best Book Move"]); - if (bookMove && count(RootMoves.begin(), RootMoves.end(), bookMove)) + if (bookMove && std::count(RootMoves.begin(), RootMoves.end(), bookMove)) { - std::swap(RootMoves[0], *find(RootMoves.begin(), RootMoves.end(), bookMove)); + std::swap(RootMoves[0], *std::find(RootMoves.begin(), RootMoves.end(), bookMove)); goto finalize; } } - // Read UCI options: GUI could change UCI parameters during the game - read_evaluation_uci_options(pos.side_to_move()); - Threads.read_uci_options(); - - TT.set_size(Options["Hash"]); - if (Options["Clear Hash"]) - { - Options["Clear Hash"] = false; - TT.clear(); - } - UCIMultiPV = Options["MultiPV"]; SkillLevel = Options["Skill Level"]; @@ -301,17 +268,13 @@ void Search::think() { log << "\nSearching: " << pos.to_fen() << "\ninfinite: " << Limits.infinite << " ponder: " << Limits.ponder - << " time: " << Limits.time - << " increment: " << Limits.increment - << " moves to go: " << Limits.movesToGo + << " time: " << Limits.time[pos.side_to_move()] + << " increment: " << Limits.inc[pos.side_to_move()] + << " moves to go: " << Limits.movestogo << endl; } - for (int i = 0; i < Threads.size(); i++) - { - Threads[i].maxPly = 0; - Threads[i].wake_up(); - } + Threads.wake_up(); // Set best timer interval to avoid lagging under time pressure. Timer is // used to check for remaining available thinking time. @@ -323,13 +286,12 @@ void Search::think() { // We're ready to start searching. Call the iterative deepening loop function id_loop(pos); - // Stop timer and send all the slaves to sleep, if not already sleeping - Threads.set_timer(0); - Threads.set_size(1); + Threads.set_timer(0); // Stop timer + Threads.sleep(); if (Options["Use Search Log"]) { - int e = elapsed_time(); + int e = SearchTime.elapsed(); Log log(Options["Search Log Filename"]); log << "Nodes: " << pos.nodes_searched() @@ -348,7 +310,7 @@ finalize: // but if we are pondering or in infinite search, we shouldn't print the best // move before we are told to do so. if (!Signals.stop && (Limits.ponder || Limits.infinite)) - Threads[pos.thread()].wait_for_stop_or_ponderhit(); + pos.this_thread()->wait_for_stop_or_ponderhit(); // Best move could be MOVE_NONE when searching on a stalemate position cout << "bestmove " << move_to_uci(RootMoves[0].pv[0], Chess960) @@ -376,7 +338,7 @@ namespace { ss->currentMove = MOVE_NULL; // Hack to skip update gains // Iterative deepening loop until requested to stop or target depth reached - while (!Signals.stop && ++depth <= MAX_PLY && (!Limits.maxDepth || depth <= Limits.maxDepth)) + while (!Signals.stop && ++depth <= MAX_PLY && (!Limits.depth || depth <= Limits.depth)) { // Save last iteration's scores before first PV line is searched and all // the move scores but the (new) PV are set to -VALUE_INFINITE. @@ -437,8 +399,8 @@ namespace { // Send full PV info to GUI if we are going to leave the loop or // if we have a fail high/low and we are deep in the search. - if ((bestValue > alpha && bestValue < beta) || elapsed_time() > 2000) - pv_info_to_uci(pos, depth, alpha, beta); + if ((bestValue > alpha && bestValue < beta) || SearchTime.elapsed() > 2000) + cout << uci_pv(pos, depth, alpha, beta) << endl; // In case of failing high/low increase aspiration window and // research, otherwise exit the fail high/low loop. @@ -468,7 +430,11 @@ namespace { skillBest = do_skill_level(); if (!Signals.stop && Options["Use Search Log"]) - pv_info_to_log(pos, depth, bestValue, elapsed_time(), &RootMoves[0].pv[0]); + { + Log log(Options["Search Log Filename"]); + log << pretty_pv(pos, depth, bestValue, SearchTime.elapsed(), &RootMoves[0].pv[0]) + << endl; + } // Filter out startup noise when monitoring best move stability if (depth > 2 && BestMoveChanges) @@ -486,14 +452,14 @@ namespace { // Stop search if most of available time is already consumed. We // probably don't have enough time to search the first move at the // next iteration anyway. - if (elapsed_time() > (TimeMgr.available_time() * 62) / 100) + if (SearchTime.elapsed() > (TimeMgr.available_time() * 62) / 100) stop = true; // Stop search early if one move seems to be much better than others if ( depth >= 12 && !stop && ( (bestMoveNeverChanged && pos.captured_piece_type()) - || elapsed_time() > (TimeMgr.available_time() * 40) / 100)) + || SearchTime.elapsed() > (TimeMgr.available_time() * 40) / 100)) { Value rBeta = bestValue - EasyMoveMargin; (ss+1)->excludedMove = RootMoves[0].pv[0]; @@ -524,7 +490,7 @@ namespace { if (skillBest == MOVE_NONE) // Still unassigned ? skillBest = do_skill_level(); - std::swap(RootMoves[0], *find(RootMoves.begin(), RootMoves.end(), skillBest)); + std::swap(RootMoves[0], *std::find(RootMoves.begin(), RootMoves.end(), skillBest)); } } @@ -546,21 +512,20 @@ namespace { assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert((alpha == beta - 1) || PvNode); assert(depth > DEPTH_ZERO); - assert(pos.thread() >= 0 && pos.thread() < Threads.size()); - Move movesSearched[MAX_MOVES]; + Move movesSearched[64]; StateInfo st; const TTEntry *tte; Key posKey; - Move ttMove, move, excludedMove, threatMove; + Move ttMove, move, excludedMove, bestMove, threatMove; Depth ext, newDepth; Bound bt; - Value bestValue, value, oldAlpha; + Value bestValue, value, oldAlpha, ttValue; Value refinedValue, nullValue, futilityBase, futilityValue; bool isPvMove, inCheck, singularExtensionNode, givesCheck; bool captureOrPromotion, dangerous, doFullDepthSearch; int moveCount = 0, playedMoveCount = 0; - Thread& thread = Threads[pos.thread()]; + Thread* thisThread = pos.this_thread(); SplitPoint* sp = NULL; refinedValue = bestValue = value = -VALUE_INFINITE; @@ -569,15 +534,17 @@ namespace { ss->ply = (ss-1)->ply + 1; // Used to send selDepth info to GUI - if (PvNode && thread.maxPly < ss->ply) - thread.maxPly = ss->ply; + if (PvNode && thisThread->maxPly < ss->ply) + thisThread->maxPly = ss->ply; // Step 1. Initialize node if (SpNode) { tte = NULL; ttMove = excludedMove = MOVE_NONE; + ttValue = VALUE_ZERO; sp = ss->sp; + bestMove = sp->bestMove; threatMove = sp->threatMove; bestValue = sp->bestValue; moveCount = sp->moveCount; // Lock must be held here @@ -588,7 +555,7 @@ namespace { } else { - ss->currentMove = ss->bestMove = threatMove = (ss+1)->excludedMove = MOVE_NONE; + ss->currentMove = threatMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; (ss+1)->skipNullMove = false; (ss+1)->reduction = DEPTH_ZERO; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; @@ -596,7 +563,7 @@ namespace { // Step 2. Check for aborted search and immediate draw // Enforce node limit here. FIXME: This only works with 1 search thread. - if (Limits.maxNodes && pos.nodes_searched() >= Limits.maxNodes) + if (Limits.nodes && pos.nodes_searched() >= Limits.nodes) Signals.stop = true; if (( Signals.stop @@ -625,27 +592,27 @@ namespace { posKey = excludedMove ? pos.exclusion_key() : pos.key(); tte = TT.probe(posKey); ttMove = RootNode ? RootMoves[PVIdx].pv[0] : tte ? tte->move() : MOVE_NONE; + ttValue = tte ? value_from_tt(tte->value(), ss->ply) : VALUE_ZERO; // At PV nodes we check for exact scores, while at non-PV nodes we check for // a fail high/low. Biggest advantage at probing at PV nodes is to have a // smooth experience in analysis mode. We don't probe at Root nodes otherwise // we should also update RootMoveList to avoid bogus output. if (!RootNode && tte && (PvNode ? tte->depth() >= depth && tte->type() == BOUND_EXACT - : can_return_tt(tte, depth, beta, ss->ply))) + : can_return_tt(tte, depth, ttValue, beta))) { TT.refresh(tte); - ss->bestMove = move = ttMove; // Can be MOVE_NONE - value = value_from_tt(tte->value(), ss->ply); + ss->currentMove = ttMove; // Can be MOVE_NONE - if ( value >= beta - && move - && !pos.is_capture_or_promotion(move) - && move != ss->killers[0]) + if ( ttValue >= beta + && ttMove + && !pos.is_capture_or_promotion(ttMove) + && ttMove != ss->killers[0]) { ss->killers[1] = ss->killers[0]; - ss->killers[0] = move; + ss->killers[0] = ttMove; } - return value; + return ttValue; } // Step 5. Evaluate the position statically and update parent's gain statistics @@ -657,7 +624,7 @@ namespace { ss->eval = tte->static_value(); ss->evalMargin = tte->static_value_margin(); - refinedValue = refine_eval(tte, ss->eval, ss->ply); + refinedValue = refine_eval(tte, ttValue, ss->eval); } else { @@ -667,11 +634,11 @@ namespace { // Update gain for the parent non-capture move given the static position // evaluation before and after the move. - if ( (move = (ss-1)->currentMove) != MOVE_NULL - && (ss-1)->eval != VALUE_NONE - && ss->eval != VALUE_NONE + if ( (move = (ss-1)->currentMove) != MOVE_NULL + && (ss-1)->eval != VALUE_NONE + && ss->eval != VALUE_NONE && !pos.captured_piece_type() - && !is_special(move)) + && type_of(move) == NORMAL) { Square to = to_sq(move); H.update_gain(pos.piece_on(to), to, -(ss-1)->eval - ss->eval); @@ -684,7 +651,7 @@ namespace { && refinedValue + razor_margin(depth) < beta && ttMove == MOVE_NONE && abs(beta) < VALUE_MATE_IN_MAX_PLY - && !pos.has_pawn_on_7th(pos.side_to_move())) + && !pos.pawn_on_7th(pos.side_to_move())) { Value rbeta = beta - razor_margin(depth); Value v = qsearch(pos, ss, rbeta-1, rbeta, DEPTH_ZERO); @@ -718,16 +685,16 @@ namespace { ss->currentMove = MOVE_NULL; // Null move dynamic reduction based on depth - int R = 3 + (depth >= 5 * ONE_PLY ? depth / 8 : 0); + Depth R = 3 * ONE_PLY + depth / 4; // Null move dynamic reduction based on value - if (refinedValue - PawnValueMidgame > beta) - R++; + if (refinedValue - PawnValueMg > beta) + R += ONE_PLY; pos.do_null_move(st); (ss+1)->skipNullMove = true; - nullValue = depth-R*ONE_PLY < ONE_PLY ? -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) - : - search(pos, ss+1, -beta, -alpha, depth-R*ONE_PLY); + nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) + : - search(pos, ss+1, -beta, -alpha, depth-R); (ss+1)->skipNullMove = false; pos.do_null_move(st); @@ -742,7 +709,7 @@ namespace { // Do verification search at high depths ss->skipNullMove = true; - Value v = search(pos, ss, alpha, beta, depth-R*ONE_PLY); + Value v = search(pos, ss, alpha, beta, depth-R); ss->skipNullMove = false; if (v >= beta) @@ -756,7 +723,7 @@ namespace { // move which was reduced. If a connection is found, return a fail // low score (which will cause the reduced move to fail high in the // parent node, which will trigger a re-search with full depth). - threatMove = (ss+1)->bestMove; + threatMove = (ss+1)->currentMove; if ( depth < ThreatDepth && (ss-1)->reduction @@ -782,11 +749,12 @@ namespace { assert(rdepth >= ONE_PLY); assert((ss-1)->currentMove != MOVE_NONE); + assert((ss-1)->currentMove != MOVE_NULL); MovePicker mp(pos, ttMove, H, pos.captured_piece_type()); CheckInfo ci(pos); - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != MOVE_NONE) if (pos.pl_move_is_legal(move, ci.pinned)) { ss->currentMove = move; @@ -815,23 +783,22 @@ namespace { split_point_start: // At split points actual search starts from here - MovePickerExt mp(pos, ttMove, depth, H, ss, PvNode ? -VALUE_INFINITE : beta); + MovePicker mp(pos, ttMove, depth, H, ss, PvNode ? -VALUE_INFINITE : beta); CheckInfo ci(pos); - ss->bestMove = MOVE_NONE; futilityBase = ss->eval + ss->evalMargin; singularExtensionNode = !RootNode && !SpNode - && depth >= SingularExtensionDepth[PvNode] - && ttMove != MOVE_NONE + && depth >= SingularExtensionDepth[PvNode] + && ttMove != MOVE_NONE && !excludedMove // Recursive singular search is not allowed && (tte->type() & BOUND_LOWER) - && tte->depth() >= depth - 3 * ONE_PLY; + && tte->depth() >= depth - 3 * ONE_PLY; // Step 11. Loop through moves // Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs - while ( bestValue < beta - && (move = mp.next_move()) != MOVE_NONE - && !thread.cutoff_occurred() + while ( bestValue < beta + && (move = mp.next_move()) != MOVE_NONE + && !thisThread->cutoff_occurred() && !Signals.stop) { assert(is_ok(move)); @@ -842,7 +809,7 @@ split_point_start: // At split points actual search starts from here // At root obey the "searchmoves" option and skip moves not listed in Root // Move List, as a consequence any illegal move is also skipped. In MultiPV // mode we also skip PV moves which have been already searched. - if (RootNode && !count(RootMoves.begin() + PVIdx, RootMoves.end(), move)) + if (RootNode && !std::count(RootMoves.begin() + PVIdx, RootMoves.end(), move)) continue; // At PV and SpNode nodes we want all moves to be legal since the beginning @@ -861,7 +828,7 @@ split_point_start: // At split points actual search starts from here { Signals.firstRootMove = (moveCount == 1); - if (pos.thread() == 0 && elapsed_time() > 2000) + if (thisThread == Threads.main_thread() && SearchTime.elapsed() > 2000) cout << "info depth " << depth / ONE_PLY << " currmove " << move_to_uci(move, Chess960) << " currmovenumber " << moveCount + PVIdx << endl; @@ -878,32 +845,28 @@ split_point_start: // At split points actual search starts from here ext = ONE_PLY; else if (givesCheck && pos.see_sign(move) >= 0) - ext = PvNode ? ONE_PLY : ONE_PLY / 2; + ext = ONE_PLY / 2; // Singular extension search. If all moves but one fail low on a search of // (alpha-s, beta-s), and just one fails high on (alpha, beta), then that move // is singular and should be extended. To verify this we do a reduced search // on all the other moves but the ttMove, if result is lower than ttValue minus // a margin then we extend ttMove. - if ( singularExtensionNode + if ( singularExtensionNode && !ext - && move == ttMove - && pos.pl_move_is_legal(move, ci.pinned)) + && move == ttMove + && pos.pl_move_is_legal(move, ci.pinned) + && abs(ttValue) < VALUE_KNOWN_WIN) { - Value ttValue = value_from_tt(tte->value(), ss->ply); - - if (abs(ttValue) < VALUE_KNOWN_WIN) - { - Value rBeta = ttValue - int(depth); - ss->excludedMove = move; - ss->skipNullMove = true; - value = search(pos, ss, rBeta - 1, rBeta, depth / 2); - ss->skipNullMove = false; - ss->excludedMove = MOVE_NONE; - ss->bestMove = MOVE_NONE; - if (value < rBeta) - ext = ONE_PLY; - } + Value rBeta = ttValue - int(depth); + ss->excludedMove = move; + ss->skipNullMove = true; + value = search(pos, ss, rBeta - 1, rBeta, depth / 2); + ss->skipNullMove = false; + ss->excludedMove = MOVE_NONE; + + if (value < rBeta) + ext = ONE_PLY; } // Update current move (this must be done after singular extension search) @@ -915,7 +878,6 @@ split_point_start: // At split points actual search starts from here && !inCheck && !dangerous && move != ttMove - && !is_castle(move) && (bestValue > VALUE_MATED_IN_MAX_PLY || bestValue == -VALUE_INFINITE)) { // Move count based pruning @@ -962,7 +924,7 @@ split_point_start: // At split points actual search starts from here } ss->currentMove = move; - if (!SpNode && !captureOrPromotion) + if (!SpNode && !captureOrPromotion && playedMoveCount < 64) movesSearched[playedMoveCount++] = move; // Step 14. Make the move @@ -970,11 +932,10 @@ split_point_start: // At split points actual search starts from here // Step 15. Reduced depth search (LMR). If the move fails high will be // re-searched at full depth. - if ( depth > 3 * ONE_PLY + if ( depth > 3 * ONE_PLY && !isPvMove && !captureOrPromotion && !dangerous - && !is_castle(move) && ss->killers[0] != move && ss->killers[1] != move) { @@ -1024,7 +985,7 @@ split_point_start: // At split points actual search starts from here // be trusted, and we don't update the best move and/or PV. if (RootNode && !Signals.stop) { - RootMove& rm = *find(RootMoves.begin(), RootMoves.end(), move); + RootMove& rm = *std::find(RootMoves.begin(), RootMoves.end(), move); // PV move or new best move ? if (isPvMove || value > alpha) @@ -1049,17 +1010,17 @@ split_point_start: // At split points actual search starts from here if (value > bestValue) { bestValue = value; - ss->bestMove = move; + bestMove = move; if ( PvNode && value > alpha && value < beta) // We want always alpha < beta alpha = value; - if (SpNode && !thread.cutoff_occurred()) + if (SpNode && !thisThread->cutoff_occurred()) { sp->bestValue = value; - sp->ss->bestMove = move; + sp->bestMove = move; sp->alpha = alpha; if (value >= beta) @@ -1069,13 +1030,13 @@ split_point_start: // At split points actual search starts from here // Step 19. Check for split if ( !SpNode - && depth >= Threads.min_split_depth() - && bestValue < beta - && Threads.available_slave_exists(pos.thread()) + && depth >= Threads.min_split_depth() + && bestValue < beta + && Threads.available_slave_exists(thisThread) && !Signals.stop - && !thread.cutoff_occurred()) - bestValue = Threads.split(pos, ss, alpha, beta, bestValue, depth, - threatMove, moveCount, &mp, NT); + && !thisThread->cutoff_occurred()) + bestValue = Threads.split(pos, ss, alpha, beta, bestValue, &bestMove, + depth, threatMove, moveCount, &mp, NT); } // Step 20. Check for mate and stalemate @@ -1092,14 +1053,14 @@ split_point_start: // At split points actual search starts from here { assert(!playedMoveCount); - bestValue = alpha; + bestValue = oldAlpha; } // Step 21. Update tables // Update transposition table entry, killers and history - if (!SpNode && !Signals.stop && !thread.cutoff_occurred()) + if (!SpNode && !Signals.stop && !thisThread->cutoff_occurred()) { - move = bestValue <= oldAlpha ? MOVE_NONE : ss->bestMove; + move = bestValue <= oldAlpha ? MOVE_NONE : bestMove; bt = bestValue <= oldAlpha ? BOUND_UPPER : bestValue >= beta ? BOUND_LOWER : BOUND_EXACT; @@ -1148,18 +1109,17 @@ split_point_start: // At split points actual search starts from here assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert((alpha == beta - 1) || PvNode); assert(depth <= DEPTH_ZERO); - assert(pos.thread() >= 0 && pos.thread() < Threads.size()); StateInfo st; - Move ttMove, move; - Value bestValue, value, evalMargin, futilityValue, futilityBase; + Move ttMove, move, bestMove; + Value ttValue, bestValue, value, evalMargin, futilityValue, futilityBase; bool inCheck, enoughMaterial, givesCheck, evasionPrunable; const TTEntry* tte; Depth ttDepth; Bound bt; Value oldAlpha = alpha; - ss->bestMove = ss->currentMove = MOVE_NONE; + ss->currentMove = bestMove = MOVE_NONE; ss->ply = (ss-1)->ply + 1; // Check for an instant draw or maximum ply reached @@ -1176,11 +1136,12 @@ split_point_start: // At split points actual search starts from here // pruning, but only for move ordering. tte = TT.probe(pos.key()); ttMove = (tte ? tte->move() : MOVE_NONE); + ttValue = tte ? value_from_tt(tte->value(),ss->ply) : VALUE_ZERO; - if (!PvNode && tte && can_return_tt(tte, ttDepth, beta, ss->ply)) + if (!PvNode && tte && can_return_tt(tte, ttDepth, ttValue, beta)) { - ss->bestMove = ttMove; // Can be MOVE_NONE - return value_from_tt(tte->value(), ss->ply); + ss->currentMove = ttMove; // Can be MOVE_NONE + return ttValue; } // Evaluate the position statically @@ -1215,7 +1176,7 @@ split_point_start: // At split points actual search starts from here alpha = bestValue; futilityBase = ss->eval + evalMargin + FutilityMarginQS; - enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame; + enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMg; } // Initialize a MovePicker object for the current position, and prepare @@ -1227,7 +1188,7 @@ split_point_start: // At split points actual search starts from here // Loop through the moves until no moves remain or a beta cutoff occurs while ( bestValue < beta - && (move = mp.next_move()) != MOVE_NONE) + && (move = mp.next_move()) != MOVE_NONE) { assert(is_ok(move)); @@ -1239,12 +1200,12 @@ split_point_start: // At split points actual search starts from here && !givesCheck && move != ttMove && enoughMaterial - && !is_promotion(move) + && type_of(move) != PROMOTION && !pos.is_passed_pawn_push(move)) { futilityValue = futilityBase - + PieceValueEndgame[pos.piece_on(to_sq(move))] - + (is_enpassant(move) ? PawnValueEndgame : VALUE_ZERO); + + PieceValue[Eg][pos.piece_on(to_sq(move))] + + (type_of(move) == ENPASSANT ? PawnValueEg : VALUE_ZERO); if (futilityValue < beta) { @@ -1263,8 +1224,8 @@ split_point_start: // At split points actual search starts from here // Detect non-capture evasions that are candidate to be pruned evasionPrunable = !PvNode - && inCheck - && bestValue > VALUE_MATED_IN_MAX_PLY + && inCheck + && bestValue > VALUE_MATED_IN_MAX_PLY && !pos.is_capture(move) && !pos.can_castle(pos.side_to_move()); @@ -1272,7 +1233,7 @@ split_point_start: // At split points actual search starts from here if ( !PvNode && (!inCheck || evasionPrunable) && move != ttMove - && !is_promotion(move) + && type_of(move) != PROMOTION && pos.see_sign(move) < 0) continue; @@ -1282,8 +1243,8 @@ split_point_start: // At split points actual search starts from here && givesCheck && move != ttMove && !pos.is_capture_or_promotion(move) - && ss->eval + PawnValueMidgame / 4 < beta - && !check_is_dangerous(pos, move, futilityBase, beta, &bestValue)) + && ss->eval + PawnValueMg / 4 < beta + && !check_is_dangerous(pos, move, futilityBase, beta)) continue; // Check for legality only before to do the move @@ -1303,7 +1264,7 @@ split_point_start: // At split points actual search starts from here if (value > bestValue) { bestValue = value; - ss->bestMove = move; + bestMove = move; if ( PvNode && value > alpha @@ -1318,7 +1279,7 @@ split_point_start: // At split points actual search starts from here return mated_in(ss->ply); // Plies to mate from the root // Update transposition table - move = bestValue <= oldAlpha ? MOVE_NONE : ss->bestMove; + move = bestValue <= oldAlpha ? MOVE_NONE : bestMove; bt = bestValue <= oldAlpha ? BOUND_UPPER : bestValue >= beta ? BOUND_LOWER : BOUND_EXACT; @@ -1334,29 +1295,28 @@ split_point_start: // At split points actual search starts from here // bestValue is updated only when returning false because in that case move // will be pruned. - bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta, Value *bestValue) + bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta) { Bitboard b, occ, oldAtt, newAtt, kingAtt; - Square from, to, ksq, victimSq; + Square from, to, ksq; Piece pc; Color them; - Value futilityValue, bv = *bestValue; from = from_sq(move); to = to_sq(move); them = ~pos.side_to_move(); ksq = pos.king_square(them); kingAtt = pos.attacks_from(ksq); - pc = pos.piece_on(from); + pc = pos.piece_moved(move); - occ = pos.occupied_squares() & ~(1ULL << from) & ~(1ULL << ksq); + occ = pos.pieces() ^ from ^ ksq; oldAtt = pos.attacks_from(pc, from, occ); newAtt = pos.attacks_from(pc, to, occ); // Rule 1. Checks which give opponent's king at most one escape square are dangerous b = kingAtt & ~pos.pieces(them) & ~newAtt & ~(1ULL << to); - if (!(b && (b & (b - 1)))) + if (!more_than_one(b)) return true; // Rule 2. Queen contact check is very dangerous @@ -1365,23 +1325,13 @@ split_point_start: // At split points actual search starts from here // Rule 3. Creating new double threats with checks b = pos.pieces(them) & newAtt & ~oldAtt & ~(1ULL << ksq); - while (b) { - victimSq = pop_1st_bit(&b); - futilityValue = futilityBase + PieceValueEndgame[pos.piece_on(victimSq)]; - // Note that here we generate illegal "double move"! - if ( futilityValue >= beta - && pos.see_sign(make_move(from, victimSq)) >= 0) + if (futilityBase + PieceValue[Eg][pos.piece_on(pop_lsb(&b))] >= beta) return true; - - if (futilityValue > bv) - bv = futilityValue; } - // Update bestValue only if check is not dangerous (because we will prune the move) - *bestValue = bv; return false; } @@ -1415,7 +1365,7 @@ split_point_start: // At split points actual search starts from here // Case 3: Moving through the vacated square p2 = pos.piece_on(f2); - if (piece_is_slider(p2) && (squares_between(f2, t2) & f1)) + if (piece_is_slider(p2) && (between_bb(f2, t2) & f1)) return true; // Case 4: The destination square for m2 is defended by the moving piece in m1 @@ -1425,13 +1375,11 @@ split_point_start: // At split points actual search starts from here // Case 5: Discovered check, checking piece is the piece moved in m1 ksq = pos.king_square(pos.side_to_move()); - if (piece_is_slider(p1) && (squares_between(t1, ksq) & f2)) - { - Bitboard occ = pos.occupied_squares(); - occ ^= f2; - if (pos.attacks_from(p1, t1, occ) & ksq) - return true; - } + if ( piece_is_slider(p1) + && (between_bb(t1, ksq) & f2) + && (pos.attacks_from(p1, t1, pos.pieces() ^ f2) & ksq)) + return true; + return false; } @@ -1492,7 +1440,7 @@ split_point_start: // At split points actual search starts from here // Case 2: If the threatened piece has value less than or equal to the // value of the threatening piece, don't prune moves which defend it. if ( pos.is_capture(threat) - && ( PieceValueMidgame[pos.piece_on(tfrom)] >= PieceValueMidgame[pos.piece_on(tto)] + && ( PieceValue[Mg][pos.piece_on(tfrom)] >= PieceValue[Mg][pos.piece_on(tto)] || type_of(pos.piece_on(tfrom)) == KING) && pos.move_attacks_square(m, tto)) return true; @@ -1500,7 +1448,7 @@ split_point_start: // At split points actual search starts from here // 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)) - && (squares_between(tfrom, tto) & mto) + && (between_bb(tfrom, tto) & mto) && pos.see_sign(m) >= 0) return true; @@ -1511,9 +1459,7 @@ split_point_start: // At split points actual search starts from here // can_return_tt() returns true if a transposition table score can be used to // cut-off at a given point in search. - bool can_return_tt(const TTEntry* tte, Depth depth, Value beta, int ply) { - - Value v = value_from_tt(tte->value(), ply); + bool can_return_tt(const TTEntry* tte, Depth depth, Value v, Value beta) { return ( tte->depth() >= depth || v >= std::max(VALUE_MATE_IN_MAX_PLY, beta) @@ -1527,12 +1473,10 @@ split_point_start: // At split points actual search starts from here // refine_eval() returns the transposition table score if possible, otherwise // falls back on static position evaluation. - Value refine_eval(const TTEntry* tte, Value defaultEval, int ply) { + Value refine_eval(const TTEntry* tte, Value v, Value defaultEval) { assert(tte); - Value v = value_from_tt(tte->value(), ply); - if ( ((tte->type() & BOUND_LOWER) && v >= defaultEval) || ((tte->type() & BOUND_UPPER) && v < defaultEval)) return v; @@ -1541,170 +1485,6 @@ split_point_start: // At split points actual search starts from here } - // current_search_time() returns the number of milliseconds which have passed - // since the beginning of the current search. - - int elapsed_time(bool reset) { - - static int searchStartTime; - - if (reset) - searchStartTime = system_time(); - - return system_time() - searchStartTime; - } - - - // score_to_uci() converts a value to a string suitable for use with the UCI - // protocol specifications: - // - // cp The score from the engine's point of view in centipawns. - // mate Mate in y moves, not plies. If the engine is getting mated - // use negative values for y. - - string score_to_uci(Value v, Value alpha, Value beta) { - - std::stringstream s; - - if (abs(v) < VALUE_MATE_IN_MAX_PLY) - s << "cp " << v * 100 / int(PawnValueMidgame); - else - s << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; - - s << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); - - return s.str(); - } - - - // pv_info_to_uci() sends search info to GUI. UCI protocol requires to send all - // the PV lines also if are still to be searched and so refer to the previous - // search score. - - void pv_info_to_uci(const Position& pos, int depth, Value alpha, Value beta) { - - int t = elapsed_time(); - int selDepth = 0; - - for (int i = 0; i < Threads.size(); i++) - if (Threads[i].maxPly > selDepth) - selDepth = Threads[i].maxPly; - - for (size_t i = 0; i < std::min(UCIMultiPV, RootMoves.size()); i++) - { - bool updated = (i <= PVIdx); - - if (depth == 1 && !updated) - continue; - - int d = (updated ? depth : depth - 1); - Value v = (updated ? RootMoves[i].score : RootMoves[i].prevScore); - std::stringstream s; - - for (int j = 0; RootMoves[i].pv[j] != MOVE_NONE; j++) - s << " " << move_to_uci(RootMoves[i].pv[j], Chess960); - - cout << "info depth " << d - << " seldepth " << selDepth - << " score " << (i == PVIdx ? score_to_uci(v, alpha, beta) : score_to_uci(v)) - << " nodes " << pos.nodes_searched() - << " nps " << (t > 0 ? pos.nodes_searched() * 1000 / t : 0) - << " time " << t - << " multipv " << i + 1 - << " pv" << s.str() << endl; - } - } - - - // pv_info_to_log() writes human-readable search information to the log file - // (which is created when the UCI parameter "Use Search Log" is "true"). It - // uses the two below helpers to pretty format time and score respectively. - - string time_to_string(int millisecs) { - - const int MSecMinute = 1000 * 60; - const int MSecHour = 1000 * 60 * 60; - - int hours = millisecs / MSecHour; - int minutes = (millisecs % MSecHour) / MSecMinute; - int seconds = ((millisecs % MSecHour) % MSecMinute) / 1000; - - std::stringstream s; - - if (hours) - s << hours << ':'; - - s << std::setfill('0') << std::setw(2) << minutes << ':' - << std::setw(2) << seconds; - return s.str(); - } - - string score_to_string(Value v) { - - std::stringstream s; - - if (v >= VALUE_MATE_IN_MAX_PLY) - s << "#" << (VALUE_MATE - v + 1) / 2; - else if (v <= VALUE_MATED_IN_MAX_PLY) - s << "-#" << (VALUE_MATE + v) / 2; - else - s << std::setprecision(2) << std::fixed << std::showpos - << float(v) / PawnValueMidgame; - - return s.str(); - } - - void pv_info_to_log(Position& pos, int depth, Value value, int time, Move pv[]) { - - const int64_t K = 1000; - const int64_t M = 1000000; - - StateInfo state[MAX_PLY_PLUS_2], *st = state; - Move* m = pv; - string san, padding; - size_t length; - std::stringstream s; - - s << std::setw(2) << depth - << std::setw(8) << score_to_string(value) - << std::setw(8) << time_to_string(time); - - if (pos.nodes_searched() < M) - s << std::setw(8) << pos.nodes_searched() / 1 << " "; - - else if (pos.nodes_searched() < K * M) - s << std::setw(7) << pos.nodes_searched() / K << "K "; - - else - s << std::setw(7) << pos.nodes_searched() / M << "M "; - - padding = string(s.str().length(), ' '); - length = padding.length(); - - while (*m != MOVE_NONE) - { - san = move_to_san(pos, *m); - - if (length + san.length() > 80) - { - s << "\n" + padding; - length = padding.length(); - } - - s << san << ' '; - length += san.length() + 1; - - pos.do_move(*m++, *st++); - } - - while (m != pv) - pos.undo_move(*--m); - - Log l(Options["Search Log Filename"]); - l << s.str() << endl; - } - - // When playing with strength handicap choose best move among the MultiPV set // using a statistical rule dependent on SkillLevel. Idea by Heinz van Saanen. @@ -1715,12 +1495,12 @@ split_point_start: // At split points actual search starts from here static RKISS rk; // PRNG sequence should be not deterministic - for (int i = abs(system_time() % 50); i > 0; i--) + for (int i = Time::current_time().msec() % 50; i > 0; i--) rk.rand(); // RootMoves are already sorted by score in descending order size_t size = std::min(MultiPV, RootMoves.size()); - int variance = std::min(RootMoves[0].score - RootMoves[size - 1].score, PawnValueMidgame); + int variance = std::min(RootMoves[0].score - RootMoves[size - 1].score, PawnValueMg); int weakness = 120 - 2 * SkillLevel; int max_s = -VALUE_INFINITE; Move best = MOVE_NONE; @@ -1749,6 +1529,50 @@ split_point_start: // At split points actual search starts from here return best; } + + // uci_pv() formats PV information according to UCI protocol. UCI requires + // to send all the PV lines also if are still to be searched and so refer to + // the previous search score. + + string uci_pv(const Position& pos, int depth, Value alpha, Value beta) { + + std::stringstream s; + int t = SearchTime.elapsed(); + int selDepth = 0; + + for (size_t i = 0; i < Threads.size(); i++) + if (Threads[i].maxPly > selDepth) + selDepth = Threads[i].maxPly; + + for (size_t i = 0; i < std::min(UCIMultiPV, RootMoves.size()); i++) + { + bool updated = (i <= PVIdx); + + if (depth == 1 && !updated) + continue; + + int d = (updated ? depth : depth - 1); + Value v = (updated ? RootMoves[i].score : RootMoves[i].prevScore); + + if (s.rdbuf()->in_avail()) + s << "\n"; + + s << "info depth " << d + << " seldepth " << selDepth + << " score " << (i == PVIdx ? score_to_uci(v, alpha, beta) : score_to_uci(v)) + << " nodes " << pos.nodes_searched() + << " nps " << (t > 0 ? pos.nodes_searched() * 1000 / t : 0) + << " time " << t + << " multipv " << i + 1 + << " pv"; + + for (size_t j = 0; RootMoves[i].pv[j] != MOVE_NONE; j++) + s << " " << move_to_uci(RootMoves[i].pv[j], Chess960); + } + + return s.str(); + } + } // namespace @@ -1771,14 +1595,14 @@ void RootMove::extract_pv_from_tt(Position& pos) { pos.do_move(m, *st++); while ( (tte = TT.probe(pos.key())) != NULL - && tte->move() != MOVE_NONE - && pos.is_pseudo_legal(tte->move()) - && pos.pl_move_is_legal(tte->move(), pos.pinned_pieces()) + && (m = tte->move()) != MOVE_NONE // Local copy, TT entry could change + && pos.is_pseudo_legal(m) + && pos.pl_move_is_legal(m, pos.pinned_pieces()) && ply < MAX_PLY && (!pos.is_draw() || ply < 2)) { - pv.push_back(tte->move()); - pos.do_move(tte->move(), *st++); + pv.push_back(m); + pos.do_move(m, *st++); ply++; } pv.push_back(MOVE_NONE); @@ -1819,11 +1643,15 @@ void RootMove::insert_pv_in_tt(Position& pos) { } -/// Thread::idle_loop() is where the thread is parked when it has no work to do. -/// The parameter 'master_sp', if non-NULL, is a pointer to an active SplitPoint -/// object for which the thread is the master. +/// Thread::idle_loop() is where the thread is parked when it has no work to do + +void Thread::idle_loop() { -void Thread::idle_loop(SplitPoint* sp_master) { + // Pointer 'sp_master', if non-NULL, points to the active SplitPoint + // object for which the thread is the master. + const SplitPoint* sp_master = splitPointsCnt ? curSplitPoint : NULL; + + assert(!sp_master || (sp_master->master == this && is_searching)); // If this thread is the master of a split point and all slaves have // finished their work at this split point, return from the idle loop. @@ -1869,13 +1697,12 @@ void Thread::idle_loop(SplitPoint* sp_master) { lock_grab(Threads.splitLock); assert(is_searching); - SplitPoint* sp = splitPoint; + SplitPoint* sp = curSplitPoint; lock_release(Threads.splitLock); Stack ss[MAX_PLY_PLUS_2]; - Position pos(*sp->pos, threadID); - int master = sp->master; + Position pos(*sp->pos, this); memcpy(ss, sp->ss - 1, 4 * sizeof(Stack)); (ss+1)->sp = sp; @@ -1894,25 +1721,26 @@ void Thread::idle_loop(SplitPoint* sp_master) { assert(is_searching); is_searching = false; - sp->slavesMask &= ~(1ULL << threadID); + sp->slavesMask &= ~(1ULL << idx); sp->nodes += pos.nodes_searched(); - // After releasing the lock we cannot access anymore any SplitPoint - // related data in a reliably way becuase it could have been released - // under our feet by the sp master. - lock_release(sp->lock); - // Wake up master thread so to allow it to return from the idle loop in // case we are the last slave of the split point. - if ( Threads.use_sleeping_threads() - && threadID != master - && !Threads[master].is_searching) - Threads[master].wake_up(); + if ( Threads.use_sleeping_threads() + && this != sp->master + && !sp->slavesMask) + { + assert(!sp->master->is_searching); + sp->master->wake_up(); + } + + // After releasing the lock we cannot access anymore any SplitPoint + // related data in a safe way becuase it could have been released under + // our feet by the sp master. Also accessing other Thread objects is + // unsafe because if we are exiting there is a chance are already freed. + lock_release(sp->lock); } } - // In helpful master concept a master can help only a sub-tree of its split - // point, and because here is all finished is not possible master is booked. - assert(!is_searching); } @@ -1922,18 +1750,18 @@ void Thread::idle_loop(SplitPoint* sp_master) { void check_time() { - static int lastInfoTime; - int e = elapsed_time(); + static Time lastInfoTime = Time::current_time(); - if (system_time() - lastInfoTime >= 1000 || !lastInfoTime) + if (lastInfoTime.elapsed() >= 1000) { - lastInfoTime = system_time(); + lastInfoTime.restart(); dbg_print(); } if (Limits.ponder) return; + int e = SearchTime.elapsed(); bool stillAtFirstMove = Signals.firstRootMove && !Signals.failedLowAtRoot && e > TimeMgr.available_time(); @@ -1942,6 +1770,6 @@ void check_time() { || stillAtFirstMove; if ( (Limits.use_time_management() && noMoreTime) - || (Limits.maxTime && e >= Limits.maxTime)) + || (Limits.movetime && e >= Limits.movetime)) Signals.stop = true; }