X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=7d8f8e87e2e9b54f879399182ccccdb839396c88;hp=1d27dad5e3e96ee9b4f431f2bf5f466e3e44dc51;hb=2940abdac83845aaa35592dc3450556b8b9a9c7f;hpb=ea06200423731c35dc62ed4a40c47e67eac1818a diff --git a/src/search.cpp b/src/search.cpp index 1d27dad5..7d8f8e87 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -28,6 +28,7 @@ #include #include +#include "bitcount.h" #include "book.h" #include "evaluate.h" #include "history.h" @@ -189,12 +190,19 @@ namespace { // Remaining depth: 1 ply 1.5 ply 2 ply 2.5 ply 3 ply 3.5 ply const Value RazorApprMargins[6] = { Value(0x520), Value(0x300), Value(0x300), Value(0x300), Value(0x300), Value(0x300) }; - // The main transposition table - TranspositionTable TT; - /// Variables initialized by UCI options + // Adjustable playing strength + int Slowdown = 0; + const int SlowdownArray[32] = { + 19, 41, 70, 110, 160, 230, 320, 430, 570, 756, 1000, 1300, 1690, 2197, + 2834, 3600, 4573, 5809, 7700, 9863, 12633, 16181, 20726, 26584, 34005, + 43557, 55792, 71463, 91536, 117247, 150180, 192363 + }; + int Strength; + const int MaxStrength = 25; + // Minimum number of full depth (i.e. non-reduced) moves at PV and non-PV nodes int LMRPVMoves, LMRNonPVMoves; // heavy SMP read access for the latter @@ -205,7 +213,7 @@ namespace { const bool UseLSNFiltering = true; const int LSNTime = 4000; // In milliseconds const Value LSNValue = value_from_centipawns(200); - bool looseOnTime = false; + bool loseOnTime = false; // Extensions. Array index 0 is used at non-PV nodes, index 1 at PV nodes. // There is heavy SMP read access on these arrays @@ -281,7 +289,7 @@ namespace { Value qsearch(Position& pos, SearchStack ss[], Value alpha, Value beta, Depth depth, int ply, int threadID); void sp_search(SplitPoint* sp, int threadID); void sp_search_pv(SplitPoint* sp, int threadID); - void init_node(SearchStack ss[], int ply, int threadID); + void init_node(const Position& pos, SearchStack ss[], int ply, int threadID); void update_pv(SearchStack ss[], int ply); void sp_update_pv(SearchStack* pss, SearchStack ss[], int ply); bool connected_moves(const Position& pos, Move m1, Move m2); @@ -294,6 +302,7 @@ namespace { bool ok_to_history(const Position& pos, Move m); void update_history(const Position& pos, Move m, Depth depth, Move movesSearched[], int moveCount); void update_killers(Move m, SearchStack& ss); + void slowdown(const Position& pos); bool fail_high_ply_1(); int current_search_time(); @@ -375,7 +384,7 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, if (button_was_pressed("Clear Hash")) { TT.clear(); - looseOnTime = false; // reset at the beginning of a new game + loseOnTime = false; // reset at the beginning of a new game } bool PonderingEnabled = get_option_value_bool("Ponder"); @@ -414,7 +423,10 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, read_weights(pos.side_to_move()); - int newActiveThreads = get_option_value_int("Threads"); + // Set the number of active threads. If UCI_LimitStrength is enabled, never + // use more than one thread. + int newActiveThreads = + get_option_value_bool("UCI_LimitStrength")? 1 : get_option_value_int("Threads"); if (newActiveThreads != ActiveThreads) { ActiveThreads = newActiveThreads; @@ -427,6 +439,19 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, for (int i = 1; i < ActiveThreads; i++) assert(thread_is_available(i, 0)); + // Set playing strength + if (get_option_value_bool("UCI_LimitStrength")) + { + Strength = (get_option_value_int("UCI_Elo") - 2100) / 25; + Slowdown = + (Strength == MaxStrength)? 0 : SlowdownArray[Max(0, 31-Strength)]; + } + else + { + Strength = MaxStrength; + Slowdown = 0; + } + // Set thinking time int myTime = time[side_to_move]; int myIncrement = increment[side_to_move]; @@ -471,10 +496,16 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, NodesBetweenPolls = Min(MaxNodes, 30000); InfiniteSearch = true; // HACK } + else if (Slowdown) { + if (Slowdown > 50000) NodesBetweenPolls = 30; + else if (Slowdown > 10000) NodesBetweenPolls = 100; + else if (Slowdown > 1000) NodesBetweenPolls = 500; + else if (Slowdown > 100) NodesBetweenPolls = 3000; + else NodesBetweenPolls = 15000; + } else NodesBetweenPolls = 30000; - // Write information to search log file if (UseLogFile) LogFile << "Searching: " << pos.to_fen() << std::endl @@ -488,17 +519,17 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, // We're ready to start thinking. Call the iterative deepening loop function // // FIXME we really need to cleanup all this LSN ugliness - if (!looseOnTime) + if (!loseOnTime) { Value v = id_loop(pos, searchMoves); - looseOnTime = ( UseLSNFiltering + loseOnTime = ( UseLSNFiltering && myTime < LSNTime && myIncrement == 0 && v < -LSNValue); } else { - looseOnTime = false; // reset for next match + loseOnTime = false; // reset for next match while (SearchStartTime + myTime + 1000 > get_system_time()) ; // wait here id_loop(pos, searchMoves); // to fail gracefully @@ -627,6 +658,15 @@ namespace { // searchMoves are verified, copied, scored and sorted RootMoveList rml(p, searchMoves); + // Print RootMoveList c'tor startup scoring to the standard output, + // so that we print information also for iteration 1. + std::cout << "info depth " << 1 << "\ninfo depth " << 1 + << " score " << value_to_string(rml.get_move_score(0)) + << " time " << current_search_time() + << " nodes " << nodes_searched() + << " nps " << nps() + << " pv " << rml.get_move(0) << "\n"; + // Initialize TT.new_search(); H.clear(); @@ -858,8 +898,9 @@ namespace { << " currmovenumber " << i + 1 << std::endl; // Decide search depth for this move + bool moveIsCapture = pos.move_is_capture(move); bool dangerous; - ext = extension(pos, move, true, pos.move_is_capture(move), pos.move_is_check(move), false, false, &dangerous); + ext = extension(pos, move, true, moveIsCapture, pos.move_is_check(move), false, false, &dangerous); newDepth = (Iteration - 2) * OnePly + ext + InitialDepth; // Make the move, and search it @@ -883,15 +924,30 @@ namespace { } else { - value = -search(pos, ss, -alpha, newDepth, 1, true, 0); + if ( newDepth >= 3*OnePly + && i >= MultiPV + LMRPVMoves + && !dangerous + && !moveIsCapture + && !move_is_promotion(move) + && !move_is_castle(move)) + { + ss[0].reduction = OnePly; + value = -search(pos, ss, -alpha, newDepth-OnePly, 1, true, 0); + } else + value = alpha + 1; // Just to trigger next condition + if (value > alpha) { - // Fail high! Set the boolean variable FailHigh to true, and - // re-search the move with a big window. The variable FailHigh is - // used for time managment: We try to avoid aborting the search - // prematurely during a fail high research. - FailHigh = true; - value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0); + value = -search(pos, ss, -alpha, newDepth, 1, true, 0); + if (value > alpha) + { + // Fail high! Set the boolean variable FailHigh to true, and + // re-search the move with a big window. The variable FailHigh is + // used for time managment: We try to avoid aborting the search + // prematurely during a fail high research. + FailHigh = true; + value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0); + } } } @@ -925,6 +981,7 @@ namespace { // Update PV rml.set_move_score(i, value); update_pv(ss, 0); + TT.extract_pv(pos, ss[0].pv); rml.set_move_pv(i, ss[0].pv); if (MultiPV == 1) @@ -938,6 +995,8 @@ namespace { // Print search information to the standard output std::cout << "info depth " << Iteration << " score " << value_to_string(value) + << ((value >= beta)? + " lowerbound" : ((value <= alpha)? " upperbound" : "")) << " time " << current_search_time() << " nodes " << nodes_searched() << " nps " << nps() @@ -1006,7 +1065,7 @@ namespace { // Initialize, and make an early exit in case of an aborted search, // an instant draw, maximum ply reached, etc. - init_node(ss, ply, threadID); + init_node(pos, ss, ply, threadID); // After init_node() that calls poll() if (AbortSearch || thread_should_stop(threadID)) @@ -1041,16 +1100,16 @@ namespace { // Initialize a MovePicker object for the current position, and prepare // to search all moves - MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply]); - Move move, movesSearched[256]; int moveCount = 0; Value value, bestValue = -VALUE_INFINITE; - Bitboard dcCandidates = mp.discovered_check_candidates(); Color us = pos.side_to_move(); bool isCheck = pos.is_check(); bool mateThreat = pos.has_mate_threat(opposite_color(us)); + MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply]); + Bitboard dcCandidates = mp.discovered_check_candidates(); + // Loop through all legal moves until no moves remain or a beta cutoff // occurs. while ( alpha < beta @@ -1059,7 +1118,7 @@ namespace { { assert(move_is_ok(move)); - bool singleReply = (isCheck && mp.number_of_moves() == 1); + bool singleReply = (isCheck && mp.number_of_evasions() == 1); bool moveIsCheck = pos.move_is_check(move, dcCandidates); bool moveIsCapture = pos.move_is_capture(move); @@ -1080,7 +1139,7 @@ namespace { { // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. - if ( depth >= 2*OnePly + if ( depth >= 3*OnePly && moveCount >= LMRPVMoves && !dangerous && !moveIsCapture @@ -1196,7 +1255,7 @@ namespace { // Initialize, and make an early exit in case of an aborted search, // an instant draw, maximum ply reached, etc. - init_node(ss, ply, threadID); + init_node(pos, ss, ply, threadID); // After init_node() that calls poll() if (AbortSearch || thread_should_stop(threadID)) @@ -1249,11 +1308,7 @@ namespace { pos.undo_null_move(); - if (value_is_mate(nullValue)) - { - /* Do not return unproven mates */ - } - else if (nullValue >= beta) + if (nullValue >= beta) { if (depth < 6 * OnePly) return beta; @@ -1320,7 +1375,7 @@ namespace { { assert(move_is_ok(move)); - bool singleReply = (isCheck && mp.number_of_moves() == 1); + bool singleReply = (isCheck && mp.number_of_evasions() == 1); bool moveIsCheck = pos.move_is_check(move, dcCandidates); bool moveIsCapture = pos.move_is_capture(move); @@ -1364,7 +1419,7 @@ namespace { // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. - if ( depth >= 2*OnePly + if ( depth >= 3*OnePly && moveCount >= LMRNonPVMoves && !dangerous && !moveIsCapture @@ -1456,7 +1511,7 @@ namespace { // Initialize, and make an early exit in case of an aborted search, // an instant draw, maximum ply reached, etc. - init_node(ss, ply, threadID); + init_node(pos, ss, ply, threadID); // After init_node() that calls poll() if (AbortSearch || thread_should_stop(threadID)) @@ -1489,10 +1544,9 @@ namespace { if (isCheck) staticValue = -VALUE_INFINITE; - else if (tte && tte->type() == VALUE_TYPE_EVAL) + else if (tte && (tte->type() & VALUE_TYPE_EVAL)) { // Use the cached evaluation score if possible - assert(tte->value() == evaluate(pos, ei, threadID)); assert(ei.futilityMargin == Value(0)); staticValue = tte->value(); @@ -1511,7 +1565,7 @@ namespace { { // Store the score to avoid a future costly evaluation() call if (!isCheck && !tte && ei.futilityMargin == 0) - TT.store(pos.get_key(), value_to_tt(bestValue, ply), VALUE_TYPE_EVAL, Depth(-127*OnePly), MOVE_NONE); + TT.store(pos.get_key(), value_to_tt(bestValue, ply), VALUE_TYPE_EV_LO, Depth(-127*OnePly), MOVE_NONE); return bestValue; } @@ -1599,9 +1653,13 @@ namespace { Move m = ss[ply].pv[ply]; if (!pvNode) { + // If bestValue isn't changed it means it is still the static evaluation of + // the node, so keep this info to avoid a future costly evaluation() call. + ValueType type = (bestValue == staticValue && !ei.futilityMargin ? VALUE_TYPE_EV_UP : VALUE_TYPE_UPPER); Depth d = (depth == Depth(0) ? Depth(0) : Depth(-1)); + if (bestValue < beta) - TT.store(pos.get_key(), value_to_tt(bestValue, ply), VALUE_TYPE_UPPER, d, MOVE_NONE); + TT.store(pos.get_key(), value_to_tt(bestValue, ply), type, d, MOVE_NONE); else TT.store(pos.get_key(), value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, d, m); } @@ -1917,15 +1975,15 @@ namespace { bool includeAllMoves = (searchMoves[0] == MOVE_NONE); // Generate all legal moves - int lm_count = generate_legal_moves(pos, mlist); + MoveStack* last = generate_moves(pos, mlist); // Add each move to the moves[] array - for (int i = 0; i < lm_count; i++) + for (MoveStack* cur = mlist; cur != last; cur++) { bool includeMove = includeAllMoves; for (int k = 0; !includeMove && searchMoves[k] != MOVE_NONE; k++) - includeMove = (searchMoves[k] == mlist[i].move); + includeMove = (searchMoves[k] == cur->move); if (!includeMove) continue; @@ -1934,7 +1992,7 @@ namespace { StateInfo st; SearchStack ss[PLY_MAX_PLUS_2]; - moves[count].move = mlist[i].move; + moves[count].move = cur->move; pos.do_move(moves[count].move, st); moves[count].score = -qsearch(pos, ss, -VALUE_INFINITE, VALUE_INFINITE, Depth(0), 1, 0); pos.undo_move(moves[count].move); @@ -2044,11 +2102,14 @@ namespace { // NodesBetweenPolls nodes, init_node() also calls poll(), which polls // for user input and checks whether it is time to stop the search. - void init_node(SearchStack ss[], int ply, int threadID) { + void init_node(const Position& pos, SearchStack ss[], int ply, int threadID) { assert(ply >= 0 && ply < PLY_MAX); assert(threadID >= 0 && threadID < ActiveThreads); + if (Slowdown && Iteration >= 3) + slowdown(pos); + Threads[threadID].nodes++; if (threadID == 0) @@ -2105,7 +2166,9 @@ namespace { // the second move is assumed to be a move from the current position. bool connected_moves(const Position& pos, Move m1, Move m2) { + Square f1, t1, f2, t2; + Piece p; assert(move_is_ok(m1)); assert(move_is_ok(m2)); @@ -2131,31 +2194,32 @@ namespace { return true; // Case 4: The destination square for m2 is attacked by the moving piece in m1 - if (pos.piece_attacks_square(pos.piece_on(t1), t1, t2)) + p = pos.piece_on(t1); + if (bit_is_set(pos.attacks_from(p, t1), t2)) return true; // Case 5: Discovered check, checking piece is the piece moved in m1 - if ( piece_is_slider(pos.piece_on(t1)) + if ( piece_is_slider(p) && bit_is_set(squares_between(t1, pos.king_square(pos.side_to_move())), f2) - && !bit_is_set(squares_between(t2, pos.king_square(pos.side_to_move())), t2)) + && !bit_is_set(squares_between(t1, pos.king_square(pos.side_to_move())), t2)) { Bitboard occ = pos.occupied_squares(); Color us = pos.side_to_move(); Square ksq = pos.king_square(us); clear_bit(&occ, f2); - if (pos.type_of_piece_on(t1) == BISHOP) + if (type_of_piece(p) == BISHOP) { if (bit_is_set(bishop_attacks_bb(ksq, occ), t1)) return true; } - else if (pos.type_of_piece_on(t1) == ROOK) + else if (type_of_piece(p) == ROOK) { if (bit_is_set(rook_attacks_bb(ksq, occ), t1)) return true; } else { - assert(pos.type_of_piece_on(t1) == QUEEN); + assert(type_of_piece(p) == QUEEN); if (bit_is_set(queen_attacks_bb(ksq, occ), t1)) return true; } @@ -2203,25 +2267,29 @@ namespace { assert(m != MOVE_NONE); Depth result = Depth(0); - *dangerous = check || singleReply || mateThreat; + *dangerous = check | singleReply | mateThreat; - if (check) - result += CheckExtension[pvNode]; + if (*dangerous) + { + if (check) + result += CheckExtension[pvNode]; - if (singleReply) - result += SingleReplyExtension[pvNode]; + if (singleReply) + result += SingleReplyExtension[pvNode]; - if (mateThreat) - result += MateThreatExtension[pvNode]; + if (mateThreat) + result += MateThreatExtension[pvNode]; + } if (pos.type_of_piece_on(move_from(m)) == PAWN) { - if (pos.move_is_pawn_push_to_7th(m)) + Color c = pos.side_to_move(); + if (relative_rank(c, move_to(m)) == RANK_7) { result += PawnPushTo7thExtension[pvNode]; *dangerous = true; } - if (pos.move_is_passed_pawn_push(m)) + if (pos.pawn_is_passed(c, move_to(m))) { result += PassedPawnExtension[pvNode]; *dangerous = true; @@ -2378,6 +2446,22 @@ namespace { ss.killers[0] = m; } + + // slowdown() simply wastes CPU cycles doing nothing useful. It's used + // in strength handicap mode. + + void slowdown(const Position &pos) { + int i, n; + n = Slowdown; + for (i = 0; i < n; i++) { + Square s = Square(i&63); + if (count_1s(pos.attackers_to(s)) > 63) + std::cout << "This can't happen, but I put this string here anyway, in order to " + "prevent the compiler from optimizing away the useless computation." << std::endl; + } + } + + // fail_high_ply_1() checks if some thread is currently resolving a fail // high at ply 1 at the node below the first root node. This information // is used for time managment.