X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=442f7f5ddc3e4fda6592ac903407d3f4f04550a4;hp=8f46d82a5ed856abb0d68a847e7f2e97afa92e49;hb=fbfce2132aff3e8528086fb14ae3fb8ef47c212e;hpb=e074a19f5c8551ebb7ac13bd8881138479f2285a diff --git a/src/search.cpp b/src/search.cpp index 8f46d82a..442f7f5d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -66,7 +66,7 @@ namespace { // Futility lookup tables (initialized at startup) and their access functions Value FutilityMargins[16][64]; // [depth][moveNumber] - int FutilityMoveCounts[32]; // [depth] + int FutilityMoveCounts[2][32]; // [improving][depth] inline Value futility_margin(Depth d, int mn) { @@ -75,16 +75,16 @@ namespace { } // Reduction lookup tables (initialized at startup) and their access function - int8_t Reductions[2][64][64]; // [pv][depth][moveNumber] + int8_t Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber] - template inline Depth reduction(Depth d, int mn) { + template inline Depth reduction(bool i, Depth d, int mn) { - return (Depth) Reductions[PvNode][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)]; + return (Depth) Reductions[PvNode][i][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)]; } size_t PVSize, PVIdx; TimeManager TimeMgr; - int BestMoveChanges; + double BestMoveChanges; Value DrawValue[COLOR_NB]; HistoryStats History; GainsStats Gains; @@ -99,7 +99,6 @@ namespace { void id_loop(Position& pos); Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply); - bool check_is_dangerous(const Position& pos, Move move, Value futilityBase, Value beta); bool allows(const Position& pos, Move first, Move second); bool refutes(const Position& pos, Move first, Move second); string uci_pv(const Position& pos, int depth, Value alpha, Value beta); @@ -132,28 +131,40 @@ void Search::init() { int mc; // moveCount // Init reductions array - for (hd = 1; hd < 64; hd++) for (mc = 1; mc < 64; mc++) + for (hd = 1; hd < 64; ++hd) for (mc = 1; mc < 64; ++mc) { double pvRed = log(double(hd)) * log(double(mc)) / 3.0; double nonPVRed = 0.33 + log(double(hd)) * log(double(mc)) / 2.25; - Reductions[1][hd][mc] = (int8_t) ( pvRed >= 1.0 ? floor( pvRed * int(ONE_PLY)) : 0); - Reductions[0][hd][mc] = (int8_t) (nonPVRed >= 1.0 ? floor(nonPVRed * int(ONE_PLY)) : 0); + Reductions[1][1][hd][mc] = (int8_t) ( pvRed >= 1.0 ? floor( pvRed * int(ONE_PLY)) : 0); + Reductions[0][1][hd][mc] = (int8_t) (nonPVRed >= 1.0 ? floor(nonPVRed * int(ONE_PLY)) : 0); + + Reductions[1][0][hd][mc] = Reductions[1][1][hd][mc]; + Reductions[0][0][hd][mc] = Reductions[0][1][hd][mc]; + + if (Reductions[0][0][hd][mc] > 2 * ONE_PLY) + Reductions[0][0][hd][mc] += ONE_PLY; + + else if (Reductions[0][0][hd][mc] > 1 * ONE_PLY) + Reductions[0][0][hd][mc] += ONE_PLY / 2; } // Init futility margins array - for (d = 1; d < 16; d++) for (mc = 0; mc < 64; mc++) - FutilityMargins[d][mc] = Value(112 * int(log(double(d * d) / 2) / log(2.0) + 1.001) - 8 * mc + 45); + for (d = 1; d < 16; ++d) for (mc = 0; mc < 64; ++mc) + FutilityMargins[d][mc] = Value(112 * int(2.9 * log(double(d))) - 8 * mc + 45); // Init futility move count array - for (d = 0; d < 32; d++) - FutilityMoveCounts[d] = int(3.001 + 0.3 * pow(double(d), 1.8)); + for (d = 0; d < 32; ++d) + { + FutilityMoveCounts[0][d] = int(3 + 0.3 * pow(double(d ), 1.8)) * 3/4 + (2 < d && d < 5); + FutilityMoveCounts[1][d] = int(3 + 0.3 * pow(double(d + 0.98), 1.8)); + } } /// 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. -size_t Search::perft(Position& pos, Depth depth) { +static size_t perft(Position& pos, Depth depth) { StateInfo st; size_t cnt = 0; @@ -162,13 +173,16 @@ size_t Search::perft(Position& pos, Depth depth) { for (MoveList it(pos); *it; ++it) { - pos.do_move(*it, st, ci, pos.move_gives_check(*it, ci)); - cnt += leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); + pos.do_move(*it, st, ci, pos.gives_check(*it, ci)); + cnt += leaf ? MoveList(pos).size() : ::perft(pos, depth - ONE_PLY); pos.undo_move(*it); } return cnt; } +size_t Search::perft(Position& pos, Depth depth) { + return depth > ONE_PLY ? ::perft(pos, depth) : MoveList(pos).size(); +} /// Search::think() is the external interface to Stockfish's search, and is /// called by the main thread when the program receives the UCI 'go' command. It @@ -212,7 +226,7 @@ void Search::think() { else DrawValue[WHITE] = DrawValue[BLACK] = VALUE_DRAW; - if (Options["Use Search Log"]) + if (Options["Write Search Log"]) { Log log(Options["Search Log Filename"]); log << "\nSearching: " << RootPos.fen() @@ -225,10 +239,10 @@ void Search::think() { } // Reset the threads, still sleeping: will be wake up at split time - for (size_t i = 0; i < Threads.size(); i++) + for (size_t i = 0; i < Threads.size(); ++i) Threads[i]->maxPly = 0; - Threads.sleepWhileIdle = Options["Use Sleeping Threads"]; + Threads.sleepWhileIdle = Options["Idle Threads Sleep"]; // Set best timer interval to avoid lagging under time pressure. Timer is // used to check for remaining available thinking time. @@ -244,7 +258,7 @@ void Search::think() { Threads.timer->msec = 0; // Stop the timer Threads.sleepWhileIdle = true; // Send idle threads to sleep - if (Options["Use Search Log"]) + if (Options["Write Search Log"]) { Time::point elapsed = Time::now() - SearchTime + 1; @@ -291,14 +305,15 @@ namespace { void id_loop(Position& pos) { - Stack stack[MAX_PLY_PLUS_2], *ss = stack+1; // To allow referencing (ss-1) - int depth, prevBestMoveChanges; + Stack stack[MAX_PLY_PLUS_6], *ss = stack+2; // To allow referencing (ss-2) + int depth; Value bestValue, alpha, beta, delta; - memset(ss-1, 0, 4 * sizeof(Stack)); + std::memset(ss-2, 0, 5 * sizeof(Stack)); (ss-1)->currentMove = MOVE_NULL; // Hack to skip update gains - depth = BestMoveChanges = 0; + depth = 0; + BestMoveChanges = 0; bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; @@ -320,16 +335,16 @@ namespace { // Iterative deepening loop until requested to stop or target depth reached while (++depth <= MAX_PLY && !Signals.stop && (!Limits.depth || depth <= Limits.depth)) { + // Age out PV variability metric + BestMoveChanges *= 0.8; + // 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. - for (size_t i = 0; i < RootMoves.size(); i++) + for (size_t i = 0; i < RootMoves.size(); ++i) RootMoves[i].prevScore = RootMoves[i].score; - prevBestMoveChanges = BestMoveChanges; // Only sensible when PVSize == 1 - BestMoveChanges = 0; - // MultiPV loop. We perform a full root search for each PV line - for (PVIdx = 0; PVIdx < PVSize; PVIdx++) + for (PVIdx = 0; PVIdx < PVSize; ++PVIdx) { // Reset aspiration window starting size if (depth >= 5) @@ -355,7 +370,7 @@ namespace { // Write PV back to transposition table in case the relevant // entries have been overwritten during the search. - for (size_t i = 0; i <= PVIdx; i++) + for (size_t i = 0; i <= PVIdx; ++i) RootMoves[i].insert_pv_in_tt(pos); // If search has been stopped return immediately. Sorting and @@ -364,6 +379,12 @@ namespace { if (Signals.stop) return; + // When failing high/low give some update (without cluttering + // the UI) before to research. + if ( (bestValue <= alpha || bestValue >= beta) + && Time::now() - SearchTime > 3000) + sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; + // In case of failing low/high increase aspiration window and // research, otherwise exit the loop. if (bestValue <= alpha) @@ -382,10 +403,6 @@ namespace { delta += delta / 2; assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); - - // Give some update (without cluttering the UI) before to research - if (Time::now() - SearchTime > 3000) - sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; } // Sort the PV lines searched so far and update the GUI @@ -399,7 +416,7 @@ namespace { if (skill.enabled() && skill.time_to_pick(depth)) skill.pick_move(); - if (Options["Use Search Log"]) + if (Options["Write Search Log"]) { RootMove& rm = RootMoves[0]; if (skill.best != MOVE_NONE) @@ -423,7 +440,7 @@ namespace { // Take in account some extra time if the best move has changed if (depth > 4 && depth < 50 && PVSize == 1) - TimeMgr.pv_instability(BestMoveChanges, prevBestMoveChanges); + TimeMgr.pv_instability(BestMoveChanges); // Stop search if most of available time is already consumed. We // probably don't have enough time to search the first move at the @@ -491,13 +508,12 @@ namespace { Depth ext, newDepth; Value bestValue, value, ttValue; Value eval, nullValue, futilityValue; - bool inCheck, givesCheck, pvMove, singularExtensionNode; + bool inCheck, givesCheck, pvMove, singularExtensionNode, improving; bool captureOrPromotion, dangerous, doFullDepthSearch; int moveCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); - moveCount = quietCount = 0; inCheck = pos.checkers(); if (SpNode) @@ -512,9 +528,10 @@ namespace { assert(splitPoint->bestValue > -VALUE_INFINITE && splitPoint->moveCount > 0); - goto split_point_start; + goto moves_loop; } + moveCount = quietCount = 0; bestValue = -VALUE_INFINITE; ss->currentMove = threatMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; ss->ply = (ss-1)->ply + 1; @@ -570,7 +587,7 @@ namespace { if ( ttValue >= beta && ttMove - && !pos.is_capture_or_promotion(ttMove) + && !pos.capture_or_promotion(ttMove) && ttMove != ss->killers[0]) { ss->killers[1] = ss->killers[0]; @@ -581,7 +598,10 @@ namespace { // Step 5. Evaluate the position statically and update parent's gain statistics if (inCheck) + { ss->staticEval = ss->evalMargin = eval = VALUE_NONE; + goto moves_loop; + } else if (tte) { @@ -592,8 +612,7 @@ namespace { // Can ttValue be used as a better position evaluation? if (ttValue != VALUE_NONE) - if ( ((tte->bound() & BOUND_LOWER) && ttValue > eval) - || ((tte->bound() & BOUND_UPPER) && ttValue < eval)) + if (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)) eval = ttValue; } else @@ -615,10 +634,9 @@ namespace { Gains.update(pos.piece_on(to), to, -(ss-1)->staticEval - ss->staticEval); } - // Step 6. Razoring (is omitted in PV nodes) + // Step 6. Razoring (skipped when in check) if ( !PvNode && depth < 4 * ONE_PLY - && !inCheck && eval + razor_margin(depth) < beta && ttMove == MOVE_NONE && abs(beta) < VALUE_MATE_IN_MAX_PLY @@ -632,13 +650,12 @@ namespace { return v; } - // Step 7. Static null move pruning (is omitted in PV nodes) + // Step 7. Static null move pruning (skipped when in check) // We're betting that the opponent doesn't have a move that will reduce // the score by more than futility_margin(depth) if we do a null move. if ( !PvNode && !ss->skipNullMove && depth < 4 * ONE_PLY - && !inCheck && eval - futility_margin(depth, (ss-1)->futilityMoveCount) >= beta && abs(beta) < VALUE_MATE_IN_MAX_PLY && abs(eval) < VALUE_KNOWN_WIN @@ -648,8 +665,7 @@ namespace { // Step 8. Null move search with verification search (is omitted in PV nodes) if ( !PvNode && !ss->skipNullMove - && depth > ONE_PLY - && !inCheck + && depth >= 2 * ONE_PLY && eval >= beta && abs(beta) < VALUE_MATE_IN_MAX_PLY && pos.non_pawn_material(pos.side_to_move())) @@ -705,13 +721,12 @@ namespace { } } - // Step 9. ProbCut (is omitted in PV nodes) + // Step 9. ProbCut (skipped when in check) // If we have a very good capture (i.e. SEE > seeValues[captured_piece_type]) // and a reduced search returns a value much above beta, we can (almost) safely // prune the previous move. if ( !PvNode && depth >= 5 * ONE_PLY - && !inCheck && !ss->skipNullMove && abs(beta) < VALUE_MATE_IN_MAX_PLY) { @@ -726,10 +741,10 @@ namespace { CheckInfo ci(pos); while ((move = mp.next_move()) != MOVE_NONE) - if (pos.pl_move_is_legal(move, ci.pinned)) + if (pos.legal(move, ci.pinned)) { ss->currentMove = move; - pos.do_move(move, st, ci, pos.move_gives_check(move, ci)); + pos.do_move(move, st, ci, pos.gives_check(move, ci)); value = -search(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode); pos.undo_move(move); if (value >= rbeta) @@ -737,10 +752,10 @@ namespace { } } - // Step 10. Internal iterative deepening + // Step 10. Internal iterative deepening (skipped when in check) if ( depth >= (PvNode ? 5 * ONE_PLY : 8 * ONE_PLY) && ttMove == MOVE_NONE - && (PvNode || (!inCheck && ss->staticEval + Value(256) >= beta))) + && (PvNode || ss->staticEval + Value(256) >= beta)) { Depth d = depth - 2 * ONE_PLY - (PvNode ? DEPTH_ZERO : depth / 4); @@ -752,18 +767,22 @@ namespace { ttMove = tte ? tte->move() : MOVE_NONE; } -split_point_start: // At split points actual search starts from here +moves_loop: // When in check and at SpNode search starts from here Square prevMoveSq = to_sq((ss-1)->currentMove); Move countermoves[] = { Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq].first, Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq].second }; - MovePicker mp(pos, ttMove, depth, History, countermoves, ss, PvNode ? -VALUE_INFINITE : beta); + MovePicker mp(pos, ttMove, depth, History, countermoves, ss); CheckInfo ci(pos); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc + improving = ss->staticEval >= (ss-2)->staticEval + || ss->staticEval == VALUE_NONE + ||(ss-2)->staticEval == VALUE_NONE; + singularExtensionNode = !RootNode && !SpNode - && depth >= (PvNode ? 6 * ONE_PLY : 8 * ONE_PLY) + && depth >= 8 * ONE_PLY && ttMove != MOVE_NONE && !excludedMove // Recursive singular search is not allowed && (tte->bound() & BOUND_LOWER) @@ -787,43 +806,35 @@ split_point_start: // At split points actual search starts from here if (SpNode) { // Shared counter cannot be decremented later if move turns out to be illegal - if (!pos.pl_move_is_legal(move, ci.pinned)) + if (!pos.legal(move, ci.pinned)) continue; moveCount = ++splitPoint->moveCount; splitPoint->mutex.unlock(); } else - moveCount++; + ++moveCount; if (RootNode) { Signals.firstRootMove = (moveCount == 1); - if (thisThread == Threads.main_thread() && Time::now() - SearchTime > 3000) + if (thisThread == Threads.main() && Time::now() - SearchTime > 3000) sync_cout << "info depth " << depth / ONE_PLY << " currmove " << move_to_uci(move, pos.is_chess960()) << " currmovenumber " << moveCount + PVIdx << sync_endl; } ext = DEPTH_ZERO; - captureOrPromotion = pos.is_capture_or_promotion(move); - givesCheck = pos.move_gives_check(move, ci); + captureOrPromotion = pos.capture_or_promotion(move); + givesCheck = pos.gives_check(move, ci); dangerous = givesCheck - || pos.is_passed_pawn_push(move) - || type_of(move) == CASTLE - || ( captureOrPromotion // Entering a pawn endgame? - && type_of(pos.piece_on(to_sq(move))) != PAWN - && type_of(move) == NORMAL - && ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) - - PieceValue[MG][pos.piece_on(to_sq(move))] == VALUE_ZERO)); - - // Step 12. Extend checks and, in PV nodes, also dangerous moves - if (PvNode && dangerous) - ext = ONE_PLY; + || pos.passed_pawn_push(move) + || type_of(move) == CASTLE; - else if (givesCheck && pos.see_sign(move) >= 0) - ext = ONE_PLY / 2; + // Step 12. Extend checks + if (givesCheck && pos.see_sign(move) >= 0) + ext = ONE_PLY; // 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 @@ -833,7 +844,7 @@ split_point_start: // At split points actual search starts from here if ( singularExtensionNode && move == ttMove && !ext - && pos.pl_move_is_legal(move, ci.pinned) + && pos.legal(move, ci.pinned) && abs(ttValue) < VALUE_KNOWN_WIN) { assert(ttValue != VALUE_NONE); @@ -862,7 +873,7 @@ split_point_start: // At split points actual search starts from here { // Move count based pruning if ( depth < 16 * ONE_PLY - && moveCount >= FutilityMoveCounts[depth] + && moveCount >= FutilityMoveCounts[improving][depth] && (!threatMove || !refutes(pos, move, threatMove))) { if (SpNode) @@ -874,9 +885,9 @@ split_point_start: // At split points actual search starts from here // Value based pruning // We illogically ignore reduction condition depth >= 3*ONE_PLY for predicted depth, // but fixing this made program slightly weaker. - Depth predictedDepth = newDepth - reduction(depth, moveCount); + Depth predictedDepth = newDepth - reduction(improving, depth, moveCount); futilityValue = ss->staticEval + ss->evalMargin + futility_margin(predictedDepth, moveCount) - + Gains[pos.piece_moved(move)][to_sq(move)]; + + Gains[pos.moved_piece(move)][to_sq(move)]; if (futilityValue < beta) { @@ -909,9 +920,9 @@ split_point_start: // At split points actual search starts from here ss->futilityMoveCount = 0; // Check for legality only before to do the move - if (!RootNode && !SpNode && !pos.pl_move_is_legal(move, ci.pinned)) + if (!RootNode && !SpNode && !pos.legal(move, ci.pinned)) { - moveCount--; + --moveCount; continue; } @@ -925,21 +936,23 @@ 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 && !pvMove && !captureOrPromotion - && !dangerous && move != ttMove && move != ss->killers[0] && move != ss->killers[1]) { - ss->reduction = reduction(depth, moveCount); + ss->reduction = reduction(improving, depth, moveCount); if (!PvNode && cutNode) ss->reduction += ONE_PLY; + else if (History[pos.piece_on(to_sq(move))][to_sq(move)] < 0) + ss->reduction += ONE_PLY / 2; + if (move == countermoves[0] || move == countermoves[1]) - ss->reduction = std::max(DEPTH_ZERO, ss->reduction-ONE_PLY); + ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY); Depth d = std::max(newDepth - ss->reduction, ONE_PLY); if (SpNode) @@ -1007,7 +1020,7 @@ split_point_start: // At split points actual search starts from here // iteration. This information is used for time management: When // the best move changes frequently, we allocate some more time. if (!pvMove) - BestMoveChanges++; + ++BestMoveChanges; } else // All other moves but the PV are set to the lowest value, this @@ -1078,7 +1091,7 @@ split_point_start: // At split points actual search starts from here // Quiet best move: update killers, history and countermoves if ( bestValue >= beta - && !pos.is_capture_or_promotion(bestMove) + && !pos.capture_or_promotion(bestMove) && !inCheck) { if (ss->killers[0] != bestMove) @@ -1090,11 +1103,11 @@ split_point_start: // At split points actual search starts from here // 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.piece_moved(bestMove), to_sq(bestMove), bonus); - for (int i = 0; i < quietCount - 1; i++) + 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.piece_moved(m), to_sq(m), -bonus); + History.update(pos.moved_piece(m), to_sq(m), -bonus); } if (is_ok((ss-1)->currentMove)) @@ -1127,7 +1140,7 @@ split_point_start: // At split points actual search starts from here Key posKey; Move ttMove, move, bestMove; Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; - bool givesCheck, enoughMaterial, evasionPrunable; + bool givesCheck, evasionPrunable; Depth ttDepth; // To flag BOUND_EXACT a node with eval above alpha and no available moves @@ -1169,7 +1182,6 @@ split_point_start: // At split points actual search starts from here { ss->staticEval = ss->evalMargin = VALUE_NONE; bestValue = futilityBase = -VALUE_INFINITE; - enoughMaterial = false; } else { @@ -1179,6 +1191,11 @@ split_point_start: // At split points actual search starts from here if ( (ss->staticEval = bestValue = tte->eval_value()) == VALUE_NONE ||(ss->evalMargin = tte->eval_margin()) == VALUE_NONE) ss->staticEval = bestValue = evaluate(pos, ss->evalMargin); + + // Can ttValue be used as a better position evaluation? + if (ttValue != VALUE_NONE) + if (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)) + bestValue = ttValue; } else ss->staticEval = bestValue = evaluate(pos, ss->evalMargin); @@ -1196,8 +1213,7 @@ split_point_start: // At split points actual search starts from here if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + ss->evalMargin + Value(128); - enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMg; + futilityBase = bestValue + ss->evalMargin + Value(128); } // Initialize a MovePicker object for the current position, and prepare @@ -1212,16 +1228,16 @@ split_point_start: // At split points actual search starts from here { assert(is_ok(move)); - givesCheck = pos.move_gives_check(move, ci); + givesCheck = pos.gives_check(move, ci); // Futility pruning if ( !PvNode && !InCheck && !givesCheck && move != ttMove - && enoughMaterial && type_of(move) != PROMOTION - && !pos.is_passed_pawn_push(move)) + && futilityBase > -VALUE_KNOWN_WIN + && !pos.passed_pawn_push(move)) { futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))] @@ -1244,10 +1260,9 @@ split_point_start: // At split points actual search starts from here } // Detect non-capture evasions that are candidate to be pruned - evasionPrunable = !PvNode - && InCheck + evasionPrunable = InCheck && bestValue > VALUE_MATED_IN_MAX_PLY - && !pos.is_capture(move) + && !pos.capture(move) && !pos.can_castle(pos.side_to_move()); // Don't search moves with negative SEE values @@ -1258,18 +1273,8 @@ split_point_start: // At split points actual search starts from here && pos.see_sign(move) < 0) continue; - // Don't search useless checks - if ( !PvNode - && !InCheck - && givesCheck - && move != ttMove - && !pos.is_capture_or_promotion(move) - && ss->staticEval + PawnValueMg / 4 < beta - && !check_is_dangerous(pos, move, futilityBase, beta)) - continue; - // Check for legality only before to do the move - if (!pos.pl_move_is_legal(move, ci.pinned)) + if (!pos.legal(move, ci.pinned)) continue; ss->currentMove = move; @@ -1345,42 +1350,6 @@ split_point_start: // At split points actual search starts from here } - // check_is_dangerous() tests if a checking move can be pruned in qsearch() - - bool check_is_dangerous(const Position& pos, Move move, Value futilityBase, Value beta) - { - Piece pc = pos.piece_moved(move); - Square from = from_sq(move); - Square to = to_sq(move); - Color them = ~pos.side_to_move(); - Square ksq = pos.king_square(them); - Bitboard enemies = pos.pieces(them); - Bitboard kingAtt = pos.attacks_from(ksq); - Bitboard occ = pos.pieces() ^ from ^ ksq; - Bitboard oldAtt = pos.attacks_from(pc, from, occ); - Bitboard newAtt = pos.attacks_from(pc, to, occ); - - // Checks which give opponent's king at most one escape square are dangerous - if (!more_than_one(kingAtt & ~(enemies | newAtt | to))) - return true; - - // Queen contact check is very dangerous - if (type_of(pc) == QUEEN && (kingAtt & to)) - return true; - - // Creating new double threats with checks is dangerous - Bitboard b = (enemies ^ ksq) & newAtt & ~oldAtt; - while (b) - { - // Note that here we generate illegal "double move"! - if (futilityBase + PieceValue[EG][pos.piece_on(pop_lsb(&b))] >= beta) - return true; - } - - return false; - } - - // allows() tests whether the 'first' move at previous ply somehow makes the // 'second' move possible, for instance if the moving piece is the same in // both moves. Normally the second move is the threat (the best move returned @@ -1391,7 +1360,7 @@ split_point_start: // At split points actual search starts from here assert(is_ok(first)); assert(is_ok(second)); assert(color_of(pos.piece_on(from_sq(second))) == ~pos.side_to_move()); - assert(color_of(pos.piece_on(to_sq(first))) == ~pos.side_to_move()); + assert(type_of(first) == CASTLE || color_of(pos.piece_on(to_sq(first))) == ~pos.side_to_move()); Square m1from = from_sq(first); Square m2from = from_sq(second); @@ -1399,7 +1368,10 @@ split_point_start: // At split points actual search starts from here Square m2to = to_sq(second); // The piece is the same or second's destination was vacated by the first move - if (m1to == m2from || m2to == m1from) + // We exclude the trivial case where a sliding piece does in two moves what + // it could do in one move: eg. Ra1a2, Ra2a3. + if ( m2to == m1from + || (m1to == m2from && !squares_aligned(m1from, m2from, m2to))) return true; // Second one moves through the square vacated by first one @@ -1442,7 +1414,7 @@ split_point_start: // At split points actual search starts from here // If the threatened piece has value less than or equal to the value of the // threat piece, don't prune moves which defend it. - if ( pos.is_capture(second) + if ( pos.capture(second) && ( PieceValue[MG][pos.piece_on(m2from)] >= PieceValue[MG][pos.piece_on(m2to)] || type_of(pos.piece_on(m2from)) == KING)) { @@ -1459,7 +1431,7 @@ split_point_start: // At split points actual search starts from here | (attacks_bb(m2to, occ) & pos.pieces(color_of(pc), QUEEN, BISHOP)); // Verify attackers are triggered by our move and not already existing - if (xray && (xray ^ (xray & pos.attacks_from(m2to)))) + if (unlikely(xray) && (xray & ~pos.attacks_from(m2to))) return true; } @@ -1479,7 +1451,7 @@ split_point_start: // At split points actual search starts from here static RKISS rk; // PRNG sequence should be not deterministic - for (int i = Time::now() % 50; i > 0; i--) + for (int i = Time::now() % 50; i > 0; --i) rk.rand(); // RootMoves are already sorted by score in descending order @@ -1491,7 +1463,7 @@ split_point_start: // At split points actual search starts from here // Choose best move. For each move score we add two terms both dependent on // weakness, one deterministic and bigger for weaker moves, and one random, // then we choose the move with the resulting highest score. - for (size_t i = 0; i < PVSize; i++) + for (size_t i = 0; i < PVSize; ++i) { int s = RootMoves[i].score; @@ -1524,11 +1496,11 @@ split_point_start: // At split points actual search starts from here size_t uciPVSize = std::min((size_t)Options["MultiPV"], RootMoves.size()); int selDepth = 0; - for (size_t i = 0; i < Threads.size(); i++) + for (size_t i = 0; i < Threads.size(); ++i) if (Threads[i]->maxPly > selDepth) selDepth = Threads[i]->maxPly; - for (size_t i = 0; i < uciPVSize; i++) + for (size_t i = 0; i < uciPVSize; ++i) { bool updated = (i <= PVIdx); @@ -1550,7 +1522,7 @@ split_point_start: // At split points actual search starts from here << " multipv " << i + 1 << " pv"; - for (size_t j = 0; RootMoves[i].pv[j] != MOVE_NONE; j++) + for (size_t j = 0; RootMoves[i].pv[j] != MOVE_NONE; ++j) s << " " << move_to_uci(RootMoves[i].pv[j], pos.is_chess960()); } @@ -1567,7 +1539,7 @@ split_point_start: // At split points actual search starts from here void RootMove::extract_pv_from_tt(Position& pos) { - StateInfo state[MAX_PLY_PLUS_2], *st = state; + StateInfo state[MAX_PLY_PLUS_6], *st = state; const TTEntry* tte; int ply = 0; Move m = pv[0]; @@ -1583,8 +1555,8 @@ void RootMove::extract_pv_from_tt(Position& pos) { tte = TT.probe(pos.key()); } while ( tte - && pos.is_pseudo_legal(m = tte->move()) // Local copy, TT could change - && pos.pl_move_is_legal(m, pos.pinned_pieces()) + && pos.pseudo_legal(m = tte->move()) // Local copy, TT could change + && pos.legal(m, pos.pinned_pieces()) && ply < MAX_PLY && (!pos.is_draw() || ply < 2)); @@ -1600,7 +1572,7 @@ void RootMove::extract_pv_from_tt(Position& pos) { void RootMove::insert_pv_in_tt(Position& pos) { - StateInfo state[MAX_PLY_PLUS_2], *st = state; + StateInfo state[MAX_PLY_PLUS_6], *st = state; const TTEntry* tte; int ply = 0; @@ -1670,14 +1642,15 @@ void Thread::idle_loop() { Threads.mutex.lock(); assert(searching); + assert(activeSplitPoint); SplitPoint* sp = activeSplitPoint; Threads.mutex.unlock(); - Stack stack[MAX_PLY_PLUS_2], *ss = stack+1; // To allow referencing (ss-1) + Stack stack[MAX_PLY_PLUS_6], *ss = stack+2; // To allow referencing (ss-2) Position pos(*sp->pos, this); - memcpy(ss-1, sp->ss-1, 4 * sizeof(Stack)); + std::memcpy(ss-2, sp->ss-2, 5 * sizeof(Stack)); ss->splitPoint = sp; sp->mutex.lock(); @@ -1764,8 +1737,8 @@ void check_time() { // Loop across all split points and sum accumulated SplitPoint nodes plus // all the currently active positions nodes. - for (size_t i = 0; i < Threads.size(); i++) - for (int j = 0; j < Threads[i]->splitPointsSize; j++) + for (size_t i = 0; i < Threads.size(); ++i) + for (int j = 0; j < Threads[i]->splitPointsSize; ++j) { SplitPoint& sp = Threads[i]->splitPoints[j];