1use super::bits::for_each_bit_idx;
2use super::constants::MAX_NUM_STEPS;
3use super::path_permutation::{PathPermutations, get_path_permutations, get_path_permutations_with_exact_steps};
4use super::piece_board::ExcludedSquaresWithDirections;
5use super::piece_board::get_exclusion_for_direction;
6use super::piece_board::{BOTTOM_ROW_MASK, PieceBoard, TOP_ROW_MASK, TRAP_MASK};
7use super::piece_board::{can_move_in_direction, get_excluded_direction, get_supported_square_exclusions, get_threatened_pieces_for_dir, get_threats_for_piece_type, shift_pieces_in_direction};
8use super::repetition_counts::RepetitionCounts;
9use super::value::Value;
10use super::zobrist::Zobrist;
11use super::{Action, Direction, Path, Piece, PushPullDirection, Square};
12
13use itertools::Itertools;
14use std::cell::OnceCell;
15use std::hash::{Hash, Hasher};
16use std::sync::Arc;
17use std::sync::atomic::{AtomicU64, Ordering};
18use tinyvec::ArrayVec;
19
20const P1_OBJECTIVE_MASK: u64 = TOP_ROW_MASK;
21const P2_OBJECTIVE_MASK: u64 = BOTTOM_ROW_MASK;
22
23#[derive(Clone, Debug)]
24pub enum GameState {
25 PlaceState(PlaceState),
26 PlayState(PlayState),
27}
28
29#[derive(Clone, Debug)]
30pub struct PlaceState {
31 p1_turn_to_move: bool,
32 piece_board: Box<PieceBoard>,
33 hash: Zobrist,
34}
35
36#[derive(Clone, Debug)]
37pub struct PlayState {
38 turn_state: TurnState,
39 step_state: StepState,
40}
41
42#[derive(Clone, Debug)]
43pub struct TurnState {
44 p1_turn_to_move: bool,
45 move_number: u16,
46 piece_board: PieceBoard,
47 hash: Zobrist,
48 hash_history: RepetitionCounts,
49}
50
51#[derive(Clone, Debug)]
52pub struct StepState {
53 steps: ArrayVec<[(Square, Direction); 3]>,
54 actions: ArrayVec<[Action; 3]>,
55 piece_board: PieceBoard,
56 banned_piece_mask: Arc<AtomicU64>,
57}
58
59impl StepState {
60 fn new(piece_board: PieceBoard) -> Self {
61 Self {
62 steps: ArrayVec::new(),
63 actions: ArrayVec::new(),
64 piece_board,
65 banned_piece_mask: Arc::new(AtomicU64::new(u64::MAX)),
66 }
67 }
68}
69
70impl Hash for GameState {
71 fn hash<H: Hasher>(&self, state: &mut H) {
72 state.write_u64(self.hash().board_state_hash());
73 }
74}
75
76impl PartialEq for GameState {
77 fn eq(&self, other: &GameState) -> bool {
78 self.hash().board_state_hash() == other.hash().board_state_hash()
79 }
80}
81
82impl Eq for GameState {}
83
84impl GameState {
85 pub fn new(p1_turn_to_move: bool, move_number: usize, piece_board: PieceBoard) -> Self {
86 let hash = Zobrist::from_piece_board(&piece_board, p1_turn_to_move, Some(0));
87
88 let turn_state = TurnState {
89 p1_turn_to_move,
90 move_number: move_number as u16,
91 piece_board: piece_board.clone(),
92 hash,
93 hash_history: RepetitionCounts::new().append(hash),
94 };
95
96 let step_state = StepState::new(piece_board);
97
98 Self::PlayState(PlayState { turn_state, step_state })
99 }
100
101 pub fn initial() -> Self {
102 Self::PlaceState(PlaceState {
103 p1_turn_to_move: true,
104 piece_board: Box::new(PieceBoard::initial()),
105 hash: Zobrist::initial(),
106 })
107 }
108
109 #[must_use]
110 pub fn take_action(&self, action: &Action) -> Self {
111 match self {
112 Self::PlaceState(place_state) => place_state.take_action(action),
113 Self::PlayState(play_state) => GameState::PlayState(play_state.take_action(action)),
114 }
115 }
116
117 pub fn is_terminal(&self) -> Option<Value> {
118 match self {
119 Self::PlaceState(_) => None,
120 Self::PlayState(play_state) => play_state.is_terminal(),
121 }
122 }
123
124 pub fn valid_actions(&self) -> Vec<Action> {
125 self.valid_actions_(true)
126 }
127
128 pub fn valid_actions_no_exclusions(&self) -> Vec<Action> {
129 self.valid_actions_(false)
130 }
131
132 pub fn get_piece_board(&self) -> &PieceBoard {
133 match self {
134 Self::PlaceState(place_state) => place_state.get_piece_board(),
135 Self::PlayState(play_state) => play_state.get_piece_board(),
136 }
137 }
138
139 pub fn is_p1_turn_to_move(&self) -> bool {
140 match self {
141 Self::PlaceState(place_state) => place_state.p1_turn_to_move,
142 Self::PlayState(play_state) => play_state.turn_state.p1_turn_to_move,
143 }
144 }
145
146 pub fn get_move_number(&self) -> usize {
147 match self {
148 Self::PlaceState(_) => 1,
149 Self::PlayState(play_state) => play_state.turn_state.move_number,
150 }
151 .into()
152 }
153
154 pub fn valid_actions_(&self, check_repetitions: bool) -> Vec<Action> {
155 match self {
156 Self::PlaceState(place_state) => place_state.valid_actions(),
157 Self::PlayState(play_state) => play_state.valid_actions(check_repetitions),
158 }
159 }
160
161 pub fn hash(&self) -> Zobrist {
162 match self {
163 Self::PlaceState(place_state) => place_state.hash,
164 Self::PlayState(play_state) => play_state.hash(),
165 }
166 }
167
168 pub fn step(&self) -> usize {
170 match self {
171 Self::PlaceState(_) => 0,
172 Self::PlayState(play_state) => play_state.get_step(),
173 }
174 }
175
176 pub fn is_play_phase(&self) -> bool {
177 matches!(self, Self::PlayState(_))
178 }
179
180 pub fn get_banned_piece_mask(&self) -> u64 {
181 match self {
182 Self::PlaceState(_) => 0,
183 Self::PlayState(play_state) => play_state.get_banned_piece_mask(),
184 }
185 }
186
187 pub fn get_vertical_symmetry(&self) -> Self {
188 match self {
189 Self::PlaceState(place_state) => Self::PlaceState(place_state.get_vertical_symmetry()),
190 Self::PlayState(play_state) => Self::PlayState(play_state.get_vertical_symmetry()),
191 }
192 }
193
194 pub fn get_trapped_animal_for_move(&self, square: Square, direction: Direction) -> Option<(Square, Piece, bool)> {
195 match self {
196 Self::PlaceState(_) => panic!("Get trapped animal is not valid in the place state"),
197 Self::PlayState(play_state) => play_state.get_trapped_animal_for_move(square, direction),
198 }
199 }
200
201 pub fn piece_to_place(&self) -> Piece {
202 match self {
203 Self::PlaceState(place_state) => place_state.piece_to_place(),
204 Self::PlayState(_) => panic!("Piece to place is not valid in the play state"),
205 }
206 }
207}
208
209impl PlayState {
210 fn initial(hash: Zobrist, piece_board: PieceBoard) -> Self {
211 Self {
212 turn_state: TurnState {
213 piece_board: piece_board.clone(),
214 p1_turn_to_move: true,
215 move_number: 2,
216 hash,
217 hash_history: RepetitionCounts::new().append(hash),
218 },
219 step_state: StepState::new(piece_board),
220 }
221 }
222
223 pub fn get_step(&self) -> usize {
224 self.step_state.steps.len()
225 }
226
227 pub fn get_trapped_animal_for_move(&self, square: Square, direction: Direction) -> Option<(Square, Piece, bool)> {
228 let mut piece_board = self.get_piece_board().clone();
229
230 piece_board.move_piece_update_board(square, direction);
231
232 let trapped_animal_bits = piece_board.get_trapped_piece_bits();
233 if trapped_animal_bits != 0 {
234 let square = Square::from_bit_board(trapped_animal_bits);
235 let piece = piece_board.get_piece_type_at_square(&square).unwrap();
236 let is_p1_piece = piece_board.get_bits_for_piece(piece, true) & square.as_bit_board() != 0;
237
238 return Some((square, piece, is_p1_piece));
239 }
240
241 None
242 }
243
244 pub fn get_vertical_symmetry(&self) -> Self {
245 Self {
246 turn_state: self.turn_state.get_vertical_symmetry(),
247 step_state: self.step_state.get_vertical_symmetry(),
248 }
249 }
250
251 fn take_action(&self, action: &Action) -> Self {
252 match action {
253 Action::Pass => self.pass(),
254 Action::Place(_) => panic!("Place action is not valid in the play state"),
255 Action::Move(square, path) => self.move_piece(*square, *path),
256 Action::PushPull(square, direction) => self.push_pull_piece(*square, *direction),
257 }
258 }
259
260 fn is_terminal(&self) -> Option<Value> {
261 let piece_board = &self.get_piece_board();
262
263 if self.get_step() > 0 {
265 return self.has_move(piece_board);
266 }
267
268 if self.turn_state.hash_history.num_repetitions(self.get_initial_hash_of_move()) >= 3 {
270 return Some(if self.turn_state.p1_turn_to_move { Value([1.0, 0.0]) } else { Value([0.0, 1.0]) });
271 }
272
273 self.rabbit_at_goal(piece_board)
276 .or_else(|| self.lost_all_rabbits(piece_board))
279 .or_else(|| self.has_move(piece_board))
282 }
283
284 fn valid_actions(&self, check_repetitions: bool) -> Vec<Action> {
285 let piece_board = &self.get_piece_board();
286 let hash = self.hash();
287 let current_step = self.get_step();
288 let p1_turn_to_move = self.turn_state.p1_turn_to_move;
289 let banned_piece_mask = self.get_banned_piece_mask();
290
291 let mut valid_actions = Vec::with_capacity(100);
292 Self::each_with_push_pull_piece_actions(piece_board, p1_turn_to_move, current_step, |a| {
293 if !self.is_action_passing(&a, piece_board, hash, check_repetitions) {
294 valid_actions.push(a);
295 }
296 false
297 });
298 Self::each_valid_curr_player_piece_moves(piece_board, banned_piece_mask, p1_turn_to_move, current_step, |a| {
299 if !self.is_action_passing(&a, piece_board, hash, check_repetitions) {
300 valid_actions.push(a);
301 }
302 false
303 });
304
305 if !self.is_pass_passing_like_action(hash, check_repetitions) {
306 valid_actions.push(Action::Pass);
307 }
308
309 valid_actions
310 }
311
312 fn has_move(&self, piece_board: &PieceBoard) -> Option<Value> {
313 if self.can_move(piece_board) {
314 None
315 } else if self.turn_state.p1_turn_to_move {
316 Some(Value([0.0, 1.0]))
317 } else {
318 Some(Value([1.0, 0.0]))
319 }
320 }
321
322 fn can_move(&self, piece_board: &PieceBoard) -> bool {
323 let piece_trapped = OnceCell::new();
324 let piece_trapped = || piece_trapped.get_or_init(|| self.piece_trapped_this_turn());
325 let banned_piece_mask = OnceCell::new();
326 let banned_piece_mask = || banned_piece_mask.get_or_init(|| self.get_banned_piece_mask());
327 let check_repetitions = true;
328 let hash = self.hash();
329 let current_step = self.get_step();
330 let p1_turn_to_move = self.turn_state.p1_turn_to_move;
331 let is_action_not_passing = |action| *piece_trapped() || !self.is_action_passing(&action, piece_board, hash, check_repetitions);
332 let can_pass = || self.can_pass(hash, check_repetitions);
333 let can_move_piece = || Self::each_valid_curr_player_piece_moves(piece_board, *banned_piece_mask(), p1_turn_to_move, current_step, is_action_not_passing);
334 let can_push_pull_piece = || Self::each_with_push_pull_piece_actions(piece_board, p1_turn_to_move, current_step, is_action_not_passing);
335
336 can_pass() || can_move_piece() || can_push_pull_piece()
337 }
338
339 fn can_pass(&self, hash: Zobrist, check_repetitions: bool) -> bool {
340 self.get_step() > 0 && !self.is_pass_passing_like_action(hash, check_repetitions)
341 }
342
343 fn is_action_valid(piece_board: &PieceBoard, p1_turn_to_move: bool, action: &Action) -> bool {
344 let is_matching_action = |a: Action| a == *action;
345
346 match action {
347 Action::Move(square, path) => {
348 let piece_type = piece_board.get_piece_type_at_square(square);
349
350 if piece_type.is_none() {
351 return false;
352 }
353
354 let curr_player_piece_mask = piece_board.get_curr_player_piece_mask(p1_turn_to_move);
355 let opp_piece_mask = piece_board.get_opponent_piece_mask(p1_turn_to_move);
356 let supported_square_exclusions = &get_supported_square_exclusions(curr_player_piece_mask);
357 let path_permuations = get_path_permutations_with_exact_steps(path.len());
358 let dangling_pieces_mask = OnceCell::new();
359 let dangling_pieces_mask = || *dangling_pieces_mask.get_or_init(|| Self::get_dangling_pieces(curr_player_piece_mask));
360 let banned_piece_mask = !square.as_bit_board();
361
362 Self::each_valid_move_for_piece(
363 piece_board,
364 banned_piece_mask,
365 piece_type.unwrap(),
366 curr_player_piece_mask,
367 dangling_pieces_mask,
368 opp_piece_mask,
369 path_permuations,
370 supported_square_exclusions,
371 p1_turn_to_move,
372 is_matching_action,
373 )
374 }
375 Action::PushPull(_, _) => Self::each_with_push_pull_piece_actions(piece_board, p1_turn_to_move, 2, is_matching_action),
376 _ => panic!("Invalid sequential action type {}", action),
377 }
378 }
379
380 fn get_piece_board(&self) -> &PieceBoard {
381 &self.step_state.piece_board
382 }
383
384 fn get_initial_hash_of_move(&self) -> Zobrist {
385 self.turn_state.hash
386 }
387
388 fn rabbit_at_goal(&self, piece_board: &PieceBoard) -> Option<Value> {
389 let p1_objective_met = piece_board.p1_pieces & piece_board.rabbits & P1_OBJECTIVE_MASK != 0;
390 let p2_objective_met = !piece_board.p1_pieces & piece_board.rabbits & P2_OBJECTIVE_MASK != 0;
391
392 if p1_objective_met || p2_objective_met {
393 let last_to_move_is_p1 = !self.turn_state.p1_turn_to_move;
396 let last_to_move_objective_met = if last_to_move_is_p1 { p1_objective_met } else { p2_objective_met };
397 let p1_won = !(last_to_move_is_p1 ^ last_to_move_objective_met);
398 Some(if p1_won { Value([1.0, 0.0]) } else { Value([0.0, 1.0]) })
399 } else {
400 None
401 }
402 }
403
404 fn lost_all_rabbits(&self, piece_board: &PieceBoard) -> Option<Value> {
405 let p1_lost_rabbits = piece_board.p1_pieces & piece_board.rabbits == 0;
406 let p2_lost_rabbits = !piece_board.p1_pieces & piece_board.rabbits == 0;
407
408 if p1_lost_rabbits || p2_lost_rabbits {
412 let last_to_move_is_p1 = !self.turn_state.p1_turn_to_move;
415 let last_to_move_objective_met = if last_to_move_is_p1 { p2_lost_rabbits } else { p1_lost_rabbits };
416 let p1_won = !(last_to_move_is_p1 ^ last_to_move_objective_met);
417 Some(if p1_won { Value([1.0, 0.0]) } else { Value([0.0, 1.0]) })
418 } else {
419 None
420 }
421 }
422
423 fn is_action_passing(&self, action: &Action, piece_board: &PieceBoard, hash: Zobrist, check_repetitions: bool) -> bool {
424 match action {
425 Action::Move(square, path) => self.is_move_passing_like_action(*square, *path, piece_board, hash, check_repetitions),
426 Action::PushPull(square, direction) => self.is_push_pull_passing_like_action(*square, *direction, piece_board, hash, check_repetitions),
427 Action::Pass => self.is_pass_passing_like_action(hash, check_repetitions),
428 Action::Place(_) => false,
429 }
430 }
431
432 #[allow(clippy::let_and_return)]
433 fn is_move_passing_like_action(&self, square: Square, path: Path, piece_board: &PieceBoard, hash: Zobrist, check_repetitions: bool) -> bool {
434 let updated_piece_board_hash = || {
435 let mut new_piece_board = piece_board.clone();
436 new_piece_board.move_piece_on_path(square, path);
437
438 let updated_piece_board_hash = hash.update_piece_board(piece_board, &new_piece_board);
439
440 updated_piece_board_hash
441 };
442
443 self.is_passing_like_action(path.len(), updated_piece_board_hash, check_repetitions)
444 }
445
446 #[allow(clippy::let_and_return)]
447 fn is_push_pull_passing_like_action(&self, square: Square, direction: PushPullDirection, piece_board: &PieceBoard, hash: Zobrist, check_repetitions: bool) -> bool {
448 let update_piece_board_hash = || {
449 let mut new_piece_board = piece_board.clone();
450 new_piece_board.push_pull_piece(square, direction);
451
452 let updated_piece_board_hash = hash.update_piece_board(piece_board, &new_piece_board);
453
454 updated_piece_board_hash
455 };
456
457 self.is_passing_like_action(2, update_piece_board_hash, check_repetitions)
458 }
459
460 fn is_pass_passing_like_action(&self, hash: Zobrist, check_repetitions: bool) -> bool {
461 self.is_passing_like_action(4, || hash, check_repetitions)
462 }
463
464 fn is_passing_like_action<F: FnMut() -> Zobrist>(&self, steps_used: usize, mut update_piece_board_hash: F, check_repetitions: bool) -> bool {
465 let curr_step_num = self.get_step();
466
467 if curr_step_num + steps_used < MAX_NUM_STEPS {
468 return false;
469 }
470
471 let initial_hash_of_move = self.get_initial_hash_of_move();
472 let updated_piece_board_hash = update_piece_board_hash();
473
474 let curr_is_p1_turn_to_move = self.turn_state.p1_turn_to_move;
475 let new_hash_no_player_switch = updated_piece_board_hash.update_step(curr_step_num, 0);
476 let new_hash_switch_players = new_hash_no_player_switch.update_player_to_move(curr_is_p1_turn_to_move, !curr_is_p1_turn_to_move);
477
478 new_hash_no_player_switch == initial_hash_of_move || (check_repetitions && self.turn_state.hash_history.num_repetitions(new_hash_switch_players) >= 2)
479 }
480
481 fn each_valid_curr_player_piece_moves<F: FnMut(Action) -> bool>(piece_board: &PieceBoard, banned_piece_mask: u64, p1_turn_to_move: bool, current_step: usize, mut cb: F) -> bool {
482 let curr_player_piece_mask = piece_board.get_curr_player_piece_mask(p1_turn_to_move);
483 let opp_piece_mask = piece_board.get_opponent_piece_mask(p1_turn_to_move);
484 let num_steps_remaining = MAX_NUM_STEPS - current_step;
485 let supported_square_exclusions = get_supported_square_exclusions(curr_player_piece_mask);
486 let path_permuations = get_path_permutations(num_steps_remaining);
487 let dangling_pieces_mask = OnceCell::new();
488 let dangling_pieces_mask = || *dangling_pieces_mask.get_or_init(|| Self::get_dangling_pieces(curr_player_piece_mask));
489
490 for piece_type in [Piece::Rabbit, Piece::Cat, Piece::Dog, Piece::Horse, Piece::Camel, Piece::Elephant] {
491 let complete = Self::each_valid_move_for_piece(
492 piece_board,
493 banned_piece_mask,
494 piece_type,
495 curr_player_piece_mask,
496 dangling_pieces_mask,
497 opp_piece_mask,
498 path_permuations,
499 &supported_square_exclusions,
500 p1_turn_to_move,
501 &mut cb,
502 );
503
504 if complete {
505 return true;
506 }
507 }
508
509 false
510 }
511
512 #[allow(clippy::too_many_arguments)]
513 fn each_valid_move_for_piece<F: FnMut(Action) -> bool, F2: Fn() -> u64>(
514 piece_board: &PieceBoard,
515 banned_piece_mask: u64,
516 piece_type: Piece,
517 curr_player_piece_mask: u64,
518 dangling_pieces_mask: F2,
519 opp_piece_mask: u64,
520 path_permuations: &[PathPermutations],
521 supported_square_exclusions: &ExcludedSquaresWithDirections,
522 p1_turn_to_move: bool,
523 mut cb: F,
524 ) -> bool {
525 let pieces_to_move_board = piece_board.get_bits_by_piece_type(piece_type) & curr_player_piece_mask & !banned_piece_mask;
526
527 if pieces_to_move_board == 0 {
529 return false;
530 }
531
532 let threatened_squares = get_threats_for_piece_type(opp_piece_mask, piece_type, piece_board);
534 let pieces_to_move_board = pieces_to_move_board & (supported_square_exclusions.none | !threatened_squares);
535
536 if pieces_to_move_board == 0 {
538 return false;
539 }
540
541 let empty_squares = !piece_board.all_pieces;
542 let invalid_rabbit_direction = if p1_turn_to_move { Direction::Down } else { Direction::Up };
543 let has_potential_dangling_piece = curr_player_piece_mask & TRAP_MASK != 0;
544
545 'moves: for paths in path_permuations.iter() {
546 let mut successful_moves = 0u64;
547
548 'paths: for (path, packed_path) in paths {
549 let mut moving_pieces_board = pieces_to_move_board & !successful_moves;
551
552 for (i, direction) in path.iter().enumerate() {
553 if piece_type == Piece::Rabbit && *direction == invalid_rabbit_direction {
554 continue 'paths;
555 }
556 let mut supported_squares = get_excluded_direction(i, path, supported_square_exclusions);
557
558 if has_potential_dangling_piece && i == 2 && path.len() >= 3 && path[0] != path[1] {
561 let dangling_pieces_mask: u64 = dangling_pieces_mask();
562 if dangling_pieces_mask != 0 {
563 let no_longer_supported_squares = Self::get_no_longer_supported_pieces(moving_pieces_board, dangling_pieces_mask, supported_square_exclusions, path);
564 supported_squares &= !no_longer_supported_squares
565 }
566 }
567
568 let supported_unendangered_squares = supported_squares | !(TRAP_MASK | threatened_squares);
569 let unoccupied_directions = can_move_in_direction(*direction, empty_squares);
570 let valid_curr_piece_moves = moving_pieces_board & supported_unendangered_squares & unoccupied_directions;
571
572 moving_pieces_board = shift_pieces_in_direction(valid_curr_piece_moves, *direction);
574
575 if moving_pieces_board == 0 {
577 continue 'paths;
578 }
579 }
580
581 let successfully_moved_pieces = Self::unwind_piece_path(moving_pieces_board, path);
582
583 let complete = Self::for_each_square(successfully_moved_pieces, |s| cb(Action::Move(s, *packed_path)));
584
585 if complete {
586 return true;
587 }
588
589 successful_moves |= successfully_moved_pieces;
591
592 if pieces_to_move_board == successful_moves {
594 continue 'moves;
595 }
596 }
597 }
598
599 false
600 }
601
602 fn unwind_piece_path(piece_board: u64, path: &[Direction]) -> u64 {
604 path.iter().rev().fold(piece_board, |piece_board, dir| shift_pieces_in_direction(piece_board, dir.invert()))
605 }
606
607 fn for_each_square<F: FnMut(Square) -> bool>(piece_board: u64, mut cb: F) -> bool {
608 let mut complete = false;
609 for_each_bit_idx(piece_board, |idx| {
610 if !complete {
611 let square = Square::from_index(idx as u8);
612 complete |= cb(square);
613 }
614 });
615 complete
616 }
617
618 fn each_with_push_pull_piece_actions<F: FnMut(Action) -> bool>(piece_board: &PieceBoard, p1_turn_to_move: bool, current_step: usize, mut cb: F) -> bool {
619 if current_step <= 2 {
620 let predator_piece_mask = piece_board.get_curr_player_non_frozen_pieces(p1_turn_to_move);
621 let opp_piece_mask = piece_board.get_opponent_piece_mask(p1_turn_to_move);
622 let empty_squares = !piece_board.all_pieces;
623
624 for (occupying_dir, vacating_dirs) in [
625 (
626 Direction::Up,
627 [
628 (Direction::Up, PushPullDirection::NN),
629 (Direction::Right, PushPullDirection::EN),
630 (Direction::Left, PushPullDirection::WN),
631 ],
632 ),
633 (
634 Direction::Right,
635 [
636 (Direction::Up, PushPullDirection::NE),
637 (Direction::Right, PushPullDirection::EE),
638 (Direction::Down, PushPullDirection::SE),
639 ],
640 ),
641 (
642 Direction::Down,
643 [
644 (Direction::Right, PushPullDirection::ES),
645 (Direction::Down, PushPullDirection::SS),
646 (Direction::Left, PushPullDirection::WS),
647 ],
648 ),
649 (
650 Direction::Left,
651 [
652 (Direction::Up, PushPullDirection::NW),
653 (Direction::Down, PushPullDirection::SW),
654 (Direction::Left, PushPullDirection::WW),
655 ],
656 ),
657 ]
658 .iter()
659 {
660 let opp_threatened_pieces_push = get_threatened_pieces_for_dir(predator_piece_mask, opp_piece_mask, *occupying_dir, piece_board);
661 let opp_threatened_pieces_pull = get_threatened_pieces_for_dir(predator_piece_mask, opp_piece_mask, occupying_dir.invert(), piece_board);
662 let opp_threatened_pieces_pull = shift_pieces_in_direction(opp_threatened_pieces_pull, *occupying_dir);
663
664 for (vacating_dir, move_direction) in vacating_dirs {
665 let unoccupied_directions = can_move_in_direction(*vacating_dir, empty_squares);
666
667 let valid_push_moves = unoccupied_directions & opp_threatened_pieces_push;
668 let valid_pull_moves = unoccupied_directions & opp_threatened_pieces_pull;
669
670 debug_assert_eq!(valid_push_moves & valid_pull_moves, 0);
671
672 let valid_push_pull_moves = valid_push_moves | valid_pull_moves;
673
674 if valid_push_pull_moves != 0 {
675 let complete = Self::for_each_square(valid_push_pull_moves, |s| cb(Action::PushPull(s, *move_direction)));
676
677 if complete {
678 return true;
679 }
680 }
681 }
682 }
683 }
684
685 false
686 }
687
688 fn pass(&self) -> PlayState {
689 let hash = self.hash().pass(self.get_step());
690 let new_hash_history = if self.piece_trapped_this_turn() {
691 RepetitionCounts::new()
692 } else {
693 self.turn_state.hash_history.clone()
694 };
695 let new_hash_history = new_hash_history.append(hash);
696
697 let piece_board = self.get_piece_board();
698 let turn_state = TurnState {
699 p1_turn_to_move: !self.turn_state.p1_turn_to_move,
700 move_number: self.turn_state.move_number + if self.turn_state.p1_turn_to_move { 0 } else { 1 },
701 hash,
702 hash_history: new_hash_history,
703 piece_board: piece_board.clone(),
704 };
705
706 let step_state = StepState::new(piece_board.clone());
707
708 PlayState { turn_state, step_state }
709 }
710
711 fn move_piece(&self, square: Square, path: Path) -> Self {
712 let mut piece_board = self.get_piece_board().clone();
713 piece_board.move_piece_on_path(square, path);
714 let steps = path
715 .iter()
716 .scan(square, |square, direction| {
717 let original = *square;
718 *square = square.shift_in_direction(direction);
719 Some((original, direction))
720 })
721 .collect::<ArrayVec<[_; 4]>>();
722
723 self.get_state_after_move(piece_board, &steps, Action::Move(square, path))
724 }
725
726 fn push_pull_piece(&self, square: Square, direction: PushPullDirection) -> Self {
727 let mut piece_board = self.get_piece_board().clone();
728 piece_board.push_pull_piece(square, direction);
729 let steps = direction.get_steps(square);
730
731 self.get_state_after_move(piece_board, &steps, Action::PushPull(square, direction))
732 }
733
734 fn get_state_after_move(&self, new_piece_board: PieceBoard, steps: &[(Square, Direction)], action: Action) -> Self {
735 let num_steps_taken = steps.len();
736 let curr_step = self.get_step();
737 let num_steps_this_turn = curr_step + num_steps_taken;
738 debug_assert!(num_steps_this_turn <= MAX_NUM_STEPS, "Number of steps should not be greater than MAX_NUM_STEPS");
739 let is_last_step = num_steps_this_turn >= MAX_NUM_STEPS;
740 let new_p1_turn_to_move = if is_last_step { !self.turn_state.p1_turn_to_move } else { self.turn_state.p1_turn_to_move };
741 let new_step = if is_last_step { 0 } else { num_steps_this_turn };
742
743 let new_hash = self
744 .get_initial_hash_of_move()
745 .update_piece_board(&self.turn_state.piece_board, &new_piece_board)
746 .update_step(0, new_step)
747 .update_player_to_move(self.turn_state.p1_turn_to_move, new_p1_turn_to_move);
748
749 let new_move_number = self.turn_state.move_number + if is_last_step && new_p1_turn_to_move { 1 } else { 0 };
750
751 let new_turn_state = if is_last_step {
752 let new_hash_history = if self.piece_trapped_this_turn() {
753 RepetitionCounts::new()
754 } else {
755 self.turn_state.hash_history.clone()
756 };
757
758 TurnState {
759 p1_turn_to_move: new_p1_turn_to_move,
760 move_number: new_move_number,
761 piece_board: new_piece_board.clone(),
762 hash: new_hash,
763 hash_history: new_hash_history.append(new_hash),
764 }
765 } else {
766 self.turn_state.clone()
767 };
768
769 let new_step_state = if is_last_step {
770 StepState::new(new_piece_board)
771 } else {
772 let mut new_steps = self.step_state.steps;
773 new_steps.extend_from_slice(steps);
774
775 let mut new_actions = self.step_state.actions.clone();
776 new_actions.push(action);
777
778 StepState {
779 steps: new_steps,
780 actions: new_actions,
781 piece_board: new_piece_board,
782 banned_piece_mask: Arc::new(AtomicU64::new(u64::MAX)),
783 }
784 };
785
786 PlayState {
787 turn_state: new_turn_state,
788 step_state: new_step_state,
789 }
790 }
791
792 fn piece_trapped_this_turn(&self) -> bool {
793 let mut piece_board = self.turn_state.piece_board.clone();
794 for (square, direction) in self.step_state.steps.iter() {
795 if piece_board.move_piece(*square, *direction) {
796 return true;
797 }
798 }
799
800 false
801 }
802
803 fn own_piece_trapped(&self, piece_board: &PieceBoard, action: &Action) -> bool {
804 let mut piece_board = piece_board.clone();
805 for (square, direction) in action.get_steps() {
806 piece_board.move_piece_update_board(square, direction);
807 let trapped_piece_bit = piece_board.get_trapped_piece_bits();
808
809 if trapped_piece_bit != 0 {
810 let curr_player_piece_mask = piece_board.get_curr_player_piece_mask(self.turn_state.p1_turn_to_move);
811
812 if trapped_piece_bit & curr_player_piece_mask != 0 {
813 return true;
814 }
815 }
816
817 piece_board.remove_trapped_pieces();
818 }
819
820 false
821 }
822
823 fn is_valid_transposition(initial_turn_piece_board: &PieceBoard, p1_turn_to_move: bool, actions: &[&Action], candidate_action: Action, expected_piece_board: &PieceBoard) -> bool {
824 let mut rearranged_order_piece_board = initial_turn_piece_board.clone();
825 let candidate_action_steps = candidate_action.get_steps();
826 let (candidate_action_departing_square, candidate_action_departing_direction) = candidate_action_steps.first().unwrap();
827 let mut has_interposed = false;
828
829 for action in actions {
830 let is_valid = Self::is_action_valid(&rearranged_order_piece_board, p1_turn_to_move, action);
831
832 if !is_valid {
833 return false;
834 }
835
836 let steps = action.get_steps();
837 for (square, direction) in steps {
838 rearranged_order_piece_board.move_piece(square, direction);
839 }
840
841 if !has_interposed && matches!(action, Action::Move(_, _)) {
842 let landing_square = action.landing_square();
843 if landing_square == *candidate_action_departing_square {
844 let is_inverse_direction = candidate_action_departing_direction.invert() == action.get_steps().last().unwrap().1;
845
846 if is_inverse_direction {
847 return false;
848 }
849
850 let is_valid = Self::is_action_valid(&rearranged_order_piece_board, p1_turn_to_move, &candidate_action);
851
852 if !is_valid {
853 return false;
854 }
855
856 for (square, direction) in candidate_action_steps {
857 rearranged_order_piece_board.move_piece(square, direction);
858 }
859
860 has_interposed = true;
861 }
862 }
863 }
864
865 rearranged_order_piece_board == *expected_piece_board
866 }
867
868 fn update_and_verify_piece_board_with_actions(piece_board: &mut PieceBoard, p1_turn_to_move: bool, actions: &[Action]) -> bool {
869 for action in actions {
870 let is_valid = Self::is_action_valid(piece_board, p1_turn_to_move, action);
871
872 if !is_valid {
873 return false;
874 }
875
876 for (square, direction) in action.get_steps() {
877 piece_board.move_piece(square, direction);
878 }
879 }
880
881 true
882 }
883
884 fn get_banned_piece_mask(&self) -> u64 {
885 let mut banned_piece_mask = self.step_state.banned_piece_mask.load(Ordering::Relaxed);
886
887 if banned_piece_mask == u64::MAX {
888 banned_piece_mask = self.get_banned_piece_mask_inner();
889 self.step_state.banned_piece_mask.store(banned_piece_mask, Ordering::SeqCst);
890 }
891
892 banned_piece_mask
893 }
894
895 fn get_banned_piece_mask_inner(&self) -> u64 {
896 if self.get_step() == 0 {
897 return 0;
898 }
899
900 let p1_turn_to_move = self.turn_state.p1_turn_to_move;
901 let initial_turn_piece_board = &self.turn_state.piece_board;
902
903 let unban_pieces = |banned_piece_mask: u64, actions: &[Action], piece_board: &PieceBoard| -> u64 {
904 if banned_piece_mask == 0 {
905 return banned_piece_mask;
906 }
907
908 let mut banned_piece_mask = banned_piece_mask;
909
910 let current_step = 3;
912
913 Self::each_valid_curr_player_piece_moves(piece_board, !banned_piece_mask, p1_turn_to_move, current_step, |candidate_action| {
914 debug_assert!(candidate_action.get_steps().len() == 1, "Candidate actions should always be of 1 step.");
915
916 let candidate_action_steps = candidate_action.get_steps();
917 let candidate_action_departing_square = candidate_action_steps.first().unwrap().0;
918
919 if candidate_action_departing_square.as_bit_board() & banned_piece_mask == 0 {
920 return false;
921 }
922
923 let expected_piece_board = {
924 let mut expected_piece_board = initial_turn_piece_board.clone();
925 let mut actions = Vec::from(actions);
926 actions.push(candidate_action.clone());
927 let is_valid = Self::update_and_verify_piece_board_with_actions(&mut expected_piece_board, p1_turn_to_move, &actions);
928
929 if !is_valid {
930 return false;
931 }
932
933 expected_piece_board
934 };
935
936 let min_actions = 1.max(actions.len() - 1);
937 let has_valid_transpostion = (min_actions..=actions.len())
938 .flat_map(|c| actions.iter().permutations(c))
939 .any(|actions_inner| Self::is_valid_transposition(initial_turn_piece_board, p1_turn_to_move, &actions_inner, candidate_action.clone(), &expected_piece_board));
940
941 if !has_valid_transpostion {
942 banned_piece_mask &= !candidate_action_departing_square.as_bit_board();
943 }
944
945 banned_piece_mask == 0
946 });
947
948 banned_piece_mask
949 };
950
951 let mut piece_board = initial_turn_piece_board.clone();
952 let actions = &self.step_state.actions;
953 let mut banned_piece_mask = 0;
954
955 for (i, action) in actions.iter().enumerate() {
956 let latest_action_steps = action.get_steps();
957 let own_piece_trapped = self.own_piece_trapped(&piece_board, action);
958
959 for (square, direction) in latest_action_steps {
960 piece_board.move_piece(square, direction);
961 }
962
963 banned_piece_mask = unban_pieces(banned_piece_mask, &actions[..=i], &piece_board);
964 let is_move_action = matches!(action, Action::Move(_, _));
965
966 if !is_move_action {
968 continue;
969 }
970
971 if own_piece_trapped {
973 let curr_player_piece_mask = piece_board.get_curr_player_piece_mask(p1_turn_to_move);
974 banned_piece_mask &= curr_player_piece_mask;
975 }
976
977 if own_piece_trapped && latest_action_steps.len() <= 2 {
979 continue;
980 }
981
982 let landing_square_bit = action.landing_square().as_bit_board();
984
985 let piece_is_frozen_after_move = piece_board.get_curr_player_frozen_pieces(p1_turn_to_move) & landing_square_bit != 0;
987
988 if piece_is_frozen_after_move {
989 continue;
990 }
991
992 banned_piece_mask |= landing_square_bit;
993 }
994
995 banned_piece_mask
996 }
997
998 fn get_dangling_pieces(curr_player_piece_mask: u64) -> u64 {
999 let up = shift_up!(curr_player_piece_mask);
1000 let right = shift_right!(curr_player_piece_mask);
1001 let down = shift_down!(curr_player_piece_mask);
1002 let left = shift_left!(curr_player_piece_mask);
1003 let mut set_once = 0;
1004 let mut set_mult = 0;
1005 for board in [up, right, down, left] {
1006 set_mult |= set_once & board;
1007 set_once ^= board;
1008 }
1009
1010 let dangling_piece_mask = curr_player_piece_mask & TRAP_MASK & !set_mult;
1011 shift_up!(dangling_piece_mask) | shift_right!(dangling_piece_mask) | shift_down!(dangling_piece_mask) | shift_left!(dangling_piece_mask)
1012 }
1013
1014 fn get_no_longer_supported_pieces(moving_pieces_board: u64, dangling_pieces: u64, supported_square_exclusions: &ExcludedSquaresWithDirections, path: &ArrayVec<[Direction; 4]>) -> u64 {
1015 let started_from_dangling_pos = shift_pieces_in_direction(shift_pieces_in_direction(dangling_pieces, path[0]), path[1]);
1016 let dropped_dangling_piece = moving_pieces_board & started_from_dangling_pos & dangling_pieces;
1017 dropped_dangling_piece & !get_exclusion_for_direction(path[0], supported_square_exclusions)
1018 }
1019
1020 fn hash(&self) -> Zobrist {
1021 let step_num = self.get_step();
1022 let hash = self.get_initial_hash_of_move();
1023
1024 if step_num == 0 {
1025 return hash;
1026 }
1027
1028 let turn_piece_board = &self.turn_state.piece_board;
1029 let curr_piece_board = self.get_piece_board();
1030 hash.update_piece_board(turn_piece_board, curr_piece_board).update_step(0, step_num)
1031 }
1032}
1033
1034impl PlaceState {
1035 fn take_action(&self, action: &Action) -> GameState {
1036 match action {
1037 Action::Place(square) => self.place(*square),
1038 _ => panic!("Provided action is not valid in the place state: {:?}", action),
1039 }
1040 }
1041
1042 fn valid_actions(&self) -> Vec<Action> {
1043 let empty_placement_bits = self.get_piece_board().empty_placements(self.p1_turn_to_move);
1044
1045 Square::from_multiple_bits(empty_placement_bits).into_iter().map(Action::Place).collect()
1046 }
1047
1048 const PIECES_TO_PLACE: [(Piece, u32); 5] = [(Piece::Elephant, 1u32), (Piece::Camel, 1), (Piece::Horse, 2), (Piece::Dog, 2), (Piece::Cat, 2)];
1049
1050 fn place(&self, square: Square) -> GameState {
1051 let mut piece_board = self.get_piece_board().clone();
1052
1053 let p1_turn_to_move = self.p1_turn_to_move;
1054 let piece_to_place = self.piece_to_place();
1055 piece_board.place(piece_to_place, square, p1_turn_to_move);
1056
1057 let last_piece_placed = piece_board.get_bits_for_piece(Piece::Cat, p1_turn_to_move).count_ones() == 2;
1058
1059 let mut new_hash = self.hash;
1060 if last_piece_placed {
1061 Self::fill_remaining_rabbits(&mut piece_board, p1_turn_to_move);
1062 new_hash = new_hash.fill_rabbits(&piece_board, p1_turn_to_move);
1063 }
1064
1065 let switch_players = last_piece_placed && p1_turn_to_move;
1066 let switch_phases = last_piece_placed && !p1_turn_to_move;
1067 let new_p1_turn_to_move = if switch_players {
1068 false
1069 } else if switch_phases {
1070 true
1071 } else {
1072 p1_turn_to_move
1073 };
1074
1075 let new_hash = new_hash.place_piece(piece_to_place, square, p1_turn_to_move, switch_players, switch_phases);
1076
1077 if switch_phases {
1078 GameState::PlayState(PlayState::initial(new_hash, piece_board))
1079 } else {
1080 GameState::PlaceState(PlaceState {
1081 p1_turn_to_move: new_p1_turn_to_move,
1082 piece_board: Box::new(piece_board),
1083 hash: new_hash,
1084 })
1085 }
1086 }
1087
1088 pub fn piece_to_place(&self) -> Piece {
1089 let piece_board = self.get_piece_board();
1090
1091 Self::PIECES_TO_PLACE
1092 .iter()
1093 .find(|(piece, count)| piece_board.get_bits_for_piece(*piece, self.p1_turn_to_move).count_ones() < *count)
1094 .expect("Expected available piece to place.")
1095 .0
1096 }
1097
1098 pub fn fill_remaining_rabbits(piece_board: &mut PieceBoard, p1_turn_to_move: bool) {
1099 let empty_squares = piece_board.empty_placements(p1_turn_to_move);
1100
1101 assert_eq!(empty_squares.count_ones(), 8, "Expected 8 squares for rabbits to be placed.");
1102
1103 piece_board.fill(Piece::Rabbit, empty_squares, p1_turn_to_move);
1104 }
1105
1106 pub fn get_piece_board(&self) -> &PieceBoard {
1107 &self.piece_board
1108 }
1109
1110 pub fn get_vertical_symmetry(&self) -> Self {
1111 let piece_board = Box::new(self.piece_board.get_vertical_symmetry());
1112 let hash = Zobrist::from_piece_board(&piece_board, self.p1_turn_to_move, Some(0));
1113
1114 Self {
1115 p1_turn_to_move: self.p1_turn_to_move,
1116 piece_board,
1117 hash,
1118 }
1119 }
1120}
1121
1122impl TurnState {
1123 fn get_vertical_symmetry(&self) -> Self {
1124 let piece_board = self.piece_board.get_vertical_symmetry();
1125 let hash = Zobrist::from_piece_board(&piece_board, self.p1_turn_to_move, Some(0));
1126
1127 Self {
1128 p1_turn_to_move: self.p1_turn_to_move,
1129 move_number: self.move_number,
1130 piece_board,
1131 hash,
1132 hash_history: RepetitionCounts::new().append(hash),
1133 }
1134 }
1135}
1136
1137impl StepState {
1138 fn get_vertical_symmetry(&self) -> Self {
1139 Self {
1140 steps: self.steps.iter().map(|(s, d)| (s.vertical_symmetry(), d.vertical_symmetry())).collect(),
1141 actions: self.actions.iter().map(|a| a.vertical_symmetry()).collect(),
1142 piece_board: self.piece_board.get_vertical_symmetry(),
1143 banned_piece_mask: Arc::new(AtomicU64::new(u64::MAX)),
1144 }
1145 }
1146}