Permalink
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
Cannot retrieve contributors at this time.
Cannot retrieve contributors at this time
| /* | |
| MIT License | |
| Copyright (c) 2018 Manik Charan | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all | |
| copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| SOFTWARE. | |
| */ | |
| #include <thread> | |
| #include <atomic> | |
| #include "syzygy/tbprobe.h" | |
| #include "controller.h" | |
| #include "evaluate.h" | |
| #include "position.h" | |
| #include "options.h" | |
| #include "move.h" | |
| #include "utils.h" | |
| #include "uci.h" | |
| #include "tt.h" | |
| // Statistics | |
| STATS( | |
| std::atomic<u64> beta_cutoffs; | |
| std::atomic<u64> first_beta_cutoffs; | |
| std::atomic<u64> all_nodes; | |
| std::atomic<u64> search_nodes; | |
| ) | |
| namespace thread | |
| { | |
| volatile bool stop; | |
| }; | |
| constexpr int MAX_HISTORY_DEPTH = 12; | |
| constexpr int HISTORY_LIMIT = 8000; | |
| constexpr int EQUAL_BOUND = 50; | |
| enum MoveOrder | |
| { | |
| HASH_MOVE = 300000, | |
| HANGING_CAP = 290000, | |
| GOOD_CAP = 280000, | |
| PROM = 270000, | |
| KILLER = 260000, | |
| BAD_CAP = 250000, | |
| }; | |
| struct SearchStack | |
| { | |
| SearchStack() | |
| { | |
| forward_pruning = true; | |
| mlist.reserve(218); | |
| orderlist.reserve(218); | |
| pv.reserve(128); | |
| killer_move[0] = killer_move[1] = 0; | |
| } | |
| int ply; | |
| bool forward_pruning; | |
| Move killer_move[2]; | |
| std::vector<Move> mlist; | |
| std::vector<int> orderlist; | |
| std::vector<Move> pv; | |
| }; | |
| struct SearchGlobals | |
| { | |
| SearchGlobals() | |
| { | |
| reduce_history(true); | |
| nodes_searched = 0; | |
| tb_hits = 0; | |
| } | |
| inline void reduce_history(bool to_zero=false) | |
| { | |
| for (int pt = PAWN; pt <= KING; ++pt) { | |
| for (int sq = 0; sq < 64; ++sq) { | |
| if (to_zero) | |
| history[pt][sq] = 0; | |
| else | |
| history[pt][sq] /= 8; | |
| } | |
| } | |
| } | |
| u64 tb_hits; | |
| u64 nodes_searched; | |
| int history[6][64]; | |
| }; | |
| static SearchStack stacks[MAX_THREADS][MAX_PLY]; | |
| static SearchGlobals globals[MAX_THREADS]; | |
| static std::thread threads[MAX_THREADS]; | |
| static std::pair<int, bool> results[MAX_THREADS]; | |
| inline int value_to_tt(int value, int ply) | |
| { | |
| if (value >= MAX_MATE_VALUE) | |
| value += ply; | |
| else if (value <= -MAX_MATE_VALUE) | |
| value -= ply; | |
| return value; | |
| } | |
| inline int value_from_tt(int value, int ply) | |
| { | |
| if (value >= MAX_MATE_VALUE) | |
| value -= ply; | |
| else if (value <= -MAX_MATE_VALUE) | |
| value += ply; | |
| return value; | |
| } | |
| void reorder_moves(const Position& pos, SearchStack* ss, SearchGlobals& sg, | |
| Move tt_move=0) | |
| { | |
| std::vector<Move>& mlist = ss->mlist; | |
| std::vector<int>& orderlist = ss->orderlist; | |
| orderlist.clear(); | |
| // Fill order vector | |
| for (unsigned i = 0; i < mlist.size(); ++i) { | |
| int order = 0; | |
| Move move = mlist[i]; | |
| if (move == tt_move) | |
| { | |
| order = HASH_MOVE; | |
| } | |
| else if (move & PROMOTION) | |
| { | |
| order = PROM + prom_type(move); | |
| } | |
| else if (move == ss->killer_move[0]) | |
| { | |
| order = KILLER; | |
| } | |
| else if (move == ss->killer_move[1]) | |
| { | |
| order = KILLER - 1; | |
| } | |
| else if (move & CAPTURE_MASK) | |
| { | |
| if (move & ENPASSANT) | |
| { | |
| order = GOOD_CAP + 10 + PAWN; | |
| goto push_order; | |
| } | |
| bool defended = pos.attackers_to(to_sq(move), THEM) > 0; | |
| int cap_val = piece_value[pos.piece_on(to_sq(move))].value(); | |
| int capper_pt = pos.piece_on(from_sq(move)); | |
| if (move & PROM_CAPTURE) | |
| cap_val += piece_value[prom_type(move)].value(); | |
| if (defended) | |
| { | |
| int cap_diff = cap_val - piece_value[capper_pt].value(); | |
| if (cap_diff > EQUAL_BOUND) | |
| order = GOOD_CAP + cap_val - capper_pt; | |
| else if (cap_diff > -EQUAL_BOUND) | |
| order = GOOD_CAP + capper_pt; | |
| else | |
| order = BAD_CAP - capper_pt; | |
| } | |
| else | |
| { | |
| order = HANGING_CAP + cap_val - capper_pt; | |
| } | |
| } | |
| else | |
| { | |
| order = sg.history[pos.piece_on(from_sq(move))][to_sq(move)]; | |
| } | |
| push_order: | |
| orderlist.push_back(order); | |
| // Sort moves using insertion sort | |
| int order_to_shift = orderlist[i]; | |
| Move move_to_shift = mlist[i]; | |
| int j; | |
| for (j = i - 1; j >= 0 && order_to_shift > orderlist[j]; --j) { | |
| orderlist[j+1] = orderlist[j]; | |
| mlist[j+1] = mlist[j]; | |
| } | |
| orderlist[j+1] = order_to_shift; | |
| mlist[j+1] = move_to_shift; | |
| } | |
| assert(mlist.size() == orderlist.size()); | |
| } | |
| int qsearch(Position& pos, SearchStack* const ss, SearchGlobals& sg, | |
| int alpha, int beta) | |
| { | |
| ++sg.nodes_searched; | |
| if (pos.get_half_moves() > 99 || pos.is_repetition()) | |
| return -options::spins["Contempt"].value; | |
| if (!(sg.nodes_searched & 2047) && (stopped() || thread::stop)) | |
| return 0; | |
| if (ss->ply >= MAX_PLY) | |
| return pos.evaluate(); | |
| // Mate distance pruning | |
| alpha = std::max((-MATE + ss->ply), alpha); | |
| beta = std::min((MATE - ss->ply), beta); | |
| if (alpha >= beta) | |
| return alpha; | |
| bool in_check = pos.checkers_to(US); | |
| if (!in_check) | |
| { | |
| int eval = pos.evaluate(); | |
| if (eval >= beta) | |
| return beta; | |
| if (eval > alpha) | |
| alpha = eval; | |
| } | |
| std::vector<Move>& mlist = ss->mlist; | |
| mlist.clear(); | |
| if (in_check) | |
| pos.generate_in_check_movelist(mlist); | |
| else | |
| pos.generate_quiesce_movelist(mlist); | |
| reorder_moves(pos, ss, sg); | |
| int legal_moves = 0; | |
| for (Move move : mlist) { | |
| Position child_pos = pos; | |
| child_pos.make_move(move); | |
| if (child_pos.checkers_to(THEM)) | |
| continue; | |
| ++legal_moves; | |
| int value = -qsearch(child_pos, ss + 1, sg, -beta, -alpha); | |
| if (!(sg.nodes_searched & 2047) && (stopped() || thread::stop)) | |
| return 0; | |
| if (value > alpha) | |
| { | |
| alpha = value; | |
| if (value >= beta) | |
| return beta; | |
| } | |
| } | |
| if (!legal_moves && in_check) | |
| return -MATE + ss->ply; | |
| return alpha; | |
| } | |
| template <bool pv_node> | |
| int search(Position& pos, SearchStack* const ss, SearchGlobals& sg, | |
| int alpha, int beta, int depth) | |
| { | |
| ss->pv.clear(); | |
| if (depth <= 0) | |
| return qsearch(pos, ss, sg, alpha, beta); | |
| ++sg.nodes_searched; | |
| if (pos.get_half_moves() > 99 || pos.is_repetition()) | |
| return -options::spins["Contempt"].value; | |
| if (ss->ply >= MAX_PLY) | |
| return pos.evaluate(); | |
| // Mate distance pruning | |
| alpha = std::max((-MATE + ss->ply), alpha); | |
| beta = std::min((MATE - ss->ply), beta); | |
| if (alpha >= beta) | |
| return alpha; | |
| // Check if time is left | |
| if (!(sg.nodes_searched & 2047) && (stopped() || thread::stop)) | |
| return 0; | |
| // Transposition table probe | |
| bool tt_hit = false; | |
| int tt_score = -INFINITY; | |
| int tt_flag = -1; | |
| Move tt_move = 0; | |
| TTEntry tt_entry = tt.probe(pos.get_hash_key()); | |
| if (tt_entry.get_key() == pos.get_hash_key()) | |
| { | |
| tt_hit = true; | |
| tt_move = tt_entry.get_move(); | |
| tt_score = value_from_tt(tt_entry.get_score(), ss->ply); | |
| tt_flag = tt_entry.get_flag(); | |
| if (!pv_node && tt_entry.get_depth() >= depth) | |
| { | |
| if ( tt_flag == FLAG_EXACT | |
| || (tt_flag == FLAG_LOWER && tt_score >= beta) | |
| || (tt_flag == FLAG_UPPER && tt_score <= alpha)) | |
| { | |
| return tt_score; | |
| } | |
| } | |
| } | |
| // Probe EGTB | |
| // No castling allowed | |
| // No fifty moves allowed | |
| if ( TB_LARGEST > 0 | |
| && !pos.get_castling_rights() | |
| && !pos.get_half_moves() | |
| && popcnt(pos.occupancy_bb()) <= (int)TB_LARGEST) | |
| { | |
| unsigned int wdl = tb_probe_wdl( | |
| pos.color_bb(US), pos.color_bb(THEM), | |
| pos.piece_bb(KING), pos.piece_bb(QUEEN), | |
| pos.piece_bb(ROOK), pos.piece_bb(BISHOP), | |
| pos.piece_bb(KNIGHT), pos.piece_bb(PAWN), pos.get_ep_sq(), | |
| true | |
| ); | |
| if (wdl != TB_RESULT_FAILED) | |
| { | |
| ++sg.tb_hits; | |
| int d = std::min(depth + 6, MAX_PLY - 1); | |
| tt.write(0, FLAG_EXACT, d, tb_values[wdl], pos.get_hash_key()); | |
| return tb_values[wdl]; | |
| } | |
| } | |
| int num_non_pawns = popcnt(pos.color_bb(US) | |
| & ~(pos.piece_bb(KING) ^ pos.piece_bb(PAWN))); | |
| bool in_check = pos.checkers_to(US); | |
| // Calculate position evaluation as static eval if no tt hit, otherwise | |
| // try to use the tt score based on the bound | |
| int static_eval; | |
| if (!pv_node) | |
| { | |
| if (tt_flag == FLAG_EXACT) | |
| { | |
| static_eval = tt_score; | |
| } | |
| else | |
| { | |
| static_eval = pos.evaluate(); | |
| if (tt_hit) | |
| { | |
| if ( (static_eval < tt_score && tt_flag == FLAG_LOWER) | |
| || (static_eval > tt_score && tt_flag == FLAG_UPPER)) | |
| { | |
| static_eval = tt_score; | |
| } | |
| } | |
| } | |
| } | |
| // Forward pruning | |
| if ( !pv_node | |
| && num_non_pawns | |
| && !in_check | |
| && ss->forward_pruning | |
| && beta > -MAX_MATE_VALUE) | |
| { | |
| // Reverse futility pruning | |
| if ( depth < 3 | |
| && static_eval - 200 * depth >= beta) | |
| return static_eval; | |
| // Null move pruning (NMP) | |
| if (depth >= 4 && static_eval >= beta - 100) | |
| { | |
| int reduction = 4; | |
| int depth_left = std::max(1, depth - reduction); | |
| ss[1].forward_pruning = false; | |
| Position child = pos; | |
| child.make_null_move(); | |
| int val = -search<false>(child, ss + 1, sg, -beta, -beta + 1, | |
| depth_left); | |
| ss[1].forward_pruning = true; | |
| // Check if time is left | |
| if (!(sg.nodes_searched & 2047) && (stopped() || thread::stop)) | |
| return 0; | |
| if (val >= beta) | |
| { | |
| if (val >= MAX_MATE_VALUE) | |
| val = beta; | |
| return val; | |
| } | |
| } | |
| } | |
| // Internal iterative deepening | |
| if ( !tt_move | |
| && depth >= 5 | |
| && (pv_node || static_eval + 100 >= beta)) | |
| { | |
| ss->forward_pruning = false; | |
| search<true>(pos, ss, sg, alpha, beta, depth - 2); | |
| ss->forward_pruning = true; | |
| tt_entry = tt.probe(pos.get_hash_key()); | |
| tt_move = tt_entry.get_move(); | |
| } | |
| // Get a pre-allocated movelist | |
| std::vector<Move>& mlist = ss->mlist; | |
| mlist.clear(); | |
| // Populate the movelist | |
| if (in_check) | |
| pos.generate_in_check_movelist(mlist); | |
| else | |
| pos.generate_movelist(mlist); | |
| // Reorder the moves | |
| reorder_moves(pos, ss, sg, tt_move); | |
| // In-check extension | |
| if (in_check) | |
| ++depth; | |
| int old_alpha = alpha; | |
| int best_value = -INFINITY, | |
| legal_moves = 0; | |
| Move best_move = 0; | |
| for (Move move : mlist) { | |
| // Check for legality and make move | |
| Position child_pos = pos; | |
| child_pos.make_move(move); | |
| if (child_pos.checkers_to(THEM)) | |
| continue; | |
| ++legal_moves; | |
| int depth_left = depth - 1; | |
| // Heuristic pruning and reductions | |
| if ( best_value > -MAX_MATE_VALUE | |
| && legal_moves > 1 | |
| && num_non_pawns | |
| && !prom_type(move) | |
| && !cap_type(move) | |
| && !child_pos.checkers_to(US)) | |
| { | |
| // Futility pruning | |
| if ( depth < 8 | |
| && !pv_node | |
| && static_eval + 100 * depth_left <= alpha) | |
| continue; | |
| // Late move reduction (LMR) | |
| if ( depth > 2 | |
| && legal_moves > (pv_node ? 5 : 3) | |
| && move != ss->killer_move[0] | |
| && move != ss->killer_move[1] | |
| && !in_check | |
| && !pos.is_passed_pawn(from_sq(move))) | |
| { | |
| depth_left -= 1 + !pv_node + (legal_moves > 10); | |
| depth_left = std::max(1, depth_left); | |
| } | |
| } | |
| // Principal Variation Search (PVS) | |
| int value; | |
| if (legal_moves == 1) | |
| { | |
| value = -search<pv_node>(child_pos, ss + 1, sg, -beta , -alpha, | |
| depth_left); | |
| } | |
| else | |
| { | |
| value = -search<false>(child_pos, ss + 1, sg, -alpha - 1, -alpha, | |
| depth_left); | |
| if (value > alpha) | |
| value = -search<pv_node>(child_pos, ss + 1, sg, -beta , -alpha, | |
| std::max(depth_left, depth - 1)); | |
| } | |
| // Check if time is left | |
| if (!(sg.nodes_searched & 2047) && (stopped() || thread::stop)) | |
| return 0; | |
| if (value > best_value) | |
| { | |
| best_value = value; | |
| best_move = move; | |
| if (value > alpha) | |
| { | |
| alpha = value; | |
| // Update PV | |
| if (pv_node) | |
| { | |
| ss->pv.clear(); | |
| ss->pv.push_back(move); | |
| ss->pv.insert(ss->pv.end(), ss[1].pv.begin(), ss[1].pv.end()); | |
| } | |
| // Update history | |
| bool quiet_move = !((move & CAPTURE_MASK) || (move & PROMOTION)); | |
| if (quiet_move && depth <= MAX_HISTORY_DEPTH) | |
| { | |
| int pt = pos.piece_on(from_sq(move)); | |
| int to = to_sq(move); | |
| sg.history[pt][to] += depth * depth; | |
| // Reduce history if it overflows | |
| if (sg.history[pt][to] > HISTORY_LIMIT) | |
| sg.reduce_history(); | |
| } | |
| if (value >= beta) | |
| { | |
| STATS( | |
| ++beta_cutoffs; | |
| first_beta_cutoffs += (legal_moves == 1); | |
| ) | |
| // Update killer moves | |
| if (quiet_move && move != ss->killer_move[0]) | |
| { | |
| ss->killer_move[1] = ss->killer_move[0]; | |
| ss->killer_move[0] = move; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Check for checkmate or stalemate | |
| if (!legal_moves) | |
| return pos.checkers_to(US) | |
| ? -MATE + ss->ply | |
| : -options::spins["Contempt"].value; | |
| STATS(++search_nodes;) | |
| STATS(all_nodes += (alpha == old_alpha);) | |
| // Transposition entry flag | |
| u64 flag = best_value >= beta ? FLAG_LOWER | |
| : best_value > old_alpha ? FLAG_EXACT | |
| : FLAG_UPPER; | |
| // Create a tt entry and store it | |
| tt.write(best_move, flag, depth, value_to_tt(best_value, ss->ply), | |
| pos.get_hash_key()); | |
| return best_value; | |
| } | |
| template <bool main_thread> | |
| int search_root(Position& pos, SearchStack* const ss, SearchGlobals& sg, | |
| int alpha, int beta, int depth) | |
| { | |
| ss->pv.clear(); | |
| ++sg.nodes_searched; | |
| // Check if time is left | |
| if (!(sg.nodes_searched & 2047) && (stopped() || thread::stop)) | |
| return 0; | |
| // Transposition table probe | |
| TTEntry tt_entry = tt.probe(pos.get_hash_key()); | |
| Move tt_move = tt_entry.get_key() == pos.get_hash_key() | |
| ? tt_entry.get_move() | |
| : 0; | |
| // Probe EGTB | |
| // No castling allowed | |
| // No fifty moves allowed | |
| if ( TB_LARGEST > 0 | |
| && !pos.get_castling_rights() | |
| && !pos.get_half_moves() | |
| && popcnt(pos.occupancy_bb()) <= (int)TB_LARGEST) | |
| { | |
| unsigned int res = tb_probe_root( | |
| pos.color_bb(US), pos.color_bb(THEM), pos.piece_bb(KING), | |
| pos.piece_bb(QUEEN), pos.piece_bb(ROOK), | |
| pos.piece_bb(BISHOP), pos.piece_bb(KNIGHT), | |
| pos.piece_bb(PAWN), pos.get_half_moves(), pos.get_ep_sq(), | |
| true, nullptr | |
| ); | |
| if (res != TB_RESULT_FAILED) | |
| { | |
| u32 prom = 0; | |
| switch (TB_GET_PROMOTES(res)) { | |
| case TB_PROMOTES_QUEEN: | |
| prom = PROM_TO_QUEEN; | |
| break; | |
| case TB_PROMOTES_KNIGHT: | |
| prom = PROM_TO_KNIGHT; | |
| break; | |
| case TB_PROMOTES_ROOK: | |
| prom = PROM_TO_ROOK; | |
| break; | |
| case TB_PROMOTES_BISHOP: | |
| prom = PROM_TO_BISHOP; | |
| break; | |
| default: | |
| break; | |
| } | |
| int from = TB_GET_FROM(res); | |
| int to = TB_GET_TO(res); | |
| Move move = prom | |
| ? get_move(from, to, PROMOTION, CAP_NONE, prom) | |
| : get_move(from, to, NORMAL); | |
| ss->pv.push_back(move); | |
| controller.stop_search = true; | |
| return tb_values[TB_GET_WDL(res)]; | |
| } | |
| } | |
| // Get a pre-allocated movelist | |
| std::vector<Move>& mlist = ss->mlist; | |
| mlist.clear(); | |
| bool in_check = pos.checkers_to(US); | |
| if (controller.limited_search) | |
| { | |
| std::copy(controller.search_moves.begin(), | |
| controller.search_moves.end(), | |
| std::back_inserter(mlist)); | |
| } | |
| else | |
| { | |
| // Populate the movelist | |
| if (in_check) | |
| pos.generate_in_check_movelist(mlist); | |
| else | |
| pos.generate_movelist(mlist); | |
| } | |
| // Reorder the moves | |
| reorder_moves(pos, ss, sg, tt_move); | |
| // In-check extension | |
| if (in_check) | |
| ++depth; | |
| int old_alpha = alpha; | |
| int best_value = -INFINITY, | |
| legal_moves = 0; | |
| Move best_move = 0; | |
| for (Move move : mlist) { | |
| // Check for legality and make move | |
| Position child_pos = pos; | |
| child_pos.make_move(move); | |
| if (child_pos.checkers_to(THEM)) | |
| continue; | |
| ++legal_moves; | |
| // Print move being searched at root | |
| if (main_thread) | |
| { | |
| controller.nodes_searched = 0; | |
| for (int i = 0; i < options::spins["Threads"].value; ++i) | |
| controller.nodes_searched += globals[i].nodes_searched; | |
| uci::print_currmove(move, legal_moves, controller.start_time, | |
| pos.is_flipped()); | |
| } | |
| int depth_left = depth - 1; | |
| // Principal Variation Search (PVS) | |
| int value; | |
| if (legal_moves == 1) | |
| { | |
| value = -search<true>(child_pos, ss + 1, sg, -beta , -alpha, | |
| depth_left); | |
| } | |
| else | |
| { | |
| value = -search<false>(child_pos, ss + 1, sg, -alpha - 1, -alpha, | |
| depth_left); | |
| if (value > alpha) | |
| value = -search<true>(child_pos, ss + 1, sg, -beta , -alpha, | |
| std::max(depth_left, depth - 1)); | |
| } | |
| // Check if time is left | |
| if (!(sg.nodes_searched & 2047) && (stopped() || thread::stop)) | |
| return 0; | |
| if (value > best_value) | |
| { | |
| best_value = value; | |
| best_move = move; | |
| // Update PV | |
| ss->pv.clear(); | |
| ss->pv.push_back(move); | |
| ss->pv.insert(ss->pv.end(), ss[1].pv.begin(), ss[1].pv.end()); | |
| if (value > alpha) | |
| { | |
| alpha = value; | |
| // Update history | |
| bool quiet_move = !((move & CAPTURE_MASK) || (move & PROMOTION)); | |
| if (quiet_move && depth <= MAX_HISTORY_DEPTH) | |
| { | |
| int pt = pos.piece_on(from_sq(move)); | |
| int to = to_sq(move); | |
| sg.history[pt][to] += depth * depth; | |
| // Reduce history if it overflows | |
| if (sg.history[pt][to] > HISTORY_LIMIT) | |
| sg.reduce_history(); | |
| } | |
| if (value >= beta) | |
| { | |
| // Update killer moves | |
| if (quiet_move && move != ss->killer_move[0]) | |
| { | |
| ss->killer_move[1] = ss->killer_move[0]; | |
| ss->killer_move[0] = move; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Check for checkmate or stalemate | |
| if (!legal_moves) | |
| return pos.checkers_to(US) | |
| ? -MATE + ss->ply | |
| : -options::spins["Contempt"].value; | |
| // Transposition entry flag | |
| u64 flag = best_value >= beta ? FLAG_LOWER | |
| : best_value > old_alpha ? FLAG_EXACT | |
| : FLAG_UPPER; | |
| // Create a tt entry and store it | |
| tt.write(best_move, flag, depth, value_to_tt(best_value, ss->ply), | |
| pos.get_hash_key()); | |
| return best_value; | |
| } | |
| void parallel_search(Position pos, int alpha, int beta, int depth, int threadnum) | |
| { | |
| auto& [value, valid] = results[threadnum]; | |
| auto& sg = globals[threadnum]; | |
| SearchStack* ss = stacks[threadnum]; | |
| valid = false; // Mark as invalid result | |
| // Start parallel search | |
| value = search_root<false>(pos, ss, sg, alpha, beta, depth); | |
| // If this thread finished searching before the others, then thread::stop | |
| // will not be set. Therefore set it to stop all others and mark this | |
| // result as a valid result | |
| if (!thread::stop) | |
| { | |
| thread::stop = true; | |
| valid = true; | |
| } | |
| } | |
| std::pair<Move, Move> Position::best_move() | |
| { | |
| STATS( | |
| all_nodes = 0; | |
| search_nodes = 0; | |
| beta_cutoffs = 0; | |
| first_beta_cutoffs = 0; | |
| ) | |
| constexpr int asp_delta[] = { 10, 30, 50, 100, 200, 300, INFINITY }; | |
| int num_threads = options::spins["Threads"].value; | |
| for (int i = 0; i < num_threads; ++i) { | |
| // Reset stacks | |
| for (int ply = 0; ply < MAX_PLY; ++ply) | |
| stacks[i][ply].ply = ply; | |
| // Reset results | |
| results[i].first = 0; | |
| results[i].second = false; | |
| // Reset globals | |
| globals[i].reduce_history(true); | |
| globals[i].nodes_searched = 0; | |
| globals[i].tb_hits = 0; | |
| } | |
| Move best_move = 0; | |
| Move ponder_move = 0; | |
| int alpha = -INFINITY; | |
| int beta = +INFINITY; | |
| int adelta; | |
| int bdelta; | |
| bool failed; | |
| int result_index; | |
| int score = 0; | |
| for (int depth = 1; depth <= controller.max_ply; ++depth) { | |
| adelta = bdelta = 0; | |
| do { | |
| failed = false; | |
| thread::stop = false; | |
| result_index = 0; // Assume main thread has completed search | |
| // Perform multithreaded search | |
| if (depth > 4) | |
| { | |
| // Start helper threads | |
| for (int i = 1; i < num_threads; ++i) { | |
| threads[i] = std::thread {parallel_search, *this, alpha, | |
| beta, depth + (i & 1), i}; | |
| } | |
| // Start main thread | |
| results[0].first = search_root<true>( | |
| *this, stacks[0], globals[0], alpha, beta, depth | |
| ); | |
| // Stop all threads | |
| thread::stop = true; | |
| // If no other thread has completed searching, main result will | |
| // be used since we started by assuming the main has completed. | |
| // Otherwise, join all threads and use the result of the one | |
| // with the completed search. | |
| for (int i = 1; i < num_threads; ++i) { | |
| threads[i].join(); | |
| // If this thread has completed search, use it | |
| if (results[i].second) | |
| result_index = i; | |
| } | |
| } | |
| // Perform single threaded search | |
| else | |
| { | |
| results[0].first = search_root<true>( | |
| *this, stacks[0], globals[0], alpha, beta, depth | |
| ); | |
| } | |
| // Get the completed search result | |
| score = results[result_index].first; | |
| if (stopped()) | |
| break; | |
| controller.nodes_searched = 0; | |
| controller.tb_hits = 0; | |
| for (int i = 0; i < num_threads; ++i) { | |
| controller.nodes_searched += globals[i].nodes_searched; | |
| controller.tb_hits += globals[i].tb_hits; | |
| } | |
| SearchStack* ss = stacks[result_index]; | |
| time_ms time_passed = utils::curr_time() - controller.start_time; | |
| int bound = score >= beta | |
| ? LOWER_BOUND | |
| : score <= alpha | |
| ? UPPER_BOUND | |
| : EXACT_BOUND; | |
| uci::print_search(score, depth, bound, time_passed, ss->pv, is_flipped()); | |
| STATS( | |
| std::cout << "info string"; | |
| if (beta_cutoffs) | |
| std::cout << " first_beta_cutoff_rate: " << (first_beta_cutoffs / (double)beta_cutoffs); | |
| std::cout << " cut_nodes_rate: " << (beta_cutoffs / (double)search_nodes) | |
| << " all_nodes_rate: " << (all_nodes / (double)search_nodes) | |
| << std::endl; | |
| ) | |
| // Failed low, decrease alpha and repeat | |
| if (score <= alpha) | |
| { | |
| alpha = std::max(score - asp_delta[adelta], -INFINITY); | |
| ++adelta; | |
| failed = true; | |
| } | |
| // Failed high, increase beta and repeat | |
| else if (score >= beta) | |
| { | |
| beta = std::min(score + asp_delta[bdelta], +INFINITY); | |
| ++bdelta; | |
| failed = true; | |
| } | |
| } | |
| while (failed); | |
| if (depth > 1 && stopped()) | |
| break; | |
| SearchStack* ss = stacks[result_index]; | |
| best_move = ss->pv[0]; | |
| ponder_move = 0; | |
| if (depth > 1 && ss->pv.size() > 1) | |
| ponder_move = ss->pv[1]; | |
| // Prepare aspiration window for next time | |
| if (depth > 4) | |
| { | |
| alpha = score - 10; | |
| beta = score + 10; | |
| } | |
| } | |
| // Do not print bestmove during go infinite or ponder | |
| while (controller.analyzing) | |
| continue; | |
| return { best_move, ponder_move }; | |
| } |