X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=ad13c4ba304c1a898d0a5684504d68c7e7fee47b;hp=713bd10fcb45477046973483602778a9947f594d;hb=67f88f5e3e4ee6e4b49151173c2898253178506f;hpb=f5727deee3823accf791984836f45db78e3a53e5 diff --git a/src/search.cpp b/src/search.cpp index 713bd10f..ad13c4ba 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -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-2013 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2008-2014 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 @@ -43,7 +43,7 @@ namespace Search { std::vector RootMoves; Position RootPos; Color RootColor; - Time::point SearchTime; + Time::point SearchTime, IterationTime; StateStackPtr SetupStates; } @@ -83,7 +83,7 @@ namespace { Value DrawValue[COLOR_NB]; HistoryStats History; GainsStats Gains; - CountermovesStats Countermoves; + MovesStats Countermoves, Followupmoves; template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); @@ -94,6 +94,7 @@ namespace { void id_loop(Position& pos); Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply); + void update_stats(Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt); string uci_pv(const Position& pos, int depth, Value alpha, Value beta); struct Skill { @@ -153,10 +154,10 @@ 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. -static size_t perft(Position& pos, Depth depth) { +static uint64_t perft(Position& pos, Depth depth) { StateInfo st; - size_t cnt = 0; + uint64_t cnt = 0; CheckInfo ci(pos); const bool leaf = depth == 2 * ONE_PLY; @@ -169,7 +170,7 @@ static size_t perft(Position& pos, Depth depth) { return cnt; } -size_t Search::perft(Position& pos, Depth depth) { +uint64_t Search::perft(Position& pos, Depth depth) { return depth > ONE_PLY ? ::perft(pos, depth) : MoveList(pos).size(); } @@ -303,6 +304,7 @@ namespace { History.clear(); Gains.clear(); Countermoves.clear(); + Followupmoves.clear(); PVSize = Options["MultiPV"]; Skill skill(Options["Skill Level"]); @@ -318,7 +320,7 @@ namespace { while (++depth <= MAX_PLY && !Signals.stop && (!Limits.depth || depth <= Limits.depth)) { // Age out PV variability metric - BestMoveChanges *= 0.8; + BestMoveChanges *= 0.5; // Save the last iteration's scores before first PV line is searched and // all the move scores except the (new) PV are set to -VALUE_INFINITE. @@ -395,6 +397,8 @@ namespace { sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; } + IterationTime = Time::now() - SearchTime; + // If skill levels are enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(depth)) skill.pick_move(); @@ -425,32 +429,12 @@ namespace { if (depth > 4 && depth < 50 && PVSize == 1) TimeMgr.pv_instability(BestMoveChanges); - // Stop the search if most of the available time has been used. We - // probably don't have enough time to search the first move at the - // next iteration anyway. - if (Time::now() - SearchTime > (TimeMgr.available_time() * 62) / 100) + // Stop the search if only one legal move is available or all + // of the available time has been used. + if ( RootMoves.size() == 1 + || IterationTime > TimeMgr.available_time() ) stop = true; - // Stop the search early if one move seems to be much better than others - if ( depth >= 12 - && BestMoveChanges <= DBL_EPSILON - && !stop - && PVSize == 1 - && bestValue > VALUE_MATED_IN_MAX_PLY - && ( RootMoves.size() == 1 - || Time::now() - SearchTime > (TimeMgr.available_time() * 20) / 100)) - { - Value rBeta = bestValue - 2 * PawnValueMg; - ss->excludedMove = RootMoves[0].pv[0]; - ss->skipNullMove = true; - Value v = search(pos, ss, rBeta - 1, rBeta, (depth - 3) * ONE_PLY, true); - ss->skipNullMove = false; - ss->excludedMove = MOVE_NONE; - - if (v < rBeta) - stop = true; - } - if (stop) { // If we are allowed to ponder do not stop the search now but @@ -515,7 +499,7 @@ namespace { moveCount = quietCount = 0; bestValue = -VALUE_INFINITE; - ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; + ss->currentMove = ss->ttMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; ss->ply = (ss-1)->ply + 1; (ss+1)->skipNullMove = false; (ss+1)->reduction = DEPTH_ZERO; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; @@ -528,7 +512,7 @@ namespace { { // Step 2. Check for aborted search and immediate draw if (Signals.stop || pos.is_draw() || ss->ply > MAX_PLY) - return DrawValue[pos.side_to_move()]; + return ss->ply > MAX_PLY && !inCheck ? evaluate(pos) : DrawValue[pos.side_to_move()]; // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply+1), but if alpha is already bigger because @@ -548,7 +532,7 @@ namespace { excludedMove = ss->excludedMove; posKey = excludedMove ? pos.exclusion_key() : pos.key(); tte = TT.probe(posKey); - ttMove = RootNode ? RootMoves[PVIdx].pv[0] : tte ? tte->move() : MOVE_NONE; + ss->ttMove = ttMove = RootNode ? RootMoves[PVIdx].pv[0] : tte ? tte->move() : MOVE_NONE; ttValue = tte ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; // At PV nodes we check for exact scores, whilst at non-PV nodes we check for @@ -566,27 +550,10 @@ namespace { TT.refresh(tte); ss->currentMove = ttMove; // Can be MOVE_NONE - // Update killers, history, and counter move on TT hit - if ( ttValue >= beta - && ttMove - && !pos.capture_or_promotion(ttMove) - && !inCheck) - { - if (ss->killers[0] != ttMove) - { - ss->killers[1] = ss->killers[0]; - ss->killers[0] = ttMove; - } - - Value bonus = Value(int(depth) * int(depth)); - History.update(pos.moved_piece(ttMove), to_sq(ttMove), bonus); + // If ttMove is quiet, update killers, history, counter move and followup move on TT hit + if (ttValue >= beta && ttMove && !pos.capture_or_promotion(ttMove) && !inCheck) + update_stats(pos, ss, ttMove, depth, NULL, 0); - if (is_ok((ss-1)->currentMove)) - { - Square prevMoveSq = to_sq((ss-1)->currentMove); - Countermoves.update(pos.piece_on(prevMoveSq), prevMoveSq, ttMove); - } - } return ttValue; } @@ -627,16 +594,14 @@ namespace { // Step 6. Razoring (skipped when in check) if ( !PvNode && depth < 4 * ONE_PLY - && eval + razor_margin(depth) < beta + && eval + razor_margin(depth) <= alpha && ttMove == MOVE_NONE && abs(beta) < VALUE_MATE_IN_MAX_PLY && !pos.pawn_on_7th(pos.side_to_move())) { - Value rbeta = beta - razor_margin(depth); - Value v = qsearch(pos, ss, rbeta-1, rbeta, DEPTH_ZERO); - if (v < rbeta) - // Logically we should return (v + razor_margin(depth)), but - // surprisingly this performed slightly weaker in tests. + Value ralpha = alpha - razor_margin(depth); + Value v = qsearch(pos, ss, ralpha, ralpha+1, DEPTH_ZERO); + if (v <= ralpha) return v; } @@ -660,17 +625,17 @@ namespace { { ss->currentMove = MOVE_NULL; - // Null move dynamic reduction based on depth - Depth R = 3 * ONE_PLY + depth / 4; + assert(eval - beta >= 0); - // Null move dynamic reduction based on value - if (eval - PawnValueMg > beta) - R += ONE_PLY; + // Null move dynamic reduction based on depth and value + Depth R = 3 * ONE_PLY + + depth / 4 + + int(eval - beta) / PawnValueMg * ONE_PLY; pos.do_null_move(st); (ss+1)->skipNullMove = true; - nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) - : - search(pos, ss+1, -beta, -alpha, depth-R, !cutNode); + nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -beta+1, DEPTH_ZERO) + : - search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); (ss+1)->skipNullMove = false; pos.undo_null_move(); @@ -685,7 +650,8 @@ namespace { // Do verification search at high depths ss->skipNullMove = true; - Value v = search(pos, ss, alpha, beta, depth-R, false); + Value v = depth-R < ONE_PLY ? qsearch(pos, ss, beta-1, beta, DEPTH_ZERO) + : search(pos, ss, beta-1, beta, depth-R, false); ss->skipNullMove = false; if (v >= beta) @@ -745,7 +711,11 @@ moves_loop: // When in check and at SpNode search starts from here Move countermoves[] = { Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq].first, Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq].second }; - MovePicker mp(pos, ttMove, depth, History, countermoves, ss); + Square prevOwnMoveSq = to_sq((ss-2)->currentMove); + Move followupmoves[] = { Followupmoves[pos.piece_on(prevOwnMoveSq)][prevOwnMoveSq].first, + Followupmoves[pos.piece_on(prevOwnMoveSq)][prevOwnMoveSq].second }; + + MovePicker mp(pos, ttMove, depth, History, countermoves, followupmoves, ss); CheckInfo ci(pos); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc improving = ss->staticEval >= (ss-2)->staticEval @@ -799,7 +769,11 @@ moves_loop: // When in check and at SpNode search starts from here ext = DEPTH_ZERO; captureOrPromotion = pos.capture_or_promotion(move); - givesCheck = pos.gives_check(move, ci); + + givesCheck = type_of(move) == NORMAL && !ci.dcCandidates + ? ci.checkSq[type_of(pos.piece_on(from_sq(move)))] & to_sq(move) + : pos.gives_check(move, ci); + dangerous = givesCheck || type_of(move) != NORMAL || pos.advanced_pawn_push(move); @@ -1062,30 +1036,9 @@ moves_loop: // When in check and at SpNode search starts from here PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, depth, bestMove, ss->staticEval); - // Quiet best move: update killers, history and countermoves - if ( bestValue >= beta - && !pos.capture_or_promotion(bestMove) - && !inCheck) - { - if (ss->killers[0] != bestMove) - { - ss->killers[1] = ss->killers[0]; - ss->killers[0] = bestMove; - } - - // Increase history value of the cut-off move and decrease all the other - // played non-capture moves. - Value bonus = Value(int(depth) * int(depth)); - History.update(pos.moved_piece(bestMove), to_sq(bestMove), bonus); - for (int i = 0; i < quietCount - 1; ++i) - { - Move m = quietsSearched[i]; - History.update(pos.moved_piece(m), to_sq(m), -bonus); - } - - if (is_ok((ss-1)->currentMove)) - Countermoves.update(pos.piece_on(prevMoveSq), prevMoveSq, bestMove); - } + // Quiet best move: update killers, history, countermoves and followupmoves + if (bestValue >= beta && !pos.capture_or_promotion(bestMove) && !inCheck) + update_stats(pos, ss, bestMove, depth, quietsSearched, quietCount - 1); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1125,7 +1078,7 @@ moves_loop: // When in check and at SpNode search starts from here // Check for an instant draw or if the maximum ply has been reached if (pos.is_draw() || ss->ply > MAX_PLY) - return DrawValue[pos.side_to_move()]; + return ss->ply > MAX_PLY && !InCheck ? evaluate(pos) : DrawValue[pos.side_to_move()]; // Decide whether or not to include checks: this fixes also the type of // TT entry depth that we are going to use. Note that in qsearch we use @@ -1200,7 +1153,9 @@ moves_loop: // When in check and at SpNode search starts from here { assert(is_ok(move)); - givesCheck = pos.gives_check(move, ci); + givesCheck = type_of(move) == NORMAL && !ci.dcCandidates + ? ci.checkSq[type_of(pos.piece_on(from_sq(move)))] & to_sq(move) + : pos.gives_check(move, ci); // Futility pruning if ( !PvNode @@ -1220,10 +1175,7 @@ moves_loop: // When in check and at SpNode search starts from here continue; } - // Prune moves with negative or equal SEE and also moves with positive - // SEE where capturing piece loses a tempo and SEE < beta - futilityBase. - if ( futilityBase < beta - && pos.see(move, beta - futilityBase) <= 0) + if (futilityBase < beta && pos.see(move) <= 0) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1321,6 +1273,41 @@ moves_loop: // When in check and at SpNode search starts from here } + // update_stats() updates killers, history, countermoves and followupmoves stats after a fail-high + // of a quiet move. + + void update_stats(Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt) { + + if (ss->killers[0] != move) + { + ss->killers[1] = ss->killers[0]; + ss->killers[0] = move; + } + + // Increase history value of the cut-off move and decrease all the other + // played quiet moves. + Value bonus = Value(int(depth) * int(depth)); + History.update(pos.moved_piece(move), to_sq(move), bonus); + for (int i = 0; i < quietsCnt; ++i) + { + Move m = quiets[i]; + History.update(pos.moved_piece(m), to_sq(m), -bonus); + } + + if (is_ok((ss-1)->currentMove)) + { + Square prevMoveSq = to_sq((ss-1)->currentMove); + Countermoves.update(pos.piece_on(prevMoveSq), prevMoveSq, move); + } + + if (is_ok((ss-2)->currentMove) && (ss-1)->currentMove == (ss-1)->ttMove) + { + Square prevOwnMoveSq = to_sq((ss-2)->currentMove); + Followupmoves.update(pos.piece_on(prevOwnMoveSq), prevOwnMoveSq, move); + } + } + + // When playing with a strength handicap, choose best move among the MultiPV // set using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. @@ -1640,7 +1627,8 @@ void check_time() { Time::point elapsed = Time::now() - SearchTime; bool stillAtFirstMove = Signals.firstRootMove && !Signals.failedLowAtRoot - && elapsed > TimeMgr.available_time(); + && elapsed > TimeMgr.available_time() + && elapsed > IterationTime * 1.4; bool noMoreTime = elapsed > TimeMgr.maximum_time() - 2 * TimerThread::Resolution || stillAtFirstMove;