static int full_search(board_t * board, int alpha, int beta, int depth, int height, mv_t pv[], int node_type) { bool in_check; bool single_reply; bool mate_threat; int trans_move, trans_depth, trans_min_depth, trans_max_depth, trans_min_value, trans_max_value; int min_value, max_value; int old_alpha; int value, best_value; int move, best_move; int new_depth; int played_nb; int i; int opt_value; bool reduced; int mb; attack_t attack[1]; sort_t sort[1]; undo_t undo[1]; mv_t new_pv[HeightMax]; mv_t played[256]; int FutilityMargin; ASSERT(board!=NULL); ASSERT(range_is_ok(alpha,beta)); ASSERT(depth_is_ok(depth)); ASSERT(height_is_ok(height)); ASSERT(pv!=NULL); ASSERT(node_type==NodePV||node_type==NodeCut||node_type==NodeAll); ASSERT(board_is_legal(board)); // horizon? if (depth <= 0){ if (node_type == NodePV) CheckDepth = 1 - CheckNb - 1; else CheckDepth = 1 - CheckNb; return full_quiescence(board,alpha,beta,0,height,pv); } // init SearchStack[height].best_move = MoveNone; SearchStack[height].move = MoveNone; SearchStack[height].threat_move = MoveNone; SearchStack[height].reduced = false; mate_threat = false; SearchCurrent->node_nb++; SearchInfo->check_nb--; PV_CLEAR(pv); if (height > SearchCurrent->max_depth) SearchCurrent->max_depth = height; if (SearchInfo->check_nb <= 0) { SearchInfo->check_nb += SearchInfo->check_inc; search_check(); } // draw? if (board_is_repetition(board) || recog_draw(board)) return ValueDraw; // mate-distance pruning if (UseDistancePruning) { // lower bound value = VALUE_MATE(height+2); // does not work if the current position is mate if (value > alpha && board_is_mate(board)) value = VALUE_MATE(height); if (value > alpha) { alpha = value; if (value >= beta) return value; } // upper bound value = -VALUE_MATE(height+1); if (value < beta) { beta = value; if (value <= alpha) return value; } } // transposition table trans_move = MoveNone; if (UseTrans && depth >= TransDepth) { if (trans_retrieve(Trans,board->key,&trans_move,&trans_min_depth,&trans_max_depth,&trans_min_value,&trans_max_value)) { // trans_move is now updated if (node_type != NodePV) { if (UseMateValues) { if (trans_min_value > +ValueEvalInf && trans_min_depth < depth) { trans_min_depth = depth; } if (trans_max_value < -ValueEvalInf && trans_max_depth < depth) { trans_max_depth = depth; } } min_value = -ValueInf; if (DEPTH_MATCH(trans_min_depth,depth)) { min_value = value_from_trans(trans_min_value,height); if (min_value >= beta) return min_value; } max_value = +ValueInf; if (DEPTH_MATCH(trans_max_depth,depth)) { max_value = value_from_trans(trans_max_value,height); if (max_value <= alpha) return max_value; } if (min_value == max_value) return min_value; // exact match } } } // height limit if (height >= HeightMax-1) return eval(board, alpha, beta); // more init old_alpha = alpha; best_value = ValueNone; best_move = MoveNone; played_nb = 0; attack_set(attack,board); in_check = ATTACK_IN_CHECK(attack); // null-move pruning if (UseNull && depth >= NullDepth && node_type != NodePV) { if (!in_check && !value_is_mate(beta) && do_null(board) && (!UseNullEval || depth <= NullReduction+1 || eval(board,alpha, beta) >= beta)) { // null-move search new_depth = depth - NullReduction - 1; //new_depth = depth - R_adpt(board->piece_size[board->turn]+board->pawn_size[board->turn],depth,NullReduction) - 1; move_do_null(board,undo); value = -full_search(board,-beta,-beta+1,new_depth,height+1,new_pv,NODE_OPP(node_type)); move_undo_null(board,undo); // verification search if (UseVer && depth > VerReduction) { if (value >= beta && (!UseVerEndgame || do_ver(board))) { new_depth = depth - VerReduction; ASSERT(new_depth>0); value = full_no_null(board,alpha,beta,new_depth,height,new_pv,NodeCut,trans_move,&move); if (value >= beta) { ASSERT(move==new_pv[0]); played[played_nb++] = move; best_move = move; SearchStack[height].move = move; SearchStack[height].best_move = move; best_value = value; pv_copy(pv,new_pv); goto cut; } } } // pruning if (value >= beta) { if (value > +ValueEvalInf) value = +ValueEvalInf; // do not return unproven mates ASSERT(!value_is_mate(value)); // pv_cat(pv,new_pv,MoveNull); best_move = MoveNone; best_value = value; goto cut; } SearchStack[height].threat_move = SearchStack[height+1].best_move; /* if (SearchStack[height-1].reduced){ // Idea by Tord Romstad if (MOVE_FROM(SearchStack[height+1].best_move) == MOVE_TO(SearchStack[height-1].move)) return alpha-1; */ /* if(((MOVE_TO(SearchStack[height - 2].threat_move) == MOVE_FROM(SearchStack[height - 2].move)) && (MOVE_TO(SearchStack[height].threat_move) == MOVE_TO(SearchStack[height - 2].move))) || ((MOVE_TO(SearchStack[height - 2].threat_move) != MOVE_FROM(SearchStack[height - 2].move)) && (MOVE_TO(SearchStack[height].threat_move) == MOVE_TO(SearchStack[height - 2].threat_move)))) return alpha-1; */ // } } } // mate threat /* mate_threat = false; if (value <= VALUE_MATE(height+2)){ mate_threat = true; } */ // Internal Iterative Deepening if (UseIID && depth >= IIDDepth && node_type == NodePV && trans_move == MoveNone) { // new_depth = depth - IIDReduction; new_depth = MIN(depth - IIDReduction,depth/2); ASSERT(new_depth>0); value = full_search(board,alpha,beta,new_depth,height,new_pv,node_type); if (value <= alpha) value = full_search(board,-ValueInf,beta,new_depth,height,new_pv,node_type); trans_move = new_pv[0]; } // move generation sort_init(sort,board,attack,depth,height,trans_move); single_reply = false; if (in_check && LIST_SIZE(sort->list) == 1) single_reply = true; // HACK // move loop opt_value = +ValueInf; while ((move=sort_next(sort)) != MoveNone) { SearchStack[height].move = move; // history_tried(move,board); // extensions new_depth = full_new_depth(depth,move,board,single_reply,mate_threat,node_type==NodePV, height); // futility pruning if (UseFutility && depth <= 2 && node_type != NodePV) { if (!in_check && new_depth < depth && !move_is_tactical(move,board) && !move_is_dangerous(move,board)) { ASSERT(!move_is_check(move,board)); // optimistic evaluation if (opt_value == +ValueInf) { if (depth==2){ FutilityMargin = FutilityMargin2; } else{ FutilityMargin = FutilityMargin1; } opt_value = eval(board,alpha,beta) + FutilityMargin; ASSERT(opt_value<+ValueInf); } value = opt_value; // pruning if (value <= alpha) { if (value > best_value) { best_value = value; PV_CLEAR(pv); } continue; } } } // history pruning /* reduced = false; if (UseHistory && depth >= HistoryDepth && node_type != NodePV) { if (!in_check && played_nb >= HistoryMoveNb && new_depth < depth) { ASSERT(best_value!=ValueNone); ASSERT(played_nb>0); ASSERT(sort->pos>0&&move==LIST_MOVE(sort->list,sort->pos-1)); if (history_reduction(move,board) == true) { ASSERT(value>=0&&value<16384); ASSERT(move!=trans_move); ASSERT(!move_is_tactical(move,board)); ASSERT(!move_is_check(move,board)); new_depth--; reduced = true; } } } */ // history pruning reduced = false; value = sort->value; // history score if (!in_check && depth < SearchCurrent->max_extensions / 2 && node_type != NodePV && new_depth < depth && value < HistoryValue / depth) continue; if (UseHistory && depth >= HistoryDepth && node_type != NodePV) { if (!in_check && played_nb >= HistoryMoveNb && new_depth < depth) { ASSERT(best_value!=ValueNone); ASSERT(played_nb>0); ASSERT(sort->pos>0&&move==LIST_MOVE(sort->list,sort->pos-1)); value = sort->value; // history score if (value < HistoryValue) { ASSERT(value>=0&&value<16384); ASSERT(move!=trans_move); ASSERT(!move_is_tactical(move,board)); ASSERT(!move_is_check(move,board)); new_depth--; reduced = true; if (UseExtendedHistory && value < HistoryValue / 2 && depth >= 8){ new_depth--; } } } } SearchStack[height].reduced = reduced; // recursive search move_do(board,move,undo); if (node_type != NodePV || best_value == ValueNone) { // first move value = -full_search(board,-beta,-alpha,new_depth,height+1,new_pv,NODE_OPP(node_type)); } else { // other moves value = -full_search(board,-alpha-1,-alpha,new_depth,height+1,new_pv,NodeCut); if (value > alpha) { // && value < beta value = -full_search(board,-beta,-alpha,new_depth,height+1,new_pv,NodePV); } } // history-pruning re-search if (HistoryReSearch && reduced && value > alpha /* was >= beta */) { ASSERT(node_type!=NodePV); //history_very_bad(move,board); SearchStack[height].reduced = false; new_depth++; ASSERT(new_depth==depth-1); value = -full_search(board,-beta,-alpha,new_depth,height+1,new_pv,NODE_OPP(node_type)); } move_undo(board,move,undo); played[played_nb++] = move; if (value > best_value) { best_value = value; pv_cat(pv,new_pv,move); if (value > alpha) { alpha = value; best_move = move; SearchStack[height].best_move = move; if (value >= beta){ // history_success(move,board); goto cut; } } } if (node_type == NodeCut) node_type = NodeAll; } // ALL node if (best_value == ValueNone) { // no legal move if (in_check) { ASSERT(board_is_mate(board)); return VALUE_MATE(height); } else { ASSERT(board_is_stalemate(board)); return ValueDraw; } } cut: ASSERT(value_is_ok(best_value)); // move ordering if (best_move != MoveNone) { good_move(best_move,board,depth,height); if (best_value >= beta && !move_is_tactical(best_move,board)) { ASSERT(played_nb>0&&played[played_nb-1]==best_move); for (i = 0; i < played_nb-1; i++) { move = played[i]; ASSERT(move!=best_move); history_bad(move,board); } history_good(best_move,board); } } // transposition table if (UseTrans && depth >= TransDepth) { trans_move = best_move; trans_depth = depth; trans_min_value = (best_value > old_alpha) ? value_to_trans(best_value,height) : -ValueInf; trans_max_value = (best_value < beta) ? value_to_trans(best_value,height) : +ValueInf; trans_store(Trans,board->key,trans_move,trans_depth,trans_min_value,trans_max_value); } return best_value; }
static int full_search(board_t * board, int alpha, int beta, int depth, int height, mv_t pv[], int node_type, bool extended, int ThreadId) { bool in_check; bool single_reply; bool good_cap; int trans_move, trans_depth, trans_flags, trans_value; int old_alpha; int value, best_value; int move, best_move; int new_depth; int played_nb; int i; int opt_value; bool reduced, cap_extended; attack_t attack[1]; sort_t sort[1]; undo_t undo[1]; mv_t new_pv[HeightMax]; mv_t played[256]; int FutilityMargin; int probe_score, probe_depth; int newHistoryValue; int threshold; int reduction; int last_move; int quiet_move_count; entry_t * found_entry; ASSERT(board!=NULL); ASSERT(range_is_ok(alpha,beta)); ASSERT(depth_is_ok(depth)); ASSERT(height_is_ok(height)); ASSERT(pv!=NULL); ASSERT(node_type==NodePV||node_type==NodeCut||node_type==NodeAll); ASSERT(board_is_legal(board)); // horizon? if (depth <= 0){ if (node_type == NodePV) SearchCurrent[ThreadId]->CheckDepth = 1 - SearchCurrent[ThreadId]->CheckNb - 1; else SearchCurrent[ThreadId]->CheckDepth = 1 - SearchCurrent[ThreadId]->CheckNb; return full_quiescence(board,alpha,beta,0,height,pv,ThreadId); } // init SearchCurrent[ThreadId]->node_nb++; SearchInfo[ThreadId]->check_nb--; PV_CLEAR(pv); if (height > SearchCurrent[ThreadId]->max_depth) SearchCurrent[ThreadId]->max_depth = height; if (SearchInfo[ThreadId]->check_nb <= 0) { SearchInfo[ThreadId]->check_nb += SearchInfo[ThreadId]->check_inc; search_check(ThreadId); } // draw? if (board_is_repetition(board)) return ValueDraw; /* Interior node recognizer from scorpio by Daniel Shawul -> dont probe at the leaves as this will slow down search For 4/3 pieces probe there also. -> After captures and pawn moves assume exact score and cutoff tree, because we are making progress. Note this is not done only for speed. -> if we are far from root (depth / 2), assume exact score and cutoff tree */ // if (egbb_is_loaded && board->piece_nb <= 5){ // probe_depth = (2 * SearchCurrent[ThreadId]->act_iteration) / 3; // if ((board->piece_nb <=4 || height <= probe_depth) // && (height >= probe_depth // || board->cap_sq != SquareNone || PIECE_IS_PAWN(board->moving_piece)) // ) { // // if (probe_bitbases(board, probe_score)){ // if (probe_score < 0 && board_is_mate(board)) return VALUE_MATE(height); // probe_score = value_from_trans(probe_score,height); // /* trans_move = MoveNone; // trans_depth = depth; // trans_flags = TransUnknown; // if (probe_score > alpha) trans_flags |= TransLower; // if (probe_score < beta) trans_flags |= TransUpper; // trans_store(Trans,board->key,trans_move,trans_depth,trans_flags,probe_score);*/ // return probe_score; // } // } // } if (recog_draw(board,ThreadId)) return ValueDraw; // mate-distance pruning if (UseDistancePruning) { // lower bound value = VALUE_MATE(height+2); // does not work if the current position is mate if (value > alpha && board_is_mate(board)) value = VALUE_MATE(height); if (value > alpha) { alpha = value; if (value >= beta) return value; } // upper bound value = -VALUE_MATE(height+1); if (value < beta) { beta = value; if (value <= alpha) return value; } } // transposition table trans_move = MoveNone; if (UseTrans && depth >= TransDepth) { if (trans_retrieve(Trans,&found_entry,board->key,&trans_move,&trans_depth,&trans_flags,&trans_value)) { // trans_move is now updated /* if (found_entry->depth != trans_depth){ found_entry->depth = trans_depth; }*/ if (node_type != NodePV /*|| ThreadId > 0*/) { if (UseMateValues) { if (trans_depth < depth) { if (trans_value < -ValueEvalInf && TRANS_IS_UPPER(trans_flags)) { trans_depth = depth; trans_flags = TransUpper; } else if (trans_value > +ValueEvalInf && TRANS_IS_LOWER(trans_flags)) { trans_depth = depth; trans_flags = TransLower; } } if (trans_depth >= depth) { trans_value = value_from_trans(trans_value,height); if ((UseExact && TRANS_IS_EXACT(trans_flags)) || (TRANS_IS_LOWER(trans_flags) && trans_value >= beta) || (TRANS_IS_UPPER(trans_flags) && trans_value <= alpha)) { return trans_value; } /* if (TRANS_IS_EXACT(trans_flags)) return trans_value; if (TRANS_IS_LOWER(trans_flags)) { if (trans_value >= beta) return trans_value; if (trans_value > alpha) alpha = trans_value; } if (TRANS_IS_UPPER(trans_flags)) { if (trans_value <= alpha) return trans_value; if (trans_value < beta) beta = trans_value; } */ } } } } } // height limit if (height >= HeightMax-1) return eval(board, alpha, beta, ThreadId); // more init old_alpha = alpha; best_value = ValueNone; best_move = MoveNone; played_nb = 0; last_move = SearchCurrent[ThreadId]->last_move; attack_set(attack,board); in_check = ATTACK_IN_CHECK(attack); // null-move pruning if (UseNull && depth >= NullDepth && node_type != NodePV && !(trans_move != MoveNone && TRANS_IS_UPPER(trans_flags) && trans_depth >= depth - NullReduction - depth/4 && trans_value < beta)) { // if hash table tells us we are < beta, the null move search probably wastes time // bugfix 25-3-2013 (trans_move condition added) was LOS ~90%, + 10 elo. Probably not nearly that good but it's a bugfix...) if (!in_check && !value_is_mate(beta) && do_null(board) && (!UseNullEval || depth <= NullReduction+1 || eval(board,alpha, beta, ThreadId) >= beta)) { // null-move search // if (depth < 11) new_depth = depth - NullReduction - depth/4; // JD: from Stockfish, 10 elo better // else // new_depth = depth - NullReduction - 2; //new_depth = depth - R_adpt(board->piece_size[board->turn]+board->pawn_size[board->turn],depth,NullReduction) - 1; move_do_null(board,undo); SearchCurrent[ThreadId]->last_move = MoveNone; // refutation table value = -full_search(board,-beta,-beta+1,new_depth,height+1,new_pv,NODE_OPP(node_type),false,ThreadId); move_undo_null(board,undo); // pruning if (value >= beta) { if (value > +ValueEvalInf) value = +ValueEvalInf; // do not return unproven mates ASSERT(!value_is_mate(value)); // pv_cat(pv,new_pv,MoveNull); best_move = MoveNone; best_value = value; goto cut; } } } // Razoring: idea by Tord Romstad (Glaurung), fixed by Jerry Donald if (node_type != NodePV && !in_check && trans_move == MoveNone && depth <= 3){ threshold = beta - RazorMargin - (depth-1)*39; // Values from Protector (Raimund Heid) if (eval(board,threshold-1, threshold, ThreadId) < threshold){ value = full_quiescence(board,threshold-1,threshold,0,height,pv,ThreadId); if (value < threshold) // corrected - was < beta which is too risky at depth > 1 return value; } } // Internal Iterative Deepening if (UseIID && depth >= IIDDepth && node_type == NodePV && trans_move == MoveNone) { new_depth = MIN(depth - IIDReduction,depth/2); ASSERT(new_depth>0); value = full_search(board,alpha,beta,new_depth,height,new_pv,node_type,false,ThreadId); if (value <= alpha) value = full_search(board,-ValueInf,beta,new_depth,height,new_pv,node_type,false,ThreadId); trans_move = new_pv[0]; } // move generation sort_init(sort,board,attack,depth,height,trans_move,last_move,ThreadId); single_reply = false; if (in_check && LIST_SIZE(sort->list) == 1) single_reply = true; // HACK // move loop opt_value = +ValueInf; good_cap = true; quiet_move_count = 0; while ((move=sort_next(sort,ThreadId)) != MoveNone) { // extensions new_depth = full_new_depth(depth,move,board,single_reply,node_type==NodePV, height, extended, &cap_extended, ThreadId); // history pruning value = sort->value; // history score if (!in_check && depth <= 6 && node_type != NodePV && new_depth < depth && value < 2 * HistoryValue / (depth + depth % 2) /*2*/ && played_nb >= 1+depth && !move_is_dangerous(move,board)){ continue; } // futility pruning if (UseFutility && node_type != NodePV && depth <= 5) { if (!in_check && new_depth < depth&& !move_is_tactical(move,board) && !move_is_dangerous(move,board)) { ASSERT(!move_is_check(move,board)); // move count based pruning (added by Jerry Donald: ~+20 elo!) if (quiet_move_count >= MoveCountLimit[depth])continue; quiet_move_count++; // optimistic evaluation (Chris Formula) if (opt_value == +ValueInf) { if (depth>=2){ FutilityMargin = FutilityMargin2 + (depth % 2) * 100; } else{ FutilityMargin = FutilityMargin1; } opt_value = eval(board,alpha-FutilityMargin,alpha,ThreadId) + FutilityMargin; ASSERT(opt_value<+ValueInf); } value = opt_value; // pruning if (value <= alpha) { if (value > best_value) { best_value = value; PV_CLEAR(pv); } continue; } } } // Late Move Reductions // init reduced = false; reduction = 0; // lookup reduction if (UseHistory) { if (!in_check && new_depth < depth && played_nb >= HistoryMoveNb && depth >= HistoryDepth && !move_is_dangerous(move,board)) { if (good_cap && !move_is_tactical(move,board)){ good_cap = false; } if (!good_cap){ reduction = (node_type == NodePV ? quietPvMoveReduction[depth<64 ? depth: 63][played_nb<64? played_nb: 63]: quietMoveReduction[depth<64 ? depth: 63][played_nb<64? played_nb: 63]); // reduced bad captures less if (move_is_tactical(move,board)) reduction = reduction / 2; // bad captures // set reduction flag if (reduction > 0) reduced = true; } } } // recursive search move_do(board,move,undo); SearchCurrent[ThreadId]->last_move = move; if (node_type != NodePV || best_value == ValueNone) { // first move or non-pv value = -full_search(board,-beta,-alpha,new_depth-reduction,height+1,new_pv,NODE_OPP(node_type),cap_extended,ThreadId); // The move was reduced and fails high; so we research with full depth if (reduced && value >= beta){ value = -full_search(board,-beta,-alpha,new_depth,height+1,new_pv,NODE_OPP(node_type),cap_extended,ThreadId); } } else { // other moves (all PV children) value = -full_search(board,-alpha-1,-alpha,new_depth-reduction,height+1,new_pv,NodeCut,cap_extended,ThreadId); // In case of fail high: // If reduced then we try a research with node_type = NodePV if (value > alpha && reduced){ // && value < beta value = -full_search(board,-beta,-alpha,new_depth-reduction,height+1,new_pv,NodePV,cap_extended,ThreadId); // Still fails high! We research to full depth if (reduced && value >= beta){ value = -full_search(board,-beta,-alpha,new_depth,height+1,new_pv,NodePV,cap_extended,ThreadId); } // If not reduced we research as a PV node } else if (value > alpha){ value = -full_search(board,-beta,-alpha,new_depth,height+1,new_pv,NodePV,cap_extended,ThreadId); } } move_undo(board,move,undo); played[played_nb++] = move; if (value > best_value) { best_value = value; pv_cat(pv,new_pv,move); if (value > alpha) { alpha = value; best_move = move; if (value >= beta){ goto cut; } } } if (node_type == NodeCut) node_type = NodeAll; } // ALL node if (best_value == ValueNone) { // no legal move if (in_check) { ASSERT(board_is_mate(board)); return VALUE_MATE(height); } else { ASSERT(board_is_stalemate(board)); return ValueDraw; } } cut: ASSERT(value_is_ok(best_value)); // move ordering if (best_move != MoveNone) { good_move(best_move,board,depth,height,ThreadId); if (best_value >= beta && !move_is_tactical(best_move,board)) { // check is 204b and d, e // refutation table ~5 elo if (last_move != MoveNull && last_move != MoveNone)refutation_update(best_move, last_move, board, ThreadId); ASSERT(played_nb>0&&played[played_nb-1]==best_move); for (i = 0; i < played_nb-1; i++) { move = played[i]; ASSERT(move!=best_move); history_bad(move,board,ThreadId); bad_move(move,board,depth,height,ThreadId); // added JD ~ 5 elo } history_good(best_move,board,ThreadId); } } // transposition table if (UseTrans && depth >= TransDepth) { trans_move = best_move; trans_depth = depth; trans_flags = TransUnknown; if (best_value > old_alpha) trans_flags |= TransLower; if (best_value < beta) trans_flags |= TransUpper; trans_value = value_to_trans(best_value,height); trans_store(Trans,board->key,trans_move,trans_depth,trans_flags,trans_value); } return best_value; }