static int handle_beta_extension(struct node *node, move m, int value) { if (!node->common->sd.settings.use_beta_extensions) return value; if (value >= node->beta && !node[-1].is_in_null_move_search && is_in_check(node[1].pos) && value < mate_value && node->depth > PLY && node->depth < 10 * PLY && node->mo->picked_count > 1 && !is_capture(m) && !is_promotion(m) && mtype(m) != mt_castle_kingside && mtype(m) != mt_castle_queenside) { node[1].alpha = -node->beta; node[1].beta = -node->alpha; node[1].depth = node->depth; return negamax_child(node); } else { return value; } }
static char* print_san_move_from(const struct position *pos, move m, char *str, enum player turn) { move moves[MOVE_ARRAY_LENGTH]; uint64_t ambig_pieces = UINT64_C(0); enum piece p = pos_piece_at(pos, mfrom(m)); (void) gen_moves(pos, moves); for (move *im = moves; *im != 0; ++im) { if ((mfrom(*im) != mfrom(m)) && (mto(*im) == mto(m)) && (pos_piece_at(pos, mfrom(*im)) == p)) ambig_pieces |= mfrom64(*im); } if ((p == pawn) && is_capture(m)) { *(str++) = index_to_file_ch(mfrom(m)); } else if (is_nonempty(ambig_pieces)) { if (is_nonempty(ambig_pieces & file64(mfrom(m)))) { if (is_nonempty(ambig_pieces & rank64(mfrom(m)))) { *(str++) = index_to_file_ch(mfrom(m)); } *(str++) = index_to_rank_ch(mfrom(m), turn); } else { *(str++) = index_to_file_ch(mfrom(m)); } } return str; }
static char* print_san_move(const struct position *pos, move m, char *str, enum player turn) { int piece = pos_piece_at(pos, mfrom(m)); if (mtype(m) == mt_castle_kingside) return str + sprintf(str, "O-O"); else if (mtype(m) == mt_castle_queenside) return str + sprintf(str, "O-O-O"); if (piece != pawn) *str++ = (char)toupper((unsigned char)piece_to_char(piece)); str = print_san_move_from(pos, m, str, turn); if (is_capture(m)) *str++ = 'x'; str = index_to_str(str, mto(m), turn); if (mtype(m) == mt_en_passant) return str + sprintf(str, "e.p."); str = print_san_promotion(m, str); str = print_san_check(pos, m, str); *str = '\0'; return str; }
int alpha_beta(app_t *app, cnodeptr_t parent, int alpha, int beta, int depth) { int palpha = alpha; int i, score = -MATE, highest = -MATE; node_t node; move_t cutoff = 0; piece_t p; init_node(&node); assert(app); app->search.nodes++; node.depth = depth; /* max depth */ if (app->game.board->ply > (SEARCH_MAXDEPTH - 1)) { /* return evaluate(app->board); */ return quiescent(app, parent, alpha, beta); } /* recursive base */ if (depth == 0) { return evaluate(app->game.board); } /* draws */ if (repetitions(app) || (app->game.board->half >= 100)) { return 0; } /* if we are checked, set the nodes checked flag */ if (check(app->game.board, app->game.board->side)) { node.flags |= NODE_CHECK; /* extend our search by 1 depth if we are in check */ /* NOTES: we may want to NOT extend our search here if the parent is in check, because the means we already extended once */ depth++; } /* TODO: - NULL moves - Late-move reduction - Tactical extensions (pins & forks -> depth++) */ /* probe our table */ if (probe_hash(&app->hash, app->game.board, &cutoff, &score, depth, alpha, beta) == TRUE) { app->hash.cut++; return score; } /* generate moves */ generate_moves(app->game.board, &node.ml, &node.ml); /* reset score */ score = -MATE; /* try to match our hash hit move */ if (cutoff != 0) { for (i = 0; i < node.ml.count; i++) { if (node.ml.moves[i] == cutoff) { node.ml.scores[i] = 20000; break; } } } /* search negamax */ for (i = 0; i < node.ml.count; i++) { /* get the next move ordered */ next_move(i, &node.ml); if (!(do_move(app->game.board, &app->game.undo, node.ml.moves[i]))) continue; score = -alpha_beta(app, &node, -beta, -alpha, depth - 1); node.made++; undo_move(app->game.board, &app->game.undo); /* score whatever is best so far */ if (score > highest) { node.best = node.ml.moves[i]; highest = score; /* update alpha */ if (score > alpha) { if (score >= beta) { /* non-captures causing beta cutoffs (killers) */ if (!is_capture(node.ml.moves[i])) { app->game.board->killers[1][app->game.board->ply] = app->game.board->killers[0][app->game.board->ply]; app->game.board->killers[0][app->game.board->ply] = node.ml.moves[i]; } /* store this beta in our transposition table */ store_hash(&app->hash, app->game.board, node.best, beta, HASH_BETA, depth); return beta; } /* update alpha */ alpha = score; /* update our history */ if (!is_capture(node.best)) { p = app->game.board->pos.squares[move_from(node.best)]; app->game.board->history[piece_color(p)][piece_type(p)][move_to(node.best)] += depth; } } } } /* check for checkmate or stalemate */ if (!node.made) { if (node.flags & NODE_CHECK) { return -MATE + app->game.board->ply; } else { return 0; } } if (alpha != palpha) { /* store this as an exact, since we beat alpha */ store_hash(&app->hash, app->game.board, node.best, highest, HASH_EXACT, depth); } else { /* store the current alpha */ store_hash(&app->hash, app->game.board, node.best, alpha, HASH_ALPHA, depth); } return alpha; }
int Search::alphaBeta(bool white_turn, int depth, int alpha, int beta, Board& board, Transposition *tt, bool null_move_in_branch, Move (&killers)[32][2], int (&history)[64][64], int ply) { // If, mate we do not need search at greater depths if (board.b[WHITE][KING] == 0) { return -10000; } else if (board.b[BLACK][KING] == 0) { return 10000; } if (depth == 0) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } if (depth == 1) { // futility pruning. we do not hope for improving a position more than 300 in one move... int static_eval = evaluate(board); if (white_turn && (static_eval + 300) < alpha) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } if (!white_turn && (static_eval - 300) > beta) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } } if (depth == 2) { // extended futility pruning. we do not hope for improving a position more than 500 in two plies... // not really proven to +ELO but does not worse performance at least int static_eval = evaluate(board); if ((white_turn && (static_eval + 500) < alpha) || (!white_turn && (static_eval - 500) > beta)) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } } // null move heuristic - we do this despite in check.. if (!null_move_in_branch && depth > 3) { // skip a turn and see if and see if we get a cut-off at shallower depth // it assumes: // 1. That the disadvantage of forfeiting one's turn is greater than the disadvantage of performing a shallower search. // 2. That the beta cut-offs prunes enough branches to be worth the time searching at reduced depth int R = 2; // depth reduction int res = alphaBeta(!white_turn, depth - 1 - R, alpha, beta, board, tt, true, killers, history, ply + 1); if (white_turn && res > alpha) { alpha = res; } else if (!white_turn && res < beta) { beta = res; } if (beta <= alpha) { if (white_turn) { return alpha; } return beta; } } MoveList moves = get_children(board, white_turn); if (moves.empty()) { return 0; } Transposition tt_pv = tt[board.hash_key % HASH_SIZE]; for (auto it = moves.begin(); it != moves.end(); ++it) { // sort pv moves first if (tt_pv.next_move != 0 && tt_pv.hash == board.hash_key && tt_pv.next_move == it->m) { it->sort_score += 1100000; } // ...then captures in MVVLVA order if (!is_capture(it->m)) { // killer moves are quite moves that has previously led to a cut-off if (killers[ply - 1][0].m == it->m) { it->sort_score += 999999; } else if (killers[ply - 1][1].m == it->m) { it->sort_score += 899999; } else { // "history heuristics" // the rest of the quite moves are sorted based on how often they increase score in the search tree it->sort_score += history[from_square(it->m)][to_square(it->m)]; } } } total_generated_moves += moves.size(); Transposition t; t.hash = board.hash_key; int next_move = 0; for (unsigned int i = 0; i < moves.size(); ++i) { // late move reduction. // we assume sort order is good enough to not search later moves as deep as the first 4 if (depth > 5 && i == 10) { depth -= 2; } pick_next_move(moves, i); Move child = moves[i]; node_count++; make_move(board, child); int res = alphaBeta(!white_turn, depth - 1, alpha, beta, board, tt, null_move_in_branch, killers, history, ply + 1); unmake_move(board, child); if (res > alpha && res < beta) { // only cache exact scores } if (res > alpha && white_turn) { next_move = child.m; alpha = res; //history heuristics if (!is_capture(child.m)) { history[from_square(child.m)][to_square(child.m)] += depth; } // update pv } if (res < beta && !white_turn) { next_move = child.m; beta = res; //history heuristics if (!is_capture(child.m)) { history[from_square(child.m)][to_square(child.m)] += depth; } // update pv } if (beta <= alpha || time_to_stop()) { if (!is_capture(child.m)) { if (killers[ply - 1][0].m != child.m) { killers[ply - 1][1] = killers[ply - 1][0]; killers[ply - 1][0] = child; } } break; } } t.next_move = next_move; tt[board.hash_key % HASH_SIZE] = t; if (white_turn) { return alpha; } return beta; }
Value name_NT_InCheck(qsearch)(Pos* pos, Stack* ss, Value alpha, BETA_ARG Depth depth) { assert(InCheck == !!pos_checkers()); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(depth <= DEPTH_ZERO); Move pv[MAX_PLY+1]; TTEntry *tte; Key posKey; Move ttMove, move, bestMove; Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; int ttHit, ttPv, givesCheck, evasionPrunable; Depth ttDepth; int moveCount; if (PvNode) { oldAlpha = alpha; // To flag BOUND_EXACT when eval above alpha and no available moves (ss+1)->pv = pv; ss->pv[0] = 0; } bestMove = 0; moveCount = 0; // Check for an instant draw or if the maximum ply has been reached if (is_draw(pos) || ss->ply >= MAX_PLY) return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); // 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 // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = InCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; // Transposition table lookup posKey = pos_key(); tte = tt_probe(posKey, &ttHit); ttValue = ttHit ? value_from_tt(tte_value(tte), ss->ply) : VALUE_NONE; ttMove = ttHit ? tte_move(tte) : 0; ttPv = ttHit ? tte_is_pv(tte) : 0; if ( !PvNode && ttHit && tte_depth(tte) >= ttDepth && ttValue != VALUE_NONE // Only in case of TT access race && (ttValue >= beta ? (tte_bound(tte) & BOUND_LOWER) : (tte_bound(tte) & BOUND_UPPER))) return ttValue; // Evaluate the position statically if (InCheck) { ss->staticEval = VALUE_NONE; bestValue = futilityBase = -VALUE_INFINITE; } else { if (ttHit) { // Never assume anything on values stored in TT if ((ss->staticEval = bestValue = tte_eval(tte)) == VALUE_NONE) ss->staticEval = bestValue = evaluate(pos); // Can ttValue be used as a better position evaluation? if (ttValue != VALUE_NONE) if (tte_bound(tte) & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)) bestValue = ttValue; } else ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Tempo; // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { if (!ttHit) tte_save(tte, posKey, value_to_tt(bestValue, ss->ply), ttPv, BOUND_LOWER, DEPTH_NONE, 0, ss->staticEval, tt_generation()); return bestValue; } if (PvNode && bestValue > alpha) alpha = bestValue; futilityBase = bestValue + 128; } ss->history = &(*pos->counterMoveHistory)[0][0]; // Initialize move picker data for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // be generated. mp_init_q(pos, ttMove, depth, to_sq((ss-1)->currentMove)); // Loop through the moves until no moves remain or a beta cutoff occurs while ((move = next_move(pos, 0))) { assert(move_is_ok(move)); givesCheck = gives_check(pos, ss, move); moveCount++; // Futility pruning if ( !InCheck && !givesCheck && futilityBase > -VALUE_KNOWN_WIN && !advanced_pawn_push(pos, move)) { assert(type_of_m(move) != ENPASSANT); // Due to !advanced_pawn_push futilityValue = futilityBase + PieceValue[EG][piece_on(to_sq(move))]; if (futilityValue <= alpha) { bestValue = max(bestValue, futilityValue); continue; } if (futilityBase <= alpha && !see_test(pos, move, 1)) { bestValue = max(bestValue, futilityBase); continue; } } // Detect non-capture evasions that are candidates to be pruned evasionPrunable = InCheck && (depth != DEPTH_ZERO || moveCount > 2) && bestValue > VALUE_MATED_IN_MAX_PLY && !is_capture(pos, move); // Don't search moves with negative SEE values if ( (!InCheck || evasionPrunable) && !see_test(pos, move, 0)) continue; // Speculative prefetch as early as possible prefetch(tt_first_entry(key_after(pos, move))); // Check for legality just before making the move if (!is_legal(pos, move)) { moveCount--; continue; } ss->currentMove = move; ss->history = &(*pos->counterMoveHistory)[moved_piece(move)][to_sq(move)]; // Make and search the move do_move(pos, move, givesCheck); #if PvNode value = givesCheck ? -qsearch_PV_true(pos, ss+1, -beta, -alpha, depth - ONE_PLY) : -qsearch_PV_false(pos, ss+1, -beta, -alpha, depth - ONE_PLY); #else value = givesCheck ? -qsearch_NonPV_true(pos, ss+1, -beta, depth - ONE_PLY) : -qsearch_NonPV_false(pos, ss+1, -beta, depth - ONE_PLY); #endif undo_move(pos, move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); // Check for a new best move if (value > bestValue) { bestValue = value; if (value > alpha) { bestMove = move; if (PvNode) // Update pv even in fail-high case update_pv(ss->pv, move, (ss+1)->pv); if (PvNode && value < beta) // Update alpha here! alpha = value; else break; // Fail high } } } // All legal moves have been searched. A special case: If we're in check // and no legal moves were found, it is checkmate. if (InCheck && bestValue == -VALUE_INFINITE) return mated_in(ss->ply); // Plies to mate from the root tte_save(tte, posKey, value_to_tt(bestValue, ss->ply), ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, ttDepth, bestMove, ss->staticEval, tt_generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; }