69template <AdversarialGameDomain Domain>
76 using State =
typename Domain::State;
78 using Move =
typename Domain::Move;
80 using Score =
typename Domain::Score;
162 template <
typename Tracer>
172 adversarial_search_detail::score_floor<Score>(),
173 adversarial_search_detail::score_ceiling<Score>());
184 template <
typename Tracer>
197 template <
typename Table,
typename Keyer>
206 template <
typename Table,
typename Keyer,
typename Tracer>
215 adversarial_search_detail::score_floor<Score>(),
216 adversarial_search_detail::score_ceiling<Score>());
220 template <
typename Table,
typename Keyer>
230 template <
typename Table,
typename Keyer,
typename Tracer>
244 template <
typename Table,
typename D_ = Domain>
252 return domain_.state_key(state);
259 adversarial_search_detail::score_floor<Score>(),
260 adversarial_search_detail::score_ceiling<Score>());
264 template <
typename Table,
typename Tracer,
typename D_ = Domain>
272 return domain_.state_key(state);
279 adversarial_search_detail::score_floor<Score>(),
280 adversarial_search_detail::score_ceiling<Score>());
284 template <
typename Table,
typename D_ = Domain>
295 return domain_.state_key(state);
302 template <
typename Table,
typename Tracer,
typename D_ = Domain>
311 return domain_.state_key(state);
334 <<
"Alpha_Beta does not support MoveOrderingMode::Estimated_Bound";
336 if constexpr (
not Killer_Table::supported)
338 <<
"Alpha_Beta killer heuristic requires equality-comparable Move";
342 <<
"Alpha_Beta history heuristic requires domain.move_key(move)";
353 const size_t base = remaining ==
Search_Unlimited ? depth + 1 : remaining + 1;
384 [&](
const Move &move) ->
bool
388 ranked.ordinal = ordinal++;
400 if (
ranked.history_score > 0)
453 template <
typename Table,
typename Keyer,
typename Tracer>
463 <<
"Alpha_Beta only supports depth-first exploration";
465 <<
"SearchLimits::max_solutions must be positive or Search_Unlimited";
472 const auto start_time = SearchClock::now();
498 template <
typename Table,
typename Keyer>
500 const size_t remaining,
507 adversarial_search_detail::store_adversarial_transposition<Move, Score>(
508 state, remaining, result, table,
keyer, value, bound,
stop_);
511 template <
typename Table,
typename Keyer>
513 const size_t remaining,
521 auto *entry = adversarial_search_detail::probe_and_count_transposition<Move, Score>(
522 state, remaining, result, table,
keyer);
524 if (entry !=
nullptr)
526 switch (entry->bound)
530 out.value = entry->value;
531 out.principal_variation = entry->principal_variation;
535 if (entry->value > alpha)
536 alpha = entry->value;
540 if (entry->value < beta)
548 out.value = entry->value;
549 out.principal_variation = entry->principal_variation;
557 template <
typename Table,
typename Keyer,
typename Tracer>
577 const size_t remaining = remaining_depth(
limits_, depth);
597 first_move_of(
cached.principal_variation));
601 if (
domain_.is_terminal(state))
604 auto leaf = evaluate_leaf(
domain_, state, result);
622 auto leaf = evaluate_leaf(
domain_, state, result);
640 auto leaf = evaluate_leaf(
domain_, state, result);
657 auto leaf = evaluate_leaf(
domain_, state, result);
707 ? std::numeric_limits<Score>::max()
733 std::optional<Move>{move});
743 const auto &
ranked : ordered_moves)
748 (
void)
domain_.for_each_successor(state,
749 [&](
const Move &move) ->
bool
757 auto leaf = evaluate_leaf(
domain_, state, result);
788 first_move_of(
best.principal_variation));
796template <AdversarialGameDomain Domain>
807template <AdversarialGameDomain Domain,
typename Tracer>
820template <AdversarialGameDomain Domain,
typename Table,
typename Keyer>
834template <AdversarialGameDomain Domain,
typename Table,
typename Keyer,
typename Tracer>
850template <AdversarialGameDomain Domain,
typename Table>
864template <AdversarialGameDomain Domain,
typename Table,
typename Tracer>
880template <AdversarialGameDomain Domain,
typename Tracer>
890 using Move =
typename Domain::Move;
891 using Score =
typename Domain::Score;
894 <<
"iterative_deepening_alpha_beta_search requires a finite SearchLimits::max_depth";
896 <<
"AdversarialIterativeDeepeningOptions::depth_step must be positive";
898 <<
"AdversarialIterativeDeepeningOptions::initial_depth exceeds SearchLimits::max_depth";
900 <<
"AspirationWindow::half_window must be non-negative";
903 const Score
full_alpha = adversarial_search_detail::score_floor<Score>();
904 const Score
full_beta = adversarial_search_detail::score_ceiling<Score>();
906 return adversarial_search_detail::run_iterative_deepening<Move, Score>(
915 Score center = Score{};
916 Score half_window =
options.aspiration.half_window;
918 center =
out.result.value;
943 ++
out.aspiration_researches;
945 half_window,
options.aspiration);
946 adversarial_search_detail::emit_trace<Move, Score>(
961 ++
out.aspiration_researches;
963 half_window,
options.aspiration);
964 adversarial_search_detail::emit_trace<Move, Score>(
982template <AdversarialGameDomain Domain,
typename Table,
typename Keyer,
typename Tracer>
995 using Move =
typename Domain::Move;
996 using Score =
typename Domain::Score;
999 <<
"iterative_deepening_alpha_beta_search requires a finite SearchLimits::max_depth";
1001 <<
"AdversarialIterativeDeepeningOptions::depth_step must be positive";
1003 <<
"AdversarialIterativeDeepeningOptions::initial_depth exceeds SearchLimits::max_depth";
1005 <<
"AspirationWindow::half_window must be non-negative";
1008 const Score
full_alpha = adversarial_search_detail::score_floor<Score>();
1009 const Score
full_beta = adversarial_search_detail::score_ceiling<Score>();
1011 return adversarial_search_detail::run_iterative_deepening<Move, Score>(
1020 Score center = Score{};
1021 Score half_window =
options.aspiration.half_window;
1023 center =
out.result.value;
1049 ++
out.aspiration_researches;
1051 half_window,
options.aspiration);
1052 adversarial_search_detail::emit_trace<Move, Score>(
1067 ++
out.aspiration_researches;
1069 half_window,
options.aspiration);
1070 adversarial_search_detail::emit_trace<Move, Score>(
1088template <AdversarialGameDomain Domain,
typename Table,
typename Tracer>
1105 [&
engine](
const typename Domain::State &state)
1107 return engine.domain().state_key(state);
1116template <AdversarialGameDomain Domain>
1130template <AdversarialGameDomain Domain,
typename Table,
typename Keyer>
1147template <AdversarialGameDomain Domain,
typename Table>
Negamax search for two-player zero-sum turn-based games.
Exception handling system with formatted messages for Aleph-w.
#define ah_invalid_argument_unless(C)
Throws std::invalid_argument if condition does NOT hold.
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Adversarial search engine with Alpha-Beta pruning.
typename Domain::Move Move
Move type.
void set_policy(const ExplorationPolicy &policy) noexcept
Replace the exploration policy for future runs.
and AdversarialSearchTracer< Tracer, Move, Score > Result search(State initial_state, Table &table, Keyer keyer, Tracer &tracer)
Execute Alpha-Beta with TT/keyer and tracing.
static size_t history_bonus(const size_t depth, const size_t remaining) noexcept
and AdversarialTranspositionMemo< Table, typename D_::State_Key, Move, Score > Result search_with_window(State initial_state, Table &table, const Score alpha, const Score beta)
Execute Alpha-Beta with domain-provided key and explicit root window.
Alpha_Beta(Domain domain, ExplorationPolicy policy=default_policy(), const SearchLimits &limits={})
Build an Alpha-Beta engine bound to one game domain.
void set_limits(const SearchLimits &limits) noexcept
Replace the hard limits for future runs.
Result search_with_window(State initial_state, const Score alpha, const Score beta)
Execute Alpha-Beta with an explicit root window.
Domain Domain_Type
Type of the problem domain.
Array< RankedMove< Move, Score > > collect_ordered_moves(State &state, const size_t depth, Result &result)
static ExplorationPolicy default_policy() noexcept
Return the default exploration policy for Alpha-Beta.
const SearchLimits & limits() const noexcept
Current hard limits.
Result search_impl(State initial_state, Table &table, Keyer &keyer, Tracer &tracer, const Score alpha, const Score beta)
adversarial_search_detail::NodeEvaluation< Move, Score > search_node(State &state, const size_t depth, Result &result, Table &table, Keyer &keyer, Tracer &tracer, const Score alpha, const Score beta)
and AdversarialTranspositionMemo< Table, typename D_::State_Key, Move, Score > Result search(State initial_state, Table &table)
Execute Alpha-Beta with a transposition table using domain().state_key().
const Domain & domain() const noexcept
Read-only access to the bound game domain.
bool ordering_active() const noexcept
Killer_Table killer_moves_
typename Domain::Score Score
Score type used for board evaluation.
History_Table history_moves_
bool expansion_limit_reached(Result &result)
bool probe_transposition(State &state, const size_t remaining, Result &result, Table &table, Keyer &keyer, Score &alpha, Score &beta, adversarial_search_detail::NodeEvaluation< Move, Score > &out)
void validate_ordering_configuration() const
static constexpr bool supports_best_first
Compile-time marker: Alpha_Beta only supports Depth_First strategy.
typename adversarial_search_detail::History_Table_Selector< Domain >::Type History_Table
History heuristic table type (sparse or no-op depending on domain).
void record_cutoff_move(const size_t depth, const size_t remaining, const Move &move)
size_t move_history_score(const Move &move) const noexcept
Domain & domain() noexcept
Mutable access to the bound game domain.
typename Domain::State State
Concrete search state type.
Result search(State initial_state, Table &table, Keyer keyer)
Execute Alpha-Beta with a transposition table and explicit keyer.
Result search(State initial_state)
Execute Alpha-Beta from initial_state.
Result search(State initial_state, Tracer &tracer)
Execute Alpha-Beta from initial_state while emitting trace events.
ExplorationPolicy policy_
void clear_ordering_heuristics()
const ExplorationPolicy & policy() const noexcept
Current exploration policy.
and AdversarialTranspositionMemo< Table, typename D_::State_Key, Move, Score > and AdversarialSearchTracer< Tracer, Move, Score > Result search(State initial_state, Table &table, Tracer &tracer)
Execute Alpha-Beta with domain-provided key and tracing.
and AdversarialSearchTracer< Tracer, Move, Score > Result search_with_window(State initial_state, Table &table, Keyer keyer, const Score alpha, const Score beta, Tracer &tracer)
Execute Alpha-Beta with TT/keyer, root window and tracing.
void store_transposition(State &state, const size_t remaining, Result &result, Table &table, Keyer &keyer, const adversarial_search_detail::NodeEvaluation< Move, Score > &value, const TranspositionBound bound)
and AdversarialTranspositionMemo< Table, typename D_::State_Key, Move, Score > and AdversarialSearchTracer< Tracer, Move, Score > Result search_with_window(State initial_state, Table &table, const Score alpha, const Score beta, Tracer &tracer)
Execute Alpha-Beta with domain-provided key, root window and tracing.
Result search_with_window(State initial_state, Table &table, Keyer keyer, const Score alpha, const Score beta)
Execute Alpha-Beta with TT/keyer and an explicit root window.
Result search_with_window(State initial_state, const Score alpha, const Score beta, Tracer &tracer)
Execute Alpha-Beta with an explicit root window and tracing.
Simple dynamic array with automatic resizing and functional operations.
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
constexpr bool is_empty() const noexcept
Checks if the container is empty.
T & append(const T &data)
Append a copy of data
Generic linear hash table.
Two-slot killer heuristic table indexed by search depth.
static ExplorationPolicy default_policy() noexcept
Return the default exploration policy for adversarial search.
Concept for explicit state-key providers used by adversarial TT overloads.
Concept for adversarial-search trace sinks.
Concept for memo tables storing adversarial-search entries.
Concept for adversarial-search domains that can estimate a child score without modifying state (incre...
Optional concept for domains that can key moves for history heuristics.
Generic concept for domains that can expose a stable state key.
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
std::optional< Move > first_move_of(const SearchPath< Move > &path)
constexpr size_t remaining_depth(const SearchLimits &limits, const size_t depth) noexcept
constexpr Score saturating_add(const Score value, const Score delta) noexcept
constexpr Score grow_aspiration_half_window(const Score current, const AspirationWindow< Score > &window) noexcept
constexpr Score saturating_subtract(const Score value, const Score delta) noexcept
void emit_trace(Tracer &tracer, const AdversarialTraceEventKind kind, const size_t depth, const size_t remaining, const size_t iteration=0, const size_t horizon=0, const size_t aspiration_retries=0, const Score value=Score{}, const Score alpha=Score{}, const Score beta=Score{}, const std::optional< Move > &move=std::nullopt, const SearchPath< Move > *principal_variation=nullptr)
void accumulate_adversarial_stats(AdversarialSearchStats &dst, const AdversarialSearchStats &src) noexcept
SearchPath< Move > prepend_move(const Move &move, const SearchPath< Move > &tail)
auto evaluate_leaf(Domain &domain, const typename Domain::State &state, Result &result)
bool should_prune_state(Domain &domain, const typename Domain::State &state, const size_t depth)
Dispatch helper for the optional should_prune hook.
void register_visit(const size_t depth, Result &result)
Update visit counters and max-depth statistic.
bool expansion_limit_reached(Result &result, const SearchLimits &limits)
Check whether the expansion limit has been reached.
Main namespace for Aleph-w library functions.
and
Check uniqueness with explicit hash + equality functors.
void sort_ranked_moves(Array< RankedMove< Move, Priority > > &moves, BetterPriority better_priority, const bool prefer_killer, const bool prefer_history)
Sort one materialized move batch using priority and optional hooks.
TranspositionBound
Stored bound classification for memoized search entries.
@ Upper_Bound
Value is an upper bound (fail-low style).
@ Exact
Exact value for the stored search configuration.
@ Lower_Bound
Value is a lower bound (fail-high style).
constexpr size_t Search_Unlimited
Sentinel used by SearchLimits to mean "no bound".
@ Exhausted
Search space within the configured bounds was exhausted.
@ NotStarted
Search object exists but no traversal has run yet.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
auto iterative_deepening_alpha_beta_search(Domain domain, typename Domain::State initial_state, Tracer &tracer, ExplorationPolicy policy=Alpha_Beta< Domain >::default_policy(), SearchLimits limits={}, AdversarialIterativeDeepeningOptions< typename Domain::Score > options={})
Iterative deepening over Alpha-Beta with optional aspiration windows.
auto alpha_beta_search(Domain domain, typename Domain::State initial_state, ExplorationPolicy policy=Alpha_Beta< Domain >::default_policy(), SearchLimits limits={})
Convenience wrapper for one-shot Alpha-Beta search.
@ Alpha_Beta_Cutoff
One Alpha-Beta fail-high cutoff occurred.
@ Expansion_Limit
Search stopped because the expansion limit was hit.
@ Aspiration_Retry
An aspiration window failed and was widened.
@ Depth_Cutoff
Search stopped at the configured horizon.
@ Transposition_Hit
One cached transposition entry was reused.
@ Enter_Node
One search node has just been entered.
@ Exit_Node
One search node finished with an exact local result.
@ Domain_Prune
Domain-side pruning hook discarded the state.
@ Terminal_Node
A terminal state was evaluated.
double search_elapsed_ms(const SearchClock::duration &duration) noexcept
@ Estimated_Bound
Rank successors by an optimistic child bound.
@ Domain
Preserve the order emitted by for_each_successor().
@ Estimated_Score
Rank successors by a cheap heuristic score estimate.
static struct argp_option options[]
Result of one iterative-deepening iteration.
size_t aspiration_researches
Number of retries triggered by aspiration failure.
bool used_aspiration_window
Whether the iteration started from a bounded window.
Score aspiration_beta
Final upper bound used in the last search attempt.
AdversarialSearchResult< Move, Score > result
Final result returned for this depth.
AdversarialSearchStats total_stats
Aggregate cost of all attempts at this depth.
Score aspiration_alpha
Final lower bound used in the last search attempt.
Controls for adversarial iterative deepening.
Aggregate result of adversarial iterative deepening.
Result of one adversarial search execution.
SearchLimits limits
Limits used for the run.
AdversarialSearchStats stats
Collected adversarial-search statistics.
Score value
Root score from the current player's perspective.
ExplorationPolicy policy
Exploration policy used during the run.
SearchStatus status
Final execution state.
SearchPath< Move > principal_variation
Best line found from the root.
size_t alpha_beta_cutoffs
Number of cutoffs performed by Alpha-Beta.
size_t heuristic_evaluations
Number of calls to evaluate(state).
TranspositionStats transpositions
Transposition-table usage during this run.
Exploration controls shared across engines.
Strategy strategy
Traversal strategy.
MoveOrderingMode move_ordering
Successor-ordering mode.
@ Depth_First
Recursive depth-first traversal (all engines).
bool use_history_heuristic
Enable experimental history heuristic where supported.
bool use_killer_moves
Enable experimental killer heuristic where supported.
size_t priority_estimates
Number of score/bound estimates computed for ordering.
size_t killer_hits
Moves promoted because they matched a killer slot.
size_t history_hits
Moves promoted because they had non-zero history.
size_t ordered_moves
Number of individual moves considered by ordering.
size_t ordered_batches
Number of successor batches materialized and ordered.
Default no-op tracer used when the caller does not request tracing.
Null history heuristic hook used when a domain has no move key.
One move plus the metadata used by ordering comparators.
Move move
The candidate move.
Hard bounds applied by the search engine.
size_t max_solutions
Maximum accepted solutions.
size_t max_depth
Maximum expansion depth.
size_t pruned_by_domain
States discarded by domain-side pruning.
size_t terminal_states
Non-solution terminal states cut by the domain.
double elapsed_ms
Wall-clock time spent inside the search call.
MoveOrderingStats move_ordering
Successor-ordering activity for this run.
size_t pruned_by_depth
States not expanded due to max depth.
size_t generated_successors
Number of successor moves emitted.
size_t expanded_states
Number of non-terminal states expanded.
size_t cutoffs
Number of search cutoffs enabled by cached data.
SearchPath< Move > principal_variation