Xiangiqgame
AI engine for Xiangqi
Loading...
Searching...
No Matches
move_evaluators.hpp
Go to the documentation of this file.
1
3
4#pragma once
5
9
10#include <array>
11#include <atomic>
15#include <functional>
16#include <limits>
20#include <unordered_map>
22
23using namespace gameboard;
24
26namespace moveselection {
27
29namespace minimaxutils {
30bool ValidateMove(SearchSummary &search_summary, const MoveCollection &allowed_moves);
31
32inline void UpdateAlpha(Points_t &alpha, Points_t cur_eval) {
33 alpha = max(alpha, cur_eval);
34}
35
36inline void UpdateBeta(Points_t &beta, Points_t cur_eval) { beta = min(beta, cur_eval); }
37
39 Points_t &alpha,
40 Points_t &beta,
41 MinimaxResultType &result_type
42) {
43 bool is_prunable = (beta <= alpha);
44 if (is_prunable) {
46 }
47 return is_prunable;
48}
49
51 Points_t &alpha,
52 Points_t &beta,
53 MinimaxResultType &result_type
54) {
55 bool is_prunable = (beta <= alpha);
56 if (is_prunable) {
58 }
59 return is_prunable;
60}
61
62} // namespace minimaxutils
63
67template <typename ConcreteSpaceInfoProvider, typename ConcretePieceValueProvider>
69public:
71 ConcreteSpaceInfoProvider &game_board,
72 ConcretePieceValueProvider &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
124private:
125 ConcreteSpaceInfoProvider &game_board_;
126 ConcretePieceValueProvider &game_position_points_;
127};
128
132template <
133 typename ConcreteSpaceInfoProvider,
134 typename ConcreteBoardStateCoordinator,
135 typename ConcretePieceValueProvider>
136class MinimaxMoveEvaluator : public MoveEvaluator<MinimaxMoveEvaluator<
137 ConcreteSpaceInfoProvider,
138 ConcreteBoardStateCoordinator,
139 ConcretePieceValueProvider>> {
140
142 ConcretePieceValueProvider game_position_points_;
143 ConcreteBoardStateCoordinator hash_calculator_;
144 ConcreteSpaceInfoProvider &game_board_;
150 std::atomic<bool> game_over_;
151
152public:
154 PieceColor evaluating_player,
156 ConcreteSpaceInfoProvider &game_board,
157 uint32_t zkey_seed = std::random_device{}(),
158 const ConcretePieceValueProvider &game_position_points =
159 ConcretePieceValueProvider()
160 )
161 : evaluating_player_{evaluating_player}
163 , game_board_{game_board}
164 , game_position_points_{game_position_points}
165 , hash_calculator_{ConcreteBoardStateCoordinator{zkey_seed}}
168 , move_sorter_{PreSearchMoveSorter{game_board_, game_position_points_}}
169 , game_over_{false} {
171 }
172
174 PieceColor evaluating_player,
176 ConcreteSpaceInfoProvider &game_board,
177 const ConcretePieceValueProvider &game_position_points,
178 ConcreteBoardStateCoordinator &hash_calculator,
180 &move_sorter
181 )
182 : evaluating_player_{evaluating_player}
184 , game_board_{game_board}
185 , game_position_points_{game_position_points}
189 , move_sorter_{move_sorter}
190 , game_over_{false} {
192 }
193
195
196 auto final_selected_move = SelectValidMove(allowed_moves);
198 return final_selected_move;
199 }
200
202 return search_summaries_;
203 }
204
206
207 inline size_t KeySizeBits() {
208 return 8 * sizeof(typename ConcreteBoardStateCoordinator::ZobristKey_t);
209 }
210
211 const ConcreteBoardStateCoordinator &hash_calculator() const {
212 return hash_calculator_;
213 }
214
215 const uint32_t zkeys_seed() { return hash_calculator_.zkeys_seed(); }
216
217 const std::string board_state_hex_str() {
218 return hash_calculator_.board_state_hex_str();
219 }
220
221private:
223 game_board_.AttachMoveCallback(std::bind(
224 &ConcreteBoardStateCoordinator::UpdateBoardState,
226 std::placeholders::_1
227 ));
228 hash_calculator_.FullBoardStateCalc(game_board_.map());
229 }
230
231 Move SelectValidMove(const MoveCollection &allowed_moves) {
232 auto &first_search_summary = RunFirstSearch(allowed_moves);
233 if (minimaxutils::ValidateMove(first_search_summary, allowed_moves)) {
234 return first_search_summary.selected_move();
235 }
236 return RunSecondSearch(allowed_moves).selected_move();
237 }
238
240 Points_t pre_attack_total = 0;
241 for (auto space : game_board_.GetAllSpacesOccupiedBy(color)) {
242 auto piece_type = game_board_.GetType(space);
243 pre_attack_total +=
244 game_position_points_.GetValueOfPieceAtPosition(color, piece_type, space);
245 }
246 return pre_attack_total;
247 }
248
250 auto &first_search_summary = search_summaries_.NewFirstSearch(
252 hash_calculator_.GetTrTableSize()
253 );
254 GetMinimaxMoveAndStats(allowed_moves, first_search_summary);
255
256 return first_search_summary;
257 };
258
260 auto &second_search_summary = search_summaries_.NewExtraSearch(
263 hash_calculator_.GetTrTableSize()
264 );
265 GetMinimaxMoveAndStats(allowed_moves, second_search_summary, false);
266
267 return second_search_summary;
268 }
269
272 hash_calculator_.UpdateMoveCounter();
273 }
274
276 SearchSummary &search_summary,
277 MinimaxResultType &result_type,
278 TranspositionTableSearchResult &tr_table_search_result,
280 ) {
281 result_type = MinimaxResultType::kTrTableHit;
282 search_summary.RecordTrTableHit(tr_table_search_result, search_depth);
283 return tr_table_search_result.score_and_moves();
284 }
285
287 PieceColor cur_player,
288 MinimaxResultType &result_type
289 ) {
290 auto empty_best_moves = MoveCollection();
291
292 if (game_board_.IsDraw()) {
293 result_type = MinimaxResultType::kDraw;
294 return EqualScoreMoves{0, empty_best_moves};
295 }
296
297 if (cur_player == evaluating_player_) {
299 return EqualScoreMoves{numeric_limits<Points_t>::min(), empty_best_moves};
300 } else {
302 return EqualScoreMoves{numeric_limits<Points_t>::max(), empty_best_moves};
303 }
304 }
305
307 PieceColor cur_player,
308 SearchSummary &search_summary,
309 MinimaxResultType &result_type,
311 ) {
312 auto result = EvaluateEndOfGameLeaf(cur_player, result_type);
314 .RecordTrData(search_depth, result_type, result, num_move_selections_);
315 search_summary.RecordNodeInfo(result_type, search_depth, result);
316 return result;
317 }
318
320 PieceColor cur_player,
321 MinimaxResultType &result_type
322 ) {
324
325 auto cur_player_points = GetPlayerTotal(cur_player);
326 auto opponent_points = GetPlayerTotal(opponent_of(cur_player));
327
328 auto empty_move_collection = MoveCollection();
329
330 if (cur_player == evaluating_player_) {
331 return EqualScoreMoves{
332 (cur_player_points - opponent_points),
333 empty_move_collection
334 };
335 } else {
336 return EqualScoreMoves{
337 (opponent_points - cur_player_points),
338 empty_move_collection
339 };
340 }
341 };
342
344 PieceColor cur_player,
345 SearchSummary &search_summary,
346 MinimaxResultType &result_type,
348 ) {
349 auto result = EvaluateNonWinLeaf(cur_player, result_type);
351 .RecordTrData(search_depth, result_type, result, num_move_selections_);
352 search_summary.RecordNodeInfo(result_type, search_depth, result);
353
354 return result;
355 }
356
357 inline bool IsImprovement(
358 Points_t cur_eval,
359 Points_t previous_best_eval,
360 PieceColor cur_player
361 ) {
362 if (cur_player == evaluating_player_) {
363 return cur_eval > previous_best_eval;
364 } else {
365 return cur_eval < previous_best_eval;
366 }
367 }
368
370 PieceColor cur_player,
371 Move move,
372 MoveCollection &best_moves,
373 Points_t cur_eval,
374 Points_t &previous_best_eval
375 ) {
376 if (cur_eval == previous_best_eval) {
377 best_moves.Append(move);
378 } else if (IsImprovement(cur_eval, previous_best_eval, cur_player)) {
379 previous_best_eval = cur_eval;
380 best_moves.moves.clear();
381 best_moves.Append(move);
382 }
383 }
384
386 MinimaxResultType &result_type,
387 Points_t best_eval,
388 MoveCollection best_moves,
390 SearchSummary &search_summary
391 ) {
392
393 // If result_type is still unknown by the time FinalizeNodeResult is called, then we
394 // have evaluated all moves from (i.e. children of) the node. Can achieve this for
395 // root node or interior node, but not for a leaf node.
396 if (result_type == MinimaxResultType::kUnknown) {
398 }
399 EqualScoreMoves result{best_eval, best_moves};
401 .RecordTrData(search_depth, result_type, result, num_move_selections_);
402 search_summary.RecordNodeInfo(result_type, search_depth, result);
403 return result;
404 }
405
406 inline bool IsPrunable(
407 Points_t &alpha,
408 Points_t &beta,
409 MinimaxResultType &result_type,
410 PieceColor cur_player
411 ) {
412 if (cur_player == evaluating_player_) {
413 return minimaxutils::IsPrunableForEvaluator(alpha, beta, result_type);
414 } else {
415 return minimaxutils::IsPrunableForEvaluatorOpponent(alpha, beta, result_type);
416 }
417 }
418
420 Move move,
421 PieceColor cur_player,
422 const MoveCollection &allowed_moves,
424 Points_t alpha,
425 Points_t beta,
426 SearchSummary &search_summary,
427 bool use_transposition_table
428 ) {
429 auto executed_move = game_board_.ExecuteMove(move);
430 auto new_allowed_moves = game_board_.CalcFinalMovesOf(opponent_of(cur_player));
431 auto cur_eval = MinimaxRecursive(
432 new_allowed_moves,
433 search_depth - 1,
434 alpha,
435 beta,
436 opponent_of(cur_player),
437 search_summary,
438 use_transposition_table
439 )
441 game_board_.UndoMove(executed_move);
442 return cur_eval;
443 }
444
446 if (cur_player == evaluating_player_) {
447 return numeric_limits<Points_t>::min();
448 } else {
449 return numeric_limits<Points_t>::max();
450 }
451 }
452
454 Points_t &alpha,
455 Points_t &beta,
456 Points_t cur_eval,
457 PieceColor cur_player
458 ) {
459 if (cur_player == evaluating_player_) {
460 minimaxutils::UpdateAlpha(alpha, cur_eval);
461 } else {
462 minimaxutils::UpdateBeta(beta, cur_eval);
463 }
464 }
465
467 PieceColor cur_player,
468 const MoveCollection &allowed_moves,
470 Points_t &alpha,
471 Points_t &beta,
472 MinimaxResultType result_type,
473 SearchSummary &search_summary,
474 bool use_transposition_table
475 ) {
476 auto max_eval = InitializedBestEval(cur_player);
477 MoveCollection best_moves;
478 auto ranked_moves = move_sorter_.GenerateRankedMoveList(cur_player, allowed_moves);
479
480 for (auto rated_move : ranked_moves) {
481 auto cur_eval = RecursivelyVisitNodes(
482 rated_move.move,
483 cur_player,
484 allowed_moves,
486 alpha,
487 beta,
488 search_summary,
489 use_transposition_table
490 );
491
492 UpdateBestMoves(cur_player, rated_move.move, best_moves, cur_eval, max_eval);
493
494 UpdatePruningParam(alpha, beta, cur_eval, cur_player);
495
496 if (IsPrunable(alpha, beta, result_type, cur_player)) {
497 break;
498 }
499 }
500
501 return FinalizeNodeResult(
502 result_type,
503 max_eval,
504 best_moves,
506 search_summary
507 );
508 }
509
511 const MoveCollection &allowed_moves,
513 Points_t alpha,
514 Points_t beta,
515 PieceColor cur_player,
516 SearchSummary &search_summary,
517 bool use_transposition_table
518 ) {
519 MinimaxResultType result_type{};
520
521 // Check if result for current board state is in transposition table
522 // (unless this is a second search in which case we don't use transposition table)
523 if (use_transposition_table) {
524 auto tr_table_search_result =
526 if (tr_table_search_result.found() &&
527 tr_table_search_result.IsConsistentWith(allowed_moves)) {
528 return HandleTrTableHit(
529 search_summary,
530 result_type,
531 tr_table_search_result,
533 );
534 }
535 }
536
537 // If no legal moves, node is an end-of-game leaf
538 if (allowed_moves.moves.size() == 0) {
539 return HandleEndOfGame(cur_player, search_summary, result_type, search_depth);
540 }
541
542 // If remaining search depth is zero, treat node as "normal" leaf (not end of game,
543 // just end of search)
544 if (search_depth == 0) {
545 return HandleLeaf(cur_player, search_summary, result_type, search_depth);
546 }
547
548 // If not end-of-game, nor end-of-search, treat node as internal node.
549 return HandleInternalNode(
550 cur_player,
551 allowed_moves,
553 alpha,
554 beta,
555 result_type,
556 search_summary,
557 use_transposition_table
558 );
559 }
560
562 const MoveCollection &allowed_moves,
563 SearchSummary &search_summary,
564 bool use_transposition_table = true
565 ) {
566 auto search_start = std::chrono::high_resolution_clock::now();
567 auto minimax_result = MinimaxRecursive(
568 allowed_moves,
570 numeric_limits<Points_t>::min(),
571 numeric_limits<Points_t>::max(),
573 search_summary,
574 use_transposition_table
575 );
576 auto search_end = std::chrono::high_resolution_clock::now();
577 std::chrono::duration<double, std::nano> search_time = search_end - search_start;
578 search_summary.set_time(search_time);
579
580 return minimax_result;
581 }
582
584 const MoveCollection &allowed_moves,
585 SearchSummary &search_summary,
586 bool use_transposition_table = true
587 ) {
588 auto minimax_result =
589 RunTimedMinimax(allowed_moves, search_summary, use_transposition_table);
590 auto selected_move = minimax_result.move_collection().SelectRandom();
591 search_summary.SetSelectedMove(selected_move);
592 search_summary.set_tr_table_size_final(hash_calculator_.GetTrTableSize());
593 }
594};
595
598template <typename ConcreteSpaceInfoProvider>
600 : public MoveEvaluator<RandomMoveEvaluator<ConcreteSpaceInfoProvider>> {
601public:
603 PieceColor evaluating_player,
604 ConcreteSpaceInfoProvider &game_board
605 )
606 : evaluating_player_{evaluating_player}
607 , game_board_{game_board} {}
608
610 auto selected_move_index =
611 utility_functs::random((size_t)0, allowed_moves.moves.size() - 1);
612 return allowed_moves.moves[selected_move_index];
613 }
614
615private:
617 ConcreteSpaceInfoProvider &game_board_;
618};
619
620} // namespace moveselection
Constants, typedefs, and simple structs used by gameboard::GameBoard.
Definition of BoardStateCoordinator.
CRTP interface with a method for selecting a gameboard::Move (concrete examples = moveselection::Mini...
Holds a gameboard::MoveCollection in which all gameboard::Move have the same value (as perceived by a...
Implements MoveEvaluator interface, and selects move::Move using Minimax algorithm; uses SpaceInfoPro...
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)
ConcreteSpaceInfoProvider & game_board_
ConcretePieceValueProvider game_position_points_
Points_t InitializedBestEval(PieceColor cur_player)
SearchSummary & RunFirstSearch(const MoveCollection &allowed_moves)
ConcreteBoardStateCoordinator hash_calculator_
void UpdateBestMoves(PieceColor cur_player, Move move, MoveCollection &best_moves, Points_t cur_eval, Points_t &previous_best_eval)
Points_t GetPlayerTotal(PieceColor color)
const moveselection::SearchSummaries & search_summaries()
moveselection::SearchSummaries search_summaries_
EqualScoreMoves HandleLeaf(PieceColor cur_player, SearchSummary &search_summary, MinimaxResultType &result_type, DepthType search_depth)
void UpdatePruningParam(Points_t &alpha, Points_t &beta, Points_t cur_eval, PieceColor cur_player)
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)
EqualScoreMoves FinalizeNodeResult(MinimaxResultType &result_type, Points_t best_eval, MoveCollection best_moves, DepthType search_depth, SearchSummary &search_summary)
SearchSummary & RunSecondSearch(const MoveCollection &allowed_moves)
void GetMinimaxMoveAndStats(const MoveCollection &allowed_moves, SearchSummary &search_summary, bool use_transposition_table=true)
EqualScoreMoves HandleEndOfGame(PieceColor cur_player, SearchSummary &search_summary, MinimaxResultType &result_type, DepthType search_depth)
bool IsImprovement(Points_t cur_eval, Points_t previous_best_eval, PieceColor cur_player)
EqualScoreMoves EvaluateNonWinLeaf(PieceColor cur_player, MinimaxResultType &result_type)
Move ImplementSelectMove(MoveCollection &allowed_moves)
MinimaxMoveEvaluator(PieceColor evaluating_player, DepthType search_depth, ConcreteSpaceInfoProvider &game_board, const ConcretePieceValueProvider &game_position_points, ConcreteBoardStateCoordinator &hash_calculator, PreSearchMoveSorter< ConcreteSpaceInfoProvider, ConcretePieceValueProvider > &move_sorter)
const ConcreteBoardStateCoordinator & hash_calculator() const
MinimaxMoveEvaluator(PieceColor evaluating_player, DepthType search_depth, ConcreteSpaceInfoProvider &game_board, uint32_t zkey_seed=std::random_device{}(), const ConcretePieceValueProvider &game_position_points=ConcretePieceValueProvider())
EqualScoreMoves HandleTrTableHit(SearchSummary &search_summary, MinimaxResultType &result_type, TranspositionTableSearchResult &tr_table_search_result, DepthType search_depth)
EqualScoreMoves RunTimedMinimax(const MoveCollection &allowed_moves, SearchSummary &search_summary, bool use_transposition_table=true)
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)
PreSearchMoveSorter< ConcreteSpaceInfoProvider, ConcretePieceValueProvider > move_sorter_
EqualScoreMoves EvaluateEndOfGameLeaf(PieceColor cur_player, MinimaxResultType &result_type)
Move SelectValidMove(const MoveCollection &allowed_moves)
bool IsPrunable(Points_t &alpha, Points_t &beta, MinimaxResultType &result_type, PieceColor cur_player)
Sorts moves based on points changed induce by single move; used by MinimaxMoveEvaluator for pre-sorti...
PreSearchMoveSorter(ConcreteSpaceInfoProvider &game_board, ConcretePieceValueProvider &game_position_points)
ConcreteSpaceInfoProvider & game_board_
ConcretePieceValueProvider & game_position_points_
std::vector< ScoredMove > GenerateRankedMoveList(PieceColor cur_player, const MoveCollection &cur_player_moves)
ScoredMove RateMove(Move move, PieceColor cur_player)
Implements gameboard::MoveEvaluator interface.
Move ImplementSelectMove(MoveCollection &allowed_moves)
RandomMoveEvaluator(PieceColor evaluating_player, ConcreteSpaceInfoProvider &game_board)
ConcreteSpaceInfoProvider & game_board_
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
Definition of MoveEvaluator interface.
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)
void UpdateBeta(Points_t &beta, Points_t cur_eval)
bool ValidateMove(SearchSummary &search_summary, const MoveCollection &allowed_moves)
Selecting a move to execute.
T random(T range_from, T range_to)
Definition of PieceValueProvider CRTP interface.
Definition of SpaceInfoProvider CRTP interface.
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)
Defiition of miscellaneous free functions (and implementation of those that are templates).