X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=8f1964dd6e9f8b426fc03a2189f30a068de6b7dc;hp=27365069420871c0dcddd0ba8308dcf63d7e4947;hb=bc4de9edaec0a618279092abbf465f47720736b8;hpb=caef31921900e092616c56193e37201b08baa875 diff --git a/src/search.cpp b/src/search.cpp index 27365069..8f1964dd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include @@ -30,6 +29,7 @@ #include "history.h" #include "movegen.h" #include "movepick.h" +#include "notation.h" #include "search.h" #include "timeman.h" #include "thread.h" @@ -51,11 +51,6 @@ using std::endl; using Eval::evaluate; using namespace Search; -// For some reason argument-dependent lookup (ADL) doesn't work for Android's -// STLPort, so explicitly qualify following functions. -using std::count; -using std::find; - namespace { // Set to true to force running with one thread. Used for debugging @@ -144,46 +139,25 @@ namespace { bool connected_threat(const Position& pos, Move m, Move threat); Value refine_eval(const TTEntry* tte, Value ttValue, Value defaultEval); Move do_skill_level(); - 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 + // Entering a pawn endgame? if ( captureOrPromotion && type_of(pos.piece_on(to_sq(m))) != PAWN - && !is_special(m) + && type_of(m) == NORMAL && ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) - PieceValueMidgame[pos.piece_on(to_sq(m))] == VALUE_ZERO)) return true; @@ -229,7 +203,7 @@ int64_t Search::perft(Position& pos, Depth depth) { StateInfo st; int64_t cnt = 0; - MoveList ml(pos); + MoveList ml(pos); // At the last ply just return the number of moves (leaf nodes) if (depth == ONE_PLY) @@ -274,9 +248,9 @@ void Search::think() { { 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; } } @@ -427,7 +401,7 @@ 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) || SearchTime.elapsed() > 2000) - pv_info_to_uci(pos, depth, alpha, beta); + 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. @@ -457,7 +431,11 @@ namespace { skillBest = do_skill_level(); if (!Signals.stop && Options["Use Search Log"]) - pv_info_to_log(pos, depth, bestValue, SearchTime.elapsed(), &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) @@ -513,7 +491,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)); } } @@ -661,7 +639,7 @@ namespace { && (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); @@ -708,16 +686,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++; + 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); @@ -732,7 +710,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) @@ -777,7 +755,7 @@ namespace { 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; @@ -806,7 +784,7 @@ 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); futilityBase = ss->eval + ss->evalMargin; singularExtensionNode = !RootNode @@ -820,7 +798,7 @@ split_point_start: // At split points actual search starts from here // 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 + && (move = mp.next_move()) != MOVE_NONE && !thisThread->cutoff_occurred() && !Signals.stop) { @@ -832,7 +810,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 @@ -878,19 +856,18 @@ split_point_start: // At split points actual search starts from here if ( singularExtensionNode && !ext && move == ttMove - && pos.pl_move_is_legal(move, ci.pinned)) + && pos.pl_move_is_legal(move, ci.pinned) + && abs(ttValue) < VALUE_KNOWN_WIN) { - 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; - 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) @@ -902,7 +879,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 @@ -961,7 +937,6 @@ split_point_start: // At split points actual search starts from here && !isPvMove && !captureOrPromotion && !dangerous - && !is_castle(move) && ss->killers[0] != move && ss->killers[1] != move) { @@ -1011,7 +986,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) @@ -1057,7 +1032,6 @@ split_point_start: // At split points actual search starts from here // Step 19. Check for split if ( !SpNode && depth >= Threads.min_split_depth() - && depth - reduction(depth, moveCount) >= Threads.min_split_depth() && bestValue < beta && Threads.available_slave_exists(thisThread) && !Signals.stop @@ -1215,7 +1189,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)); @@ -1227,12 +1201,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); + + (type_of(move) == ENPASSANT ? PawnValueEndgame : VALUE_ZERO); if (futilityValue < beta) { @@ -1260,7 +1234,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; @@ -1355,7 +1329,7 @@ split_point_start: // At split points actual search starts from here while (b) { // Note that here we generate illegal "double move"! - if (futilityBase + PieceValueEndgame[pos.piece_on(pop_1st_bit(&b))] >= beta) + if (futilityBase + PieceValueEndgame[pos.piece_on(pop_lsb(&b))] >= beta) return true; } @@ -1512,156 +1486,6 @@ split_point_start: // At split points actual search starts from here } - // 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 = SearchTime.elapsed(); - 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. @@ -1706,6 +1530,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 (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); + + 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