Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
negamax_tictactoe_example.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
51#include <cstdint>
52#include <iostream>
53
54#include <State_Search.H>
55#include <tpl_array.H>
56
57using Aleph::Array;
58
59namespace
60{
61
62namespace Search = Aleph::Search;
63
64struct TicTacToeState
65{
66 Array<int> board;
67 int player = 1;
68 size_t moves_played = 0;
69
70 TicTacToeState() : board(size_t(9), 0)
71 {
72 // empty
73 }
74};
75
76class TicTacToeDomain
77{
78public:
79 struct Move
80 {
81 size_t cell = 0;
82
83 [[nodiscard]] bool operator == (const Move &) const noexcept = default;
84 };
85
86 using State = TicTacToeState;
87 using Score = int;
88 using State_Key = std::uint64_t;
89
90 // The game stops on a win or when no empty cells remain.
91 bool is_terminal(const State &state) const
92 {
93 return winner(state) != 0 or state.moves_played == 9;
94 }
95
96 // Evaluate from the perspective of the player that must move in `state`.
97 Score evaluate(const State &state) const
98 {
99 const int win = winner(state);
100 if (win != 0)
101 return win == state.player ? 100 : -100;
102
103 if (state.moves_played == 9)
104 return 0;
105
106 return heuristic(state, state.player) - heuristic(state, -state.player);
107 }
108
109 // Compact ternary encoding used by the optional transposition table.
110 [[nodiscard]] State_Key state_key(const State &state) const noexcept
111 {
112 State_Key key = static_cast<State_Key>(state.player > 0 ? 1 : 2);
113 for (size_t i = 0; i < state.board.size(); ++i)
114 {
115 key *= 3;
116 if (state.board[i] == 1)
117 key += 1;
118 else if (state.board[i] == -1)
119 key += 2;
120 }
121
122 return key;
123 }
124
125 // Apply one legal move and flip the side to move.
126 void apply(State &state, const Move &move) const
127 {
128 state.board[move.cell] = state.player;
129 state.player = -state.player;
130 ++state.moves_played;
131 }
132
133 // Restore the previous position exactly.
134 void undo(State &state, const Move &move) const
135 {
136 --state.moves_played;
137 state.player = -state.player;
138 state.board[move.cell] = 0;
139 }
140
141 // Generate legal moves lazily in a fixed domain order.
142 template <typename Visitor>
143 bool for_each_successor(const State &state, Visitor visit) const
144 {
145 static constexpr size_t order[] = {4, 0, 2, 6, 8, 1, 3, 5, 7};
146
147 for (const size_t cell : order)
148 if (state.board[cell] == 0 and not visit(Move{cell}))
150
151 return true;
152 }
153
154private:
155 [[nodiscard]] static int winner(const State &state) noexcept
156 {
157 static constexpr size_t lines[8][3] = {
158 {0, 1, 2}, {3, 4, 5}, {6, 7, 8},
159 {0, 3, 6}, {1, 4, 7}, {2, 5, 8},
160 {0, 4, 8}, {2, 4, 6}
161 };
162
163 for (const auto &line : lines)
164 {
165 const int a = state.board[line[0]];
166 if (a != 0 and a == state.board[line[1]] and a == state.board[line[2]])
167 return a;
168 }
169
170 return 0;
171 }
172
173 [[nodiscard]] static Score heuristic(const State &state, const int player) noexcept
174 {
175 static constexpr size_t lines[8][3] = {
176 {0, 1, 2}, {3, 4, 5}, {6, 7, 8},
177 {0, 3, 6}, {1, 4, 7}, {2, 5, 8},
178 {0, 4, 8}, {2, 4, 6}
179 };
180
181 Score total = 0;
182 for (const auto &line : lines)
183 {
184 size_t mine = 0;
185 size_t empty = 0;
186 bool blocked = false;
187
188 for (const size_t cell : line)
189 if (state.board[cell] == player)
190 ++mine;
191 else if (state.board[cell] == 0)
192 ++empty;
193 else
194 {
195 blocked = true;
196 break;
197 }
198
199 if (blocked)
200 continue;
201
202 if (mine == 2 and empty == 1)
203 total += 10;
204 else if (mine == 1 and empty == 2)
205 total += 1;
206 }
207
208 return total;
209 }
210};
211
212void print_board(const TicTacToeState &state)
213{
214 for (size_t row = 0; row < 3; ++row)
215 {
216 for (size_t col = 0; col < 3; ++col)
217 {
218 const int cell = state.board[row*3 + col];
219 std::cout << (cell == 1 ? 'X' : cell == -1 ? 'O' : '.');
220 }
221 std::cout << '\n';
222 }
223}
224
225} // end namespace
226
227int main()
228{
229 TicTacToeState root;
230 using TT = Search::GameTT<TicTacToeDomain::State_Key,
231 TicTacToeDomain::Move,
232 TicTacToeDomain::Score>;
233 using Trace = Search::TraceCollector<TicTacToeDomain::Move,
234 TicTacToeDomain::Score>;
235
236 auto negamax = Search::negamax_search(TicTacToeDomain{}, root);
238 auto negamax_tt = Search::negamax_search(TicTacToeDomain{}, root, negamax_tt_cache);
239
240 auto alpha_beta = Search::alpha_beta_search(TicTacToeDomain{}, root);
242 auto alpha_beta_tt = Search::alpha_beta_search(TicTacToeDomain{}, root, alpha_beta_tt_cache);
243
245 ordered_policy.move_ordering = Search::MoveOrderingMode::Estimated_Score;
246 ordered_policy.use_killer_moves = true;
247
250
253 iterative_options.depth_step = 1;
254 iterative_options.aspiration.half_window = 4;
255 iterative_options.aspiration.growth = 4;
256
258 Trace trace;
259 auto iterative = Search::iterative_deepening_alpha_beta_search(TicTacToeDomain{},
260 root,
262 trace,
266
267 std::cout << "Tic-Tac-Toe reference example\n";
268 std::cout << "Root position:\n";
270 std::cout << '\n';
271
272 std::cout << "Negamax root value: " << negamax.value << '\n';
273 std::cout << "Negamax best move: " << negamax.first_move().cell << '\n';
274 std::cout << "Negamax visited states: " << negamax.stats.visited_states << '\n';
275 std::cout << "Negamax + TT visited states: "
276 << negamax_tt.stats.visited_states << '\n';
277 std::cout << "Negamax + TT hits: "
278 << negamax_tt.stats.transpositions.hits << '\n';
279 std::cout << "Negamax + TT cutoffs: "
280 << negamax_tt.stats.transpositions.cutoffs << '\n';
281
282 std::cout << '\n';
283 std::cout << "Alpha-Beta root value: " << alpha_beta.value << '\n';
284 std::cout << "Alpha-Beta best move: " << alpha_beta.first_move().cell << '\n';
285 std::cout << "Alpha-Beta visited states: " << alpha_beta.stats.visited_states << '\n';
286 std::cout << "Alpha-Beta cutoffs: " << alpha_beta.stats.alpha_beta_cutoffs << '\n';
287 std::cout << "Alpha-Beta + TT visited states: "
288 << alpha_beta_tt.stats.visited_states << '\n';
289 std::cout << "Alpha-Beta + TT hits: "
290 << alpha_beta_tt.stats.transpositions.hits << '\n';
291 std::cout << "Alpha-Beta + TT cutoffs: "
292 << alpha_beta_tt.stats.transpositions.cutoffs << '\n';
293
294 std::cout << '\n';
295 std::cout << "Negamax + TT table stores: "
296 << negamax_tt_cache.stats().stores << '\n';
297 std::cout << "Alpha-Beta + TT table stores: "
298 << alpha_beta_tt_cache.stats().stores << '\n';
299 std::cout << "Iterative Alpha-Beta best move: "
300 << iterative.result.first_move().cell << '\n';
301 std::cout << "Iterative Alpha-Beta iterations: "
302 << iterative.completed_iterations << '\n';
303 std::cout << "Iterative Alpha-Beta aspiration retries: "
304 << iterative.aspiration_researches << '\n';
305 std::cout << "Iterative Alpha-Beta visited states: "
306 << iterative.total_stats.visited_states << '\n';
307 std::cout << "Iterative Alpha-Beta TT stores: "
308 << iterative_tt_cache.stats().stores << '\n';
309 std::cout << "Trace events captured: " << trace.size() << '\n';
310
311 return 0;
312}
Umbrella header for the implicit state-space search framework.
Collector that stores trace events in an Aleph list.
Definition Negamax.H:273
static ExplorationPolicy default_policy() noexcept
Return the default exploration policy for Alpha-Beta.
Definition Alpha_Beta.H:97
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
Transposition-table container built on top of Aleph hash maps.
RAII helper for function entry/exit tracing.
Definition trace.H:94
__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)
Definition gmpfrxx.h:4060
User-facing facade exported by the umbrella search header.
and
Check uniqueness with explicit hash + equality functors.
bool operator==(const DynList< T > &l1, const DynList< T > &l2)
Equality operator for DynList.
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.
#define TT
Definition ran_array.c:29
Controls for adversarial iterative deepening.
Definition Negamax.H:329
size_t initial_depth
First horizon to search.
Definition Negamax.H:330
Exploration controls shared across engines.
Hard bounds applied by the search engine.
size_t max_depth
Maximum expansion depth.
Dynamic array container with automatic resizing.