1use crate::{
2 emulator::TravelKind,
3 piece::{Pieces, Side},
4};
5
6pub struct Turns {
7 working_board: Pieces,
8 curr_turn: usize,
9 turns: Vec<Pieces>,
10 to_move: Side,
15}
16
17impl Turns {
19 pub fn with(pieces: Pieces) -> Self {
20 Self {
21 working_board: pieces.clone(),
22 curr_turn: 0,
23 turns: vec![pieces],
24 to_move: Side::White,
25 }
26 }
27
28 pub fn set_to_move(&mut self, side: Side) {
29 self.to_move = side;
30 }
31
32 pub fn curr_turn(&self) -> usize {
33 self.curr_turn
34 }
35
36 pub fn inner_ref(&self) -> &Vec<Pieces> {
37 &self.turns
38 }
39
40 pub fn working_board_ref(&self) -> &Pieces {
41 &self.working_board
42 }
43
44 pub fn working_board_mut(&mut self) -> &mut Pieces {
45 &mut self.working_board
46 }
47
48 pub fn save_turn(&mut self) {
55 if self.turns.get(self.curr_turn + 1).is_some() {
56 self.turns
57 .resize_with(self.curr_turn + 1, || unreachable!("see if guard"));
58 }
59
60 self.turns.push(self.working_board.clone());
61 self.curr_turn += 1;
62 }
63
64 pub fn first(&mut self) {
65 self.load_turn(0);
66 }
67
68 pub fn last(&mut self) {
69 self.load_turn(self.turns.len() - 1);
70 }
71
72 pub fn prev(&mut self) -> Result<(), ()> {
73 if self.curr_turn == 0 {
74 Err(())
75 } else {
76 self.curr_turn -= 1;
77 self.load_turn(self.curr_turn);
78 Ok(())
79 }
80 }
81
82 pub fn next(&mut self) -> Result<(), ()> {
83 if self.curr_turn + 1 >= self.turns.len() {
84 Err(())
85 } else {
86 self.curr_turn += 1;
87 self.load_turn(self.curr_turn);
88 Ok(())
89 }
90 }
91
92 fn load_turn(&mut self, turn: usize) {
93 self.working_board.clone_from(&self.turns[turn]);
94 self.curr_turn = turn;
95 }
96}
97
98struct EngineMove {
100 travel: (usize, f32, f32),
101 rotate: (usize, f32),
102}
103
104pub type Score = f32;
106const DEPTH: usize = 3;
108
109impl Turns {
111 fn eval(&self) -> Score {
115 let mult = match self.to_move {
116 Side::Black => -1.,
117 Side::White => 1.,
118 };
119 let mut ans = 0.0;
120 for piece in self.working_board.inner_ref() {
121 ans +=
123 mult * match piece.side() {
124 Side::Black => -1.,
125 Side::White => 1.,
126 } * piece.kind().value()
127 * 100.;
128
129 const CENTER_X: f32 = 4.0;
132 const CENTER_Y: f32 = 4.0;
134 ans +=
135 mult * match piece.side() {
136 Side::Black => -1.,
137 Side::White => 1.,
138 } * (5.0
139 - Score::sqrt((piece.x() - CENTER_X).powi(2) + (piece.y() - CENTER_Y).powi(2)));
140 }
141 ans
142 }
143
144 fn negamax_ab(&mut self, depth: usize, mut alpha: Score, beta: Score) -> Score {
152 if depth == 0 {
154 return self.eval();
155 }
156
157 let mut best_score = Score::NEG_INFINITY;
158
159 for move_ in self.all_moves() {
160 self.apply(&move_);
161 let score = -self.negamax_ab(depth - 1, -beta, -alpha);
162 self.unapply();
163
164 if score > best_score {
165 best_score = score;
166 if score > alpha {
167 alpha = score;
168 }
169 }
170 if score >= beta {
171 break;
172 }
173 }
174
175 best_score
176 }
177
178 pub fn make_best_move(&mut self) {
180 let mut best_score: Score = Score::NEG_INFINITY;
181 let mut best_move: Option<EngineMove> = None;
182
183 for piece in self
184 .working_board
185 .inner_mut()
186 .iter_mut()
187 .chain(self.turns[self.curr_turn].inner_mut())
188 {
189 piece.init_auxiliary_data();
190 }
191
192 let moves = self.all_moves();
193 assert!(!moves.is_empty());
194 for move_ in moves {
195 self.apply(&move_);
196 let score = -self.negamax_ab(DEPTH, Score::NEG_INFINITY, Score::INFINITY);
197 self.unapply();
198
199 if score >= best_score {
200 best_score = score;
201 best_move = Some(move_);
202 }
203 }
204
205 self.apply(&best_move.expect("should've found a valid move."));
206
207 println!("best move had score {best_score}");
208 println!(
209 "current board state has score {} according to {:?}",
210 self.eval(),
211 self.to_move
212 );
213 }
214
215 fn unapply(&mut self) {
217 self.prev().expect("There will be a prev move.");
218 self.to_move = self.to_move.toggle();
219 }
220
221 fn apply(&mut self, move_: &EngineMove) {
227 debug_assert_eq!(
229 self.working_board
230 .get_mut(move_.travel.0)
231 .expect("EngineMove supplied wasn't valid")
232 .side(),
233 self.to_move
234 );
235 debug_assert_eq!(
236 self.working_board
237 .get_mut(move_.rotate.0)
238 .expect("EngineMove supplied wasn't valid")
239 .side(),
240 self.to_move
241 );
242
243 let (i, x, y) = move_.travel;
244
245 let i = self.working_board.travel(i, x, y);
246 self.working_board
247 .get_mut(i)
248 .expect("EngineMove supplied wasn't valid")
249 .update_capmove_points_unchecked();
251
252 let (_, r) = move_.rotate;
253 self.working_board
254 .get_mut(i)
255 .expect("EngineMove supplied wasn't valid")
256 .set_angle(r);
257
258 self.save_turn();
259 self.to_move = self.to_move.toggle();
260 }
261
262 fn all_moves(&mut self) -> Vec<EngineMove> {
266 let mut ans = vec![];
267 for piece in self.working_board.inner_mut().iter_mut() {
268 piece.init_auxiliary_data();
269 }
270 for (i, piece) in self.working_board.inner_ref().iter().enumerate() {
271 if piece.side() != self.to_move {
272 continue;
273 }
274 for (tvk, x, y) in piece
275 .move_points_unchecked()
276 .map(|&(x, y)| (TravelKind::Move, x, y))
277 .chain(
278 piece
279 .capture_points_unchecked()
280 .map(|&(x, y)| (TravelKind::Capture, x, y)),
281 )
282 {
283 if self.working_board_ref().travelable(piece, x, y, tvk) {
284 ans.push(EngineMove {
285 travel: (i, x, y),
286 rotate: (i, piece.angle()),
287 });
288 }
289 }
290 }
291 ans
292 }
293}