Xiangiqgame
AI engine for Xiangqi
Loading...
Searching...
No Matches
move_evaluator_minimax_for_concepts.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <array>
8#include <atomic>
12#include <functional>
13#include <limits>
14#include <memory>
15#include <optional>
16#include <unordered_map>
17
18using namespace gameboard;
19
20namespace moveselection {
21
22namespace minimaxutils_forconcepts {
23bool ValidateMove(SearchSummary &search_summary, const MoveCollection &allowed_moves);
24
25inline void UpdateAlpha(Points_t &alpha, Points_t cur_eval) {
26 alpha = max(alpha, cur_eval);
27}
28
29inline void UpdateBeta(Points_t &beta, Points_t cur_eval) { beta = min(beta, cur_eval); }
30
32 Points_t &alpha,
33 Points_t &beta,
34 MinimaxResultType &result_type
35) {
36 bool is_prunable = (beta <= alpha);
37 if (is_prunable) {
39 }
40 return is_prunable;
41}
42
44 Points_t &alpha,
45 Points_t &beta,
46 MinimaxResultType &result_type
47) {
48 bool is_prunable = (beta <= alpha);
49 if (is_prunable) {
51 }
52 return is_prunable;
53}
54} // namespace minimaxutils_forconcepts
55
59template <SpaceInfoProviderConcept G, PieceValueProviderConcept P>
61private:
62 // SpaceInfoProviderConcept &game_board_;
63 // PieceValueProviderConcept &game_position_points_;
64 std::shared_ptr<G> game_board_;
65 std::shared_ptr<P> game_position_points_;
66
67public:
69 // SpaceInfoProviderConcept &game_board,
70 // PieceValueProviderConcept &game_position_points
71 std::shared_ptr<G> game_board,
72 std::shared_ptr<P> game_position_points
73 )
74 : game_board_{game_board}
75 , game_position_points_{game_position_points} {}
76
77 ScoredMove RateMove(Move move, PieceColor cur_player) {
78 auto piece_type = game_board_->GetType(move.start);
79
80 auto end_points = game_position_points_
81 ->GetValueOfPieceAtPosition(cur_player, piece_type, move.end);
82 auto start_points = game_position_points_->GetValueOfPieceAtPosition(
83 cur_player,
84 piece_type,
85 move.start
86 );
87 auto position_value_delta = end_points - start_points;
88
89 Points_t capture_val;
90
91 if (game_board_->GetColor(move.end) == opponent_of(cur_player)) {
92 auto captured_piece_type = game_board_->GetType(move.end);
93 capture_val = game_position_points_->GetValueOfPieceAtPosition(
94 opponent_of(cur_player),
95 captured_piece_type,
96 move.end
97 );
98 } else {
99 capture_val = 0;
100 }
101
102 return ScoredMove{move, (position_value_delta + capture_val)};
103 }
104
105 std::vector<ScoredMove> GenerateRankedMoveList(
106 PieceColor cur_player,
107 const MoveCollection &cur_player_moves
108 ) {
109 vector<ScoredMove> rated_moves;
110 for (auto cur_move : cur_player_moves.moves) {
111 auto cur_rated_move = RateMove(cur_move, cur_player);
112 rated_moves.emplace_back(cur_rated_move);
113 }
114 sort(
115 rated_moves.begin(),
116 rated_moves.end(),
117 [](const ScoredMove &move_a, const ScoredMove &move_b) {
118 return move_a.score > move_b.score;
119 }
120 );
121 return rated_moves;
122 }
123};
124
128template <
133
135 std::shared_ptr<P> game_position_points_;
136 std::shared_ptr<H> hash_calculator_;
137 std::shared_ptr<G> game_board_;
142 std::atomic<bool> game_over_;
143
144public:
146 PieceColor evaluating_player,
148 std::shared_ptr<G> game_board,
149 std::shared_ptr<P> game_position_points,
150 std::shared_ptr<H> hash_calculator
151 )
152 : evaluating_player_{evaluating_player}
154 , game_board_{game_board}
155 , game_position_points_{game_position_points}
160 , game_over_{false} {
161 // initialize_hash_calculator();
162 }
163
164 using KeyType = typename H::KeyType;
165
167 throw std::runtime_error("MinimaxMoveEvaluator selected an illegal move");
168 }
169
170 Move SelectMove(const MoveCollection &allowed_moves) {
171
172 auto final_selected_move = SelectValidMove(allowed_moves);
174 return final_selected_move;
175 }
176
177 inline const std::optional<moveselection::SearchSummaries> search_summaries(
178 ) const override {
179 return search_summaries_;
180 }
181
183
184 inline size_t KeySizeBits() { return 8 * sizeof(KeyType); }
185
186 const H &hash_calculator() const { return hash_calculator_; }
187
188 const uint32_t zkeys_seed() { return hash_calculator_->zkeys_seed(); }
189
190 // const std::string board_state_hex_str() {
191 // return hash_calculator_->board_state_hex_str();
192 // }
193
194private:
195 // void initialize_hash_calculator() {
196 // game_board_->AttachMoveCallback(hash_calculator_.UpdateBoardState);
197 // hash_calculator_->FullBoardStateCalc(game_board_->map());
198 // }
199
200 Move SelectValidMove(const MoveCollection &allowed_moves) {
201 auto &first_search_summary = RunFirstSearch(allowed_moves);
202 if (minimaxutils_forconcepts::ValidateMove(first_search_summary, allowed_moves)) {
203 return first_search_summary.selected_move();
204 }
205 return RunSecondSearch(allowed_moves).selected_move();
206 }
207
209 Points_t pre_attack_total = 0;
210 for (auto space : game_board_->GetAllSpacesOccupiedBy(color)) {
211 auto piece_type = game_board_->GetType(space);
212 pre_attack_total +=
213 game_position_points_->GetValueOfPieceAtPosition(color, piece_type, space);
214 }
215 return pre_attack_total;
216 }
217
219 auto &first_search_summary = search_summaries_.NewFirstSearch(
221 hash_calculator_->GetTrTableSize()
222 );
223 GetMinimaxMoveAndStats(allowed_moves, first_search_summary);
224
225 return first_search_summary;
226 };
227
229 auto &second_search_summary = search_summaries_.NewExtraSearch(
232 hash_calculator_->GetTrTableSize()
233 );
234 GetMinimaxMoveAndStats(allowed_moves, second_search_summary, false);
235
236 return second_search_summary;
237 }
238
241 hash_calculator_->UpdateMoveCounter();
242 }
243
245 SearchSummary &search_summary,
246 MinimaxResultType &result_type,
247 TranspositionTableSearchResult &tr_table_search_result,
249 ) {
250 result_type = MinimaxResultType::kTrTableHit;
251 search_summary.RecordTrTableHit(tr_table_search_result, search_depth);
252 return tr_table_search_result.score_and_moves();
253 }
254
256 PieceColor cur_player,
257 MinimaxResultType &result_type
258 ) {
259 auto empty_best_moves = MoveCollection();
260
261 if (game_board_->IsDraw()) {
262 result_type = MinimaxResultType::kDraw;
263 return EqualScoreMoves{0, empty_best_moves};
264 }
265
266 if (cur_player == evaluating_player_) {
268 return EqualScoreMoves{numeric_limits<Points_t>::min(), empty_best_moves};
269 } else {
271 return EqualScoreMoves{numeric_limits<Points_t>::max(), empty_best_moves};
272 }
273 }
274
276 PieceColor cur_player,
277 SearchSummary &search_summary,
278 MinimaxResultType &result_type,
280 ) {
281 auto result = EvaluateEndOfGameLeaf(cur_player, result_type);
283 ->RecordTrData(search_depth, result_type, result, num_move_selections_);
284 search_summary.RecordNodeInfo(result_type, search_depth, result);
285 return result;
286 }
287
289 PieceColor cur_player,
290 MinimaxResultType &result_type
291 ) {
293
294 auto cur_player_points = GetPlayerTotal(cur_player);
295 auto opponent_points = GetPlayerTotal(opponent_of(cur_player));
296
297 auto empty_move_collection = MoveCollection();
298
299 if (cur_player == evaluating_player_) {
300 return EqualScoreMoves{
301 (cur_player_points - opponent_points),
302 empty_move_collection
303 };
304 } else {
305 return EqualScoreMoves{
306 (opponent_points - cur_player_points),
307 empty_move_collection
308 };
309 }
310 };
311
313 PieceColor cur_player,
314 SearchSummary &search_summary,
315 MinimaxResultType &result_type,
317 ) {
318 auto result = EvaluateNonWinLeaf(cur_player, result_type);
320 ->RecordTrData(search_depth, result_type, result, num_move_selections_);
321 search_summary.RecordNodeInfo(result_type, search_depth, result);
322
323 return result;
324 }
325
326 inline bool IsImprovement(
327 Points_t cur_eval,
328 Points_t previous_best_eval,
329 PieceColor cur_player
330 ) {
331 if (cur_player == evaluating_player_) {
332 return cur_eval > previous_best_eval;
333 } else {
334 return cur_eval < previous_best_eval;
335 }
336 }
337
339 PieceColor cur_player,
340 Move move,
341 MoveCollection &best_moves,
342 Points_t cur_eval,
343 Points_t &previous_best_eval
344 ) {
345 if (cur_eval == previous_best_eval) {
346 best_moves.Append(move);
347 } else if (IsImprovement(cur_eval, previous_best_eval, cur_player)) {
348 previous_best_eval = cur_eval;
349 best_moves.moves.clear();
350 best_moves.Append(move);
351 }
352 }
353
355 MinimaxResultType &result_type,
356 Points_t best_eval,
357 MoveCollection best_moves,
359 SearchSummary &search_summary
360 ) {
361
362 // If result_type is still unknown by the time FinalizeNodeResult is called, then we
363 // have evaluated all moves from (i.e. children of) the node. Can achieve this for
364 // root node or interior node, but not for a leaf node.
365 if (result_type == MinimaxResultType::kUnknown) {
367 }
368 EqualScoreMoves result{best_eval, best_moves};
370 ->RecordTrData(search_depth, result_type, result, num_move_selections_);
371 search_summary.RecordNodeInfo(result_type, search_depth, result);
372 return result;
373 }
374
375 inline bool IsPrunable(
376 Points_t &alpha,
377 Points_t &beta,
378 MinimaxResultType &result_type,
379 PieceColor cur_player
380 ) {
381 if (cur_player == evaluating_player_) {
382 return minimaxutils_forconcepts::IsPrunableForEvaluator(alpha, beta, result_type);
383 } else {
385 alpha,
386 beta,
387 result_type
388 );
389 }
390 }
391
393 Move move,
394 PieceColor cur_player,
395 const MoveCollection &allowed_moves,
397 Points_t alpha,
398 Points_t beta,
399 SearchSummary &search_summary,
400 bool use_transposition_table
401 ) {
402 auto executed_move = game_board_->ExecuteMove(move);
403 auto new_allowed_moves = game_board_->CalcFinalMovesOf(opponent_of(cur_player));
404 auto cur_eval = MinimaxRecursive(
405 new_allowed_moves,
406 search_depth - 1,
407 alpha,
408 beta,
409 opponent_of(cur_player),
410 search_summary,
411 use_transposition_table
412 )
414 game_board_->UndoMove(executed_move);
415 return cur_eval;
416 }
417
419 if (cur_player == evaluating_player_) {
420 return numeric_limits<Points_t>::min();
421 } else {
422 return numeric_limits<Points_t>::max();
423 }
424 }
425
427 Points_t &alpha,
428 Points_t &beta,
429 Points_t cur_eval,
430 PieceColor cur_player
431 ) {
432 if (cur_player == evaluating_player_) {
434 } else {
436 }
437 }
438
440 PieceColor cur_player,
441 const MoveCollection &allowed_moves,
443 Points_t &alpha,
444 Points_t &beta,
445 MinimaxResultType result_type,
446 SearchSummary &search_summary,
447 bool use_transposition_table
448 ) {
449 auto max_eval = InitializedBestEval(cur_player);
450 MoveCollection best_moves;
451 auto ranked_moves = move_sorter_.GenerateRankedMoveList(cur_player, allowed_moves);
452
453 for (auto rated_move : ranked_moves) {
454 auto cur_eval = RecursivelyVisitNodes(
455 rated_move.move,
456 cur_player,
457 allowed_moves,
459 alpha,
460 beta,
461 search_summary,
462 use_transposition_table
463 );
464
465 UpdateBestMoves(cur_player, rated_move.move, best_moves, cur_eval, max_eval);
466
467 UpdatePruningParam(alpha, beta, cur_eval, cur_player);
468
469 if (IsPrunable(alpha, beta, result_type, cur_player)) {
470 break;
471 }
472 }
473
474 return FinalizeNodeResult(
475 result_type,
476 max_eval,
477 best_moves,
479 search_summary
480 );
481 }
482
484 const MoveCollection &allowed_moves,
486 Points_t alpha,
487 Points_t beta,
488 PieceColor cur_player,
489 SearchSummary &search_summary,
490 bool use_transposition_table
491 ) {
492 MinimaxResultType result_type{};
493
494 // Check if result for current board state is in transposition table
495 // (unless this is a second search in which case we don't use transposition table)
496 if (use_transposition_table) {
497 auto tr_table_search_result =
499 if (tr_table_search_result.found() &&
500 tr_table_search_result.IsConsistentWith(allowed_moves)) {
501 return HandleTrTableHit(
502 search_summary,
503 result_type,
504 tr_table_search_result,
506 );
507 }
508 }
509
510 // If no legal moves, node is an end-of-game leaf
511 if (allowed_moves.moves.size() == 0) {
512 return HandleEndOfGame(cur_player, search_summary, result_type, search_depth);
513 }
514
515 // If remaining search depth is zero, treat node as "normal" leaf (not end of game,
516 // just end of search)
517 if (search_depth == 0) {
518 return HandleLeaf(cur_player, search_summary, result_type, search_depth);
519 }
520
521 // If not end-of-game, nor end-of-search, treat node as internal node.
522 return HandleInternalNode(
523 cur_player,
524 allowed_moves,
526 alpha,
527 beta,
528 result_type,
529 search_summary,
530 use_transposition_table
531 );
532 }
533
535 const MoveCollection &allowed_moves,
536 SearchSummary &search_summary,
537 bool use_transposition_table = true
538 ) {
539 auto search_start = std::chrono::high_resolution_clock::now();
540 auto minimax_result = MinimaxRecursive(
541 allowed_moves,
543 numeric_limits<Points_t>::min(),
544 numeric_limits<Points_t>::max(),
546 search_summary,
547 use_transposition_table
548 );
549 auto search_end = std::chrono::high_resolution_clock::now();
550 std::chrono::duration<double, std::nano> search_time = search_end - search_start;
551 search_summary.set_time(search_time);
552
553 return minimax_result;
554 }
555
557 const MoveCollection &allowed_moves,
558 SearchSummary &search_summary,
559 bool use_transposition_table = true
560 ) {
561 auto minimax_result =
562 RunTimedMinimax(allowed_moves, search_summary, use_transposition_table);
563 auto selected_move = minimax_result.move_collection().SelectRandom();
564 search_summary.SetSelectedMove(selected_move);
565 search_summary.set_tr_table_size_final(hash_calculator_->GetTrTableSize());
566 }
567};
568
569} // namespace moveselection
Constants, typedefs, and simple structs used by gameboard::GameBoard.
Holds a gameboard::MoveCollection in which all gameboard::Move have the same value (as perceived by a...
Complies with MoveEvaluatorConcept, and selects move::Move using Minimax algorithm; uses SpaceInfoPro...
EqualScoreMoves EvaluateEndOfGameLeaf(PieceColor cur_player, MinimaxResultType &result_type)
Points_t RecursivelyVisitNodes(Move move, PieceColor cur_player, const MoveCollection &allowed_moves, DepthType search_depth, Points_t alpha, Points_t beta, SearchSummary &search_summary, bool use_transposition_table)
bool IsPrunable(Points_t &alpha, Points_t &beta, MinimaxResultType &result_type, PieceColor cur_player)
EqualScoreMoves RunTimedMinimax(const MoveCollection &allowed_moves, SearchSummary &search_summary, bool use_transposition_table=true)
void UpdateBestMoves(PieceColor cur_player, Move move, MoveCollection &best_moves, Points_t cur_eval, Points_t &previous_best_eval)
bool IsImprovement(Points_t cur_eval, Points_t previous_best_eval, PieceColor cur_player)
SearchSummary & RunSecondSearch(const MoveCollection &allowed_moves)
EqualScoreMoves FinalizeNodeResult(MinimaxResultType &result_type, Points_t best_eval, MoveCollection best_moves, DepthType search_depth, SearchSummary &search_summary)
SearchSummary & RunFirstSearch(const MoveCollection &allowed_moves)
EqualScoreMoves EvaluateNonWinLeaf(PieceColor cur_player, MinimaxResultType &result_type)
MinimaxMoveEvaluatorForConcepts(PieceColor evaluating_player, DepthType search_depth, std::shared_ptr< G > game_board, std::shared_ptr< P > game_position_points, std::shared_ptr< H > hash_calculator)
EqualScoreMoves MinimaxRecursive(const MoveCollection &allowed_moves, DepthType search_depth, Points_t alpha, Points_t beta, PieceColor cur_player, SearchSummary &search_summary, bool use_transposition_table)
void UpdatePruningParam(Points_t &alpha, Points_t &beta, Points_t cur_eval, PieceColor cur_player)
const std::optional< moveselection::SearchSummaries > search_summaries() const override
EqualScoreMoves HandleInternalNode(PieceColor cur_player, const MoveCollection &allowed_moves, DepthType &search_depth, Points_t &alpha, Points_t &beta, MinimaxResultType result_type, SearchSummary &search_summary, bool use_transposition_table)
void GetMinimaxMoveAndStats(const MoveCollection &allowed_moves, SearchSummary &search_summary, bool use_transposition_table=true)
EqualScoreMoves HandleLeaf(PieceColor cur_player, SearchSummary &search_summary, MinimaxResultType &result_type, DepthType search_depth)
EqualScoreMoves HandleEndOfGame(PieceColor cur_player, SearchSummary &search_summary, MinimaxResultType &result_type, DepthType search_depth)
EqualScoreMoves HandleTrTableHit(SearchSummary &search_summary, MinimaxResultType &result_type, TranspositionTableSearchResult &tr_table_search_result, DepthType search_depth)
Sorts moves based on points changed induce by single move; used by MinimaxMoveEvaluator for pre-sorti...
std::vector< ScoredMove > GenerateRankedMoveList(PieceColor cur_player, const MoveCollection &cur_player_moves)
ScoredMove RateMove(Move move, PieceColor cur_player)
PreSearchMoveSorterForConcepts(std::shared_ptr< G > game_board, std::shared_ptr< P > game_position_points)
Stores data collected during a single call to moveselection::MinimaxMoveEvaluator....
void set_time(std::chrono::duration< double, std::nano > search_time)
void RecordNodeInfo(MinimaxResultType result_type, DepthType search_depth, const EqualScoreMoves &equal_score_moves)
void RecordTrTableHit(TranspositionTableSearchResult &tr_table_search_result, DepthType remaining_search_depth)
void set_tr_table_size_final(size_t tr_table_size_final)
void SetSelectedMove(Move selected_move)
Container for storing a moveselection::MinimaxCalcResult retrieved by a call to boardstate::SingleZob...
Data structs used by moveselection::MinimaxEvaluator.
uint16_t DepthType
uint16_t MoveCountType
int Points_t
Tracking piece positions and determining legal moves.
PieceColor opponent_of(PieceColor color)
bool IsPrunableForEvaluator(Points_t &alpha, Points_t &beta, MinimaxResultType &result_type)
void UpdateAlpha(Points_t &alpha, Points_t cur_eval)
bool IsPrunableForEvaluatorOpponent(Points_t &alpha, Points_t &beta, MinimaxResultType &result_type)
bool ValidateMove(SearchSummary &search_summary, const MoveCollection &allowed_moves)
void UpdateBeta(Points_t &beta, Points_t cur_eval)
Selecting a move to execute.
A container for multiple gameboard::Move objects.
A gameboard::BoardSpace pair (start and end).
gameboard::BoardSpace end
gameboard::BoardSpace start
A gameboard::Move, and an associated score calculated by a MoveEvaluator.
Stores a moveselection::SearchSummary for each moveselection::MinimaxMoveEvaluator....
SearchSummary & NewFirstSearch(DepthType search_depth, size_t tr_table_size_initial)
SearchSummary & NewExtraSearch(DepthType search_depth, MoveCountType search_number, size_t tr_table_size_current)