1
+ use std:: collections:: HashMap ;
1
2
use std:: time:: Instant ;
2
3
3
- use chess:: { Action , Board , BoardStatus , Color , MoveGen } ;
4
+ use chess:: { Action , Board , BoardStatus , ChessMove , Color , MoveGen } ;
4
5
5
6
use crate :: common:: constants:: modules:: * ;
6
7
use crate :: common:: utils:: { self , Stats } ;
7
8
9
+ use super :: utils:: { Evaluation , TranspositionEntry } ;
10
+
8
11
pub ( crate ) struct Algorithm {
9
12
pub ( crate ) modules : u32 ,
13
+ transposition_table : HashMap < Board , TranspositionEntry > ,
10
14
}
11
15
12
16
impl Algorithm {
13
17
pub ( crate ) fn new ( modules : u32 ) -> Self {
14
- Self { modules }
18
+ Self {
19
+ modules,
20
+ transposition_table : HashMap :: new ( ) ,
21
+ }
15
22
}
16
23
24
+ #[ allow( clippy:: too_many_arguments) ]
17
25
fn node_eval_recursive (
18
- & self ,
26
+ & mut self ,
19
27
board : & Board ,
20
28
depth : u32 ,
21
29
mut alpha : f32 ,
22
30
mut beta : f32 ,
23
31
original : bool ,
24
32
deadline : Option < Instant > ,
25
33
stats : & mut Stats ,
26
- ) -> ( Option < Action > , f32 , Option < Vec < String > > ) {
34
+ ) -> Evaluation {
27
35
if depth == 0 {
28
36
stats. leaves_visited += 1 ;
29
- return ( None , self . eval ( board) , None ) ;
37
+ return Evaluation :: new ( self . eval ( board) , None , None ) ;
30
38
}
31
39
32
40
// Whether we should try to maximise the eval
33
41
let maximise: bool = board. side_to_move ( ) == Color :: White ;
34
- let mut best_eval = ( None , if maximise { f32:: MIN } else { f32:: MAX } , None ) ;
42
+ let mut best_evaluation =
43
+ Evaluation :: new ( if maximise { f32:: MIN } else { f32:: MAX } , None , None ) ;
35
44
36
45
let legal_moves = MoveGen :: new_legal ( board) ;
37
46
let num_legal_moves = legal_moves. len ( ) ;
38
- if num_legal_moves == 0 && board. checkers ( ) . popcnt ( ) == 0 {
39
- // Is Stalemate, no checking pieces
40
- best_eval = ( None , 0. , None )
47
+ if num_legal_moves == 0 {
48
+ if board. checkers ( ) . popcnt ( ) == 0 {
49
+ // Is Stalemate, no checking pieces
50
+ best_evaluation. eval = 0. ;
51
+ }
52
+ // If we arrive at here and it is checkmate, then we know that the side playing
53
+ // has been checkmated, and therefore the current `best_eval` is correct. Because if we tried to
54
+ // maximise, we failed, and if trying to minimise, we failed and therefore get the
55
+ // lowest/highest eval
56
+ println ! ( "The thing happened" ) ;
57
+ return best_evaluation;
41
58
}
42
- // If we arrive at here and it is checkmate, then we know that the side playing
43
- // has been checkmated, and therefore the current `best_eval` is correct. Because if we tried to
44
- // maximise, we failed, and if trying to minimise, we failed and therefore get the
45
- // lowest/highest eval
46
59
47
- for ( i, chess_move) in legal_moves. enumerate ( ) {
60
+ let mut boards = legal_moves
61
+ . map ( |chess_move| {
62
+ let board = board. make_move_new ( chess_move) ;
63
+ let mut transposition_entry = None ;
64
+ if self . modules & TRANSPOSITION_TABLE != 0 {
65
+ transposition_entry = self . transposition_table . get ( & board) ;
66
+ }
67
+ ( chess_move, board, transposition_entry)
68
+ } )
69
+ . collect :: < Vec < ( ChessMove , Board , Option < & TranspositionEntry > ) > > ( ) ;
70
+
71
+ // Sort by eval
72
+ boards. sort_by ( |board1, board2| {
73
+ let ordering = board1
74
+ . 2
75
+ . map_or ( 0. , |entry| entry. eval )
76
+ . partial_cmp ( & board2. 2 . map_or ( 0. , |entry| entry. eval ) )
77
+ . expect ( "Eval is a valid value" ) ;
78
+
79
+ if !maximise {
80
+ return ordering. reverse ( ) ;
81
+ }
82
+ ordering
83
+ } ) ;
84
+
85
+ for ( i, ( chess_move, new_board, transposition_entry) ) in boards. iter ( ) . enumerate ( ) {
48
86
if deadline. map_or ( false , |deadline| {
49
87
!Instant :: now ( ) . saturating_duration_since ( deadline) . is_zero ( )
50
88
} ) {
@@ -53,57 +91,55 @@ impl Algorithm {
53
91
// node on our layer
54
92
stats. progress_on_next_layer *= 1. / num_legal_moves as f32 ;
55
93
stats. progress_on_next_layer += i as f32 / num_legal_moves as f32 ;
56
- return best_eval ;
94
+ return best_evaluation ;
57
95
} ;
58
96
59
- let new_position = board. make_move_new ( chess_move) ;
60
- let eval = self . node_eval_recursive (
61
- & new_position,
62
- depth - 1 ,
63
- alpha,
64
- beta,
65
- false ,
66
- deadline,
67
- stats,
68
- ) ;
97
+ let evaluation = if transposition_entry. is_none ( )
98
+ || transposition_entry. unwrap ( ) . depth < depth
99
+ {
100
+ self . node_eval_recursive ( new_board, depth - 1 , alpha, beta, false , deadline, stats)
101
+ } else {
102
+ Evaluation :: new (
103
+ transposition_entry. unwrap ( ) . eval ,
104
+ transposition_entry. unwrap ( ) . next_action ,
105
+ None ,
106
+ )
107
+ } ;
69
108
stats. nodes_visited += 1 ;
70
109
71
- if maximise && eval. 1 > best_eval. 1 || !maximise && eval. 1 < best_eval. 1 {
110
+ // Replace best_eval if ours is better
111
+ if maximise && evaluation. eval > best_evaluation. eval
112
+ || !maximise && evaluation. eval < best_evaluation. eval
113
+ {
72
114
if original && self . modules & ANALYZE != 0 {
73
115
let mut vec = Vec :: new ( ) ;
74
116
let new_best_move = chess_move. to_string ( ) ;
75
- let new_best_eval = eval . 1 ;
117
+ let new_best_eval = evaluation . eval ;
76
118
utils:: vector_push_debug!(
77
119
vec,
78
120
self . modules,
79
121
maximise,
80
- best_eval . 1 ,
122
+ best_evaluation . eval ,
81
123
new_best_move,
82
124
new_best_eval,
83
125
) ;
84
- if let Some ( Action :: MakeMove ( previous_best_move) ) = best_eval. 0 {
126
+ if let Some ( Action :: MakeMove ( previous_best_move) ) = best_evaluation. next_action
127
+ {
85
128
let previous_best_move = previous_best_move. to_string ( ) ;
86
129
utils:: vector_push_debug!( vec, previous_best_move) ;
87
130
}
88
- best_eval . 2 = Some ( vec) ;
131
+ best_evaluation . debug_data = Some ( vec) ;
89
132
}
90
133
91
- best_eval . 1 = eval . 1 ;
92
- best_eval . 0 = Some ( Action :: MakeMove ( chess_move) ) ;
134
+ best_evaluation . eval = evaluation . eval ;
135
+ best_evaluation . next_action = Some ( Action :: MakeMove ( * chess_move) ) ;
93
136
}
94
137
95
138
if self . modules & ALPHA_BETA != 0 {
96
139
if maximise {
97
- // if eval.1 > alpha {
98
- // println!("Alpha changed. {} -> {}. Beta: {}", alpha, eval.1, beta);
99
- // }
100
- alpha = alpha. max ( eval. 1 ) ;
140
+ alpha = alpha. max ( evaluation. eval ) ;
101
141
} else {
102
- // if eval.1 < beta {
103
- // println!("Beta changed. {} -> {}. Alpha: {}", beta, eval.1, alpha);
104
- // }
105
-
106
- beta = beta. min ( eval. 1 ) ;
142
+ beta = beta. min ( evaluation. eval ) ;
107
143
}
108
144
109
145
if alpha > beta {
@@ -113,35 +149,50 @@ impl Algorithm {
113
149
}
114
150
}
115
151
116
- best_eval
152
+ self . transposition_table . insert (
153
+ * board,
154
+ TranspositionEntry :: new ( depth, best_evaluation. eval , best_evaluation. next_action ) ,
155
+ ) ;
156
+ best_evaluation
117
157
}
118
158
119
159
fn next_action_internal (
120
- & self ,
160
+ & mut self ,
121
161
board : & Board ,
122
162
depth : u32 ,
123
163
deadline : Option < Instant > ,
124
164
) -> ( chess:: Action , Vec < String > , Stats ) {
125
165
let mut stats = Stats :: default ( ) ;
126
166
let out =
127
167
self . node_eval_recursive ( board, depth, f32:: MIN , f32:: MAX , true , deadline, & mut stats) ;
128
- let action = out. 0 . unwrap_or ( Action :: Resign ( board. side_to_move ( ) ) ) ;
129
- let analyzer_data = out. 2 . unwrap_or_default ( ) ;
168
+ let action = if out. next_action . is_none ( ) {
169
+ match board. status ( ) {
170
+ BoardStatus :: Ongoing => {
171
+ panic ! ( "No action returned by algorithm even though game is still ongoing" )
172
+ }
173
+ BoardStatus :: Stalemate => Action :: DeclareDraw ,
174
+ BoardStatus :: Checkmate => Action :: Resign ( board. side_to_move ( ) ) ,
175
+ }
176
+ } else {
177
+ out. next_action . unwrap ( )
178
+ } ;
179
+ let analyzer_data = out. debug_data . unwrap_or_default ( ) ;
130
180
( action, analyzer_data, stats)
131
181
}
132
182
133
183
pub ( crate ) fn next_action (
134
- & self ,
184
+ & mut self ,
135
185
board : & Board ,
136
186
deadline : Instant ,
137
187
) -> ( chess:: Action , Vec < String > , Stats ) {
188
+ // Guarantee that at least the first layer gets done.
138
189
const START_DEPTH : u32 = 1 ;
139
190
let mut deepest_complete_output = self . next_action_internal ( board, START_DEPTH , None ) ;
140
191
let mut deepest_complete_depth = START_DEPTH ;
192
+
141
193
for depth in ( deepest_complete_depth + 1 ) ..=10 {
142
194
let latest_output = self . next_action_internal ( board, depth, Some ( deadline) ) ;
143
- let time_since_deadline = Instant :: now ( ) . saturating_duration_since ( deadline) ;
144
- if !time_since_deadline. is_zero ( ) {
195
+ if utils:: passed_deadline ( deadline) {
145
196
// The cancelled layer is the one with this data
146
197
deepest_complete_output. 2 . progress_on_next_layer =
147
198
latest_output. 2 . progress_on_next_layer ;
0 commit comments