Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
alpha_beta_connect3_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
40# include <iostream>
41
42# include <State_Search.H>
43# include <tpl_array.H>
44
45using namespace Aleph;
46
47namespace
48{
49
50struct Connect3State
51{
52 Array<int> board;
53 Array<size_t> heights;
54 int player = 1;
55 size_t moves_played = 0;
56
57 Connect3State()
58 : board(size_t(16), 0), heights(size_t(4), size_t(0))
59 {
60 // empty
61 }
62};
63
64class Connect3Domain
65{
66public:
67 struct Move
68 {
69 size_t column = 0;
70
71 [[nodiscard]] bool operator == (const Move &) const noexcept = default;
72 };
73
74 using State = Connect3State;
75 using Score = int;
76 using Move_Key = size_t;
77
78 static constexpr size_t Rows = 4;
79 static constexpr size_t Cols = 4;
80 static constexpr size_t Align = 3;
81
82 bool is_terminal(const State &state) const
83 {
84 return winner(state) != 0 or state.moves_played == Rows*Cols;
85 }
86
87 Score evaluate(const State &state) const
88 {
89 const int win = winner(state);
90 if (win != 0)
91 return win == state.player ? 1000 : -1000;
92
93 if (state.moves_played == Rows*Cols)
94 return 0;
95
96 return heuristic(state, state.player) - heuristic(state, -state.player);
97 }
98
99 [[nodiscard]] Move_Key move_key(const Move &move) const noexcept
100 {
101 return move.column;
102 }
103
104 void apply(State &state, const Move &move) const
105 {
106 const size_t row = state.heights[move.column];
107 state.board[index(row, move.column)] = state.player;
108 ++state.heights[move.column];
109 state.player = -state.player;
110 ++state.moves_played;
111 }
112
113 void undo(State &state, const Move &move) const
114 {
115 --state.moves_played;
116 state.player = -state.player;
117 --state.heights[move.column];
118 const size_t row = state.heights[move.column];
119 state.board[index(row, move.column)] = 0;
120 }
121
122 template <typename Visitor>
123 bool for_each_successor(const State &state, Visitor visit) const
124 {
125 static constexpr size_t order[] = {0, 3, 1, 2};
126
127 for (const size_t col : order)
128 if (state.heights[col] < Rows and not visit(Move{col}))
130
131 return true;
132 }
133
134private:
135 [[nodiscard]] static constexpr size_t index(const size_t row,
136 const size_t col) noexcept
137 {
138 return row*Cols + col;
139 }
140
141 [[nodiscard]] static int winner(const State &state) noexcept
142 {
143 for (size_t row = 0; row < Rows; ++row)
144 for (size_t col = 0; col < Cols; ++col)
145 {
146 const int cell = state.board[index(row, col)];
147 if (cell == 0)
148 continue;
149
150 if (col + Align <= Cols)
151 {
152 bool ok = true;
153 for (size_t k = 1; k < Align; ++k)
154 ok = ok and state.board[index(row, col + k)] == cell;
155 if (ok)
156 return cell;
157 }
158
159 if (row + Align <= Rows)
160 {
161 bool ok = true;
162 for (size_t k = 1; k < Align; ++k)
163 ok = ok and state.board[index(row + k, col)] == cell;
164 if (ok)
165 return cell;
166 }
167
168 if (row + Align <= Rows and col + Align <= Cols)
169 {
170 bool ok = true;
171 for (size_t k = 1; k < Align; ++k)
172 ok = ok and state.board[index(row + k, col + k)] == cell;
173 if (ok)
174 return cell;
175 }
176
177 if (row + Align <= Rows and col + 1 >= Align)
178 {
179 bool ok = true;
180 for (size_t k = 1; k < Align; ++k)
181 ok = ok and state.board[index(row + k, col - k)] == cell;
182 if (ok)
183 return cell;
184 }
185 }
186
187 return 0;
188 }
189
190 [[nodiscard]] static Score heuristic(const State &state, const int player) noexcept
191 {
192 Score total = 0;
193
194 auto score_window = [&](const size_t r0,
195 const size_t c0,
196 const int dr,
197 const int dc) noexcept
198 {
199 size_t mine = 0;
200 size_t empty = 0;
201
202 for (size_t k = 0; k < Align; ++k)
203 {
204 const size_t row = static_cast<size_t>(static_cast<int>(r0) +
205 dr*static_cast<int>(k));
206 const size_t col = static_cast<size_t>(static_cast<int>(c0) +
207 dc*static_cast<int>(k));
208 const int cell = state.board[index(row, col)];
209 if (cell == player)
210 ++mine;
211 else if (cell == 0)
212 ++empty;
213 else
214 return;
215 }
216
217 if (mine == 2 and empty == 1)
218 total += 20;
219 else if (mine == 1 and empty == 2)
220 total += 2;
221 };
222
223 for (size_t row = 0; row < Rows; ++row)
224 for (size_t col = 0; col + Align <= Cols; ++col)
225 score_window(row, col, 0, 1);
226
227 for (size_t row = 0; row + Align <= Rows; ++row)
228 for (size_t col = 0; col < Cols; ++col)
229 score_window(row, col, 1, 0);
230
231 for (size_t row = 0; row + Align <= Rows; ++row)
232 for (size_t col = 0; col + Align <= Cols; ++col)
233 score_window(row, col, 1, 1);
234
235 for (size_t row = 0; row + Align <= Rows; ++row)
236 for (size_t col = Align - 1; col < Cols; ++col)
237 score_window(row, col, 1, -1);
238
239 return total;
240 }
241};
242
243void print_board(const Connect3State &state)
244{
245 for (size_t row = Connect3Domain::Rows; row > 0; --row)
246 {
247 const size_t current = row - 1;
248 for (size_t col = 0; col < Connect3Domain::Cols; ++col)
249 {
250 const int cell = state.board[current*Connect3Domain::Cols + col];
251 std::cout << (cell == 1 ? 'X' : cell == -1 ? 'O' : '.');
252 }
253 std::cout << '\n';
254 }
255}
256
257void drop(Connect3Domain &domain, Connect3State &state, const size_t column)
258{
259 domain.apply(state, Connect3Domain::Move{column});
260}
261
262} // end namespace
263
264int main()
265{
266 Connect3Domain domain;
267 Connect3State state;
268
269 drop(domain, state, 1);
270 drop(domain, state, 2);
271 drop(domain, state, 1);
272 drop(domain, state, 2);
273 drop(domain, state, 0);
274
275 SearchLimits limits;
276 limits.max_depth = 6;
277
280 auto plain = plain_engine.search(state);
281
283 ordered_policy.move_ordering = MoveOrderingMode::Estimated_Score;
284 ordered_policy.use_killer_moves = true;
285 ordered_policy.use_history_heuristic = true;
287 auto ordered = ordered_engine.search(state);
288
289 std::cout << "Reduced Connect-3 position:\n";
290 print_board(state);
291 std::cout << '\n';
292 std::cout << "Alpha-Beta value: " << plain.value << '\n';
293 std::cout << "Best column: " << plain.first_move().column << '\n';
294 std::cout << "Visited states (domain order): " << plain.stats.visited_states << '\n';
295 std::cout << "Cutoffs (domain order): " << plain.stats.alpha_beta_cutoffs << '\n';
296 std::cout << '\n';
297 std::cout << "Best column with ordering: " << ordered.first_move().column << '\n';
298 std::cout << "Visited states (ordered): " << ordered.stats.visited_states << '\n';
299 std::cout << "Cutoffs (ordered): " << ordered.stats.alpha_beta_cutoffs << '\n';
300 std::cout << "Ordered batches: " << ordered.stats.move_ordering.ordered_batches << '\n';
301 std::cout << "Priority estimates: " << ordered.stats.move_ordering.priority_estimates << '\n';
302 std::cout << "Killer hits: " << ordered.stats.move_ordering.killer_hits << '\n';
303 std::cout << "History hits: " << ordered.stats.move_ordering.history_hits << '\n';
304
305 return 0;
306}
Umbrella header for the implicit state-space search framework.
Adversarial search engine with Alpha-Beta pruning.
Definition Alpha_Beta.H:71
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
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
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.
Exploration controls shared across engines.
Hard bounds applied by the search engine.
size_t max_depth
Maximum expansion depth.
static int * k
Dynamic array container with automatic resizing.