use super::bit_manip::first_set_bit;
use super::bit_mask::*;
use super::{map_bit_board_to_squares, Action, Direction, Piece, Square};
use super::{List, Terminal, Zobrist};
use std::hash::{Hash, Hasher};
#[derive(Hash, Eq, PartialEq, Clone, Copy, Debug)]
pub enum PushPullState {
None,
PossiblePull(Square, Piece),
MustCompletePush(Square, Piece),
}
#[derive(Clone, Debug)]
pub struct PlayPhase {
previous_piece_boards_this_move: Vec<PieceBoard>,
push_pull_state: PushPullState,
initial_hash_of_move: Zobrist,
hash_history: List<Zobrist>,
piece_trapped_this_turn: bool,
}
#[derive(Clone, Debug)]
pub enum Phase {
PlacePhase,
PlayPhase(PlayPhase),
}
#[derive(Clone, Debug)]
pub struct PieceBoardState {
pub p1_pieces: u64,
pub all_pieces: u64,
pub elephants: u64,
pub camels: u64,
pub horses: u64,
pub dogs: u64,
pub cats: u64,
pub rabbits: u64,
}
impl PieceBoardState {
pub fn bits_for_piece(&self, piece: Piece, p1_pieces: bool) -> u64 {
let player_piece_mask = if p1_pieces {
self.p1_pieces
} else {
!self.p1_pieces & self.all_pieces
};
self.bits_by_piece_type(piece) & player_piece_mask
}
pub fn player_piece_mask(&self, p1_pieces: bool) -> u64 {
if p1_pieces {
self.p1_pieces
} else {
!self.p1_pieces & self.all_pieces
}
}
pub fn bits_by_piece_type(&self, piece: Piece) -> u64 {
match piece {
Piece::Elephant => self.elephants,
Piece::Camel => self.camels,
Piece::Horse => self.horses,
Piece::Dog => self.dogs,
Piece::Cat => self.cats,
Piece::Rabbit => self.rabbits,
}
}
pub fn placement_bit(&self) -> u64 {
let placement_mask = if self.p1_pieces & P1_PLACEMENT_MASK == P1_PLACEMENT_MASK {
P2_PLACEMENT_MASK
} else {
P1_PLACEMENT_MASK
};
let squares_to_place = !self.all_pieces & placement_mask;
first_set_bit(squares_to_place)
}
pub fn trapped_piece_bits(&self) -> u64 {
let animal_is_on_trap = animal_is_on_trap(self);
if animal_is_on_trap {
let unsupported_piece_bits = both_player_unsupported_piece_bits(self);
unsupported_piece_bits & TRAP_MASK
} else {
0
}
}
pub fn piece_type_at_square(&self, square: &Square) -> Option<Piece> {
let square_bit = square.as_bit_board();
if square_bit & self.all_pieces != 0 {
Some(piece_type_at_bit(square_bit, self))
} else {
None
}
}
}
#[derive(Clone, Debug)]
pub struct PieceBoard(PieceBoardState);
impl PieceBoard {
pub fn initial() -> Self {
Self(PieceBoardState {
p1_pieces: 0,
all_pieces: 0,
elephants: 0,
camels: 0,
horses: 0,
dogs: 0,
cats: 0,
rabbits: 0,
})
}
pub fn new(
p1_pieces: u64,
elephants: u64,
camels: u64,
horses: u64,
dogs: u64,
cats: u64,
rabbits: u64,
) -> Self {
Self(PieceBoardState {
p1_pieces,
elephants,
camels,
horses,
dogs,
cats,
rabbits,
all_pieces: elephants | camels | horses | dogs | cats | rabbits,
})
}
pub fn piece_board(&self) -> &PieceBoardState {
&self.0
}
fn take_action(&self, move_action: &Action) -> (PieceBoardState, bool) {
let mut piece_board_state = self.0.clone();
let animal_was_trapped = if let Action::Move(square, direction) = move_action {
Self::move_piece(&mut piece_board_state, square, direction);
Self::remove_trapped_pieces(&mut piece_board_state)
} else {
panic!("Action must be of type Action::Move");
};
(piece_board_state, animal_was_trapped)
}
fn move_piece(piece_board_state: &mut PieceBoardState, square: &Square, direction: &Direction) {
let source_square_bit = square.as_bit_board();
let pb = piece_board_state;
pb.elephants = shift_piece_in_direction(pb.elephants, source_square_bit, direction);
pb.camels = shift_piece_in_direction(pb.camels, source_square_bit, direction);
pb.horses = shift_piece_in_direction(pb.horses, source_square_bit, direction);
pb.dogs = shift_piece_in_direction(pb.dogs, source_square_bit, direction);
pb.cats = shift_piece_in_direction(pb.cats, source_square_bit, direction);
pb.rabbits = shift_piece_in_direction(pb.rabbits, source_square_bit, direction);
pb.p1_pieces = shift_piece_in_direction(pb.p1_pieces, source_square_bit, direction);
pb.all_pieces = shift_piece_in_direction(pb.all_pieces, source_square_bit, direction);
}
fn remove_trapped_pieces(piece_board_state: &mut PieceBoardState) -> bool {
let trapped_animal_bits = piece_board_state.trapped_piece_bits();
let animal_is_trapped = trapped_animal_bits != 0;
if animal_is_trapped {
let untrapped_animal_bits = !trapped_animal_bits;
piece_board_state.elephants &= untrapped_animal_bits;
piece_board_state.camels &= untrapped_animal_bits;
piece_board_state.horses &= untrapped_animal_bits;
piece_board_state.dogs &= untrapped_animal_bits;
piece_board_state.cats &= untrapped_animal_bits;
piece_board_state.rabbits &= untrapped_animal_bits;
piece_board_state.p1_pieces &= untrapped_animal_bits;
piece_board_state.all_pieces &= untrapped_animal_bits;
}
animal_is_trapped
}
}
#[derive(Clone, Debug)]
pub struct GameState {
p1_turn_to_move: bool,
move_number: usize,
phase: Phase,
piece_board: PieceBoard,
hash: Zobrist,
}
impl Hash for GameState {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash.board_state_hash());
state.finish();
}
}
impl PartialEq for GameState {
fn eq(&self, other: &GameState) -> bool {
self.hash.board_state_hash() == other.hash.board_state_hash()
}
}
impl Eq for GameState {}
impl GameState {
pub fn initial() -> Self {
GameState {
p1_turn_to_move: true,
move_number: 1,
piece_board: PieceBoard::initial(),
phase: Phase::PlacePhase,
hash: Zobrist::initial(),
}
}
pub fn new(
p1_turn_to_move: bool,
move_number: usize,
phase: Phase,
piece_board: PieceBoard,
hash: Zobrist,
) -> Self {
GameState {
p1_turn_to_move,
move_number,
phase,
piece_board,
hash,
}
}
#[must_use = "This function does not modify the given state. You must use the resultant state."]
pub fn take_action(&self, action: &Action) -> Self {
match action {
Action::Pass => self.pass(),
Action::Place(piece) => self.place(*piece),
Action::Move(square, direction) => self.move_piece(square, direction),
}
}
pub fn is_terminal(&self) -> Option<Terminal> {
self.as_play_phase().and_then(|play_phase| {
let piece_board = &self.piece_board();
if play_phase.step() > 0 {
return self.has_move(piece_board);
}
self.rabbit_at_goal(piece_board)
.or_else(|| self.lost_all_rabbits(piece_board))
.or_else(|| self.has_move(piece_board))
})
}
#[allow(
clippy::blocks_in_if_conditions,
clippy::if_same_then_else,
clippy::needless_bool
)]
pub fn has_move(&self, piece_board: &PieceBoardState) -> Option<Terminal> {
let has_move = if let Phase::PlayPhase(play_phase) = &self.phase {
if play_phase.push_pull_state.is_must_complete_push() {
let valid_actions = self.must_complete_push_actions(piece_board);
self.has_non_passing_like_action(valid_actions)
} else if self.can_pass(true) {
true
} else if {
let mut valid_actions = Vec::new();
self.extend_with_valid_curr_player_piece_moves(&mut valid_actions, piece_board);
self.has_non_passing_like_action(valid_actions)
} {
true
} else if {
let mut valid_actions = Vec::new();
self.extend_with_pull_piece_actions(&mut valid_actions, piece_board);
self.has_non_passing_like_action(valid_actions)
} {
true
} else if {
let mut valid_actions = Vec::new();
self.extend_with_push_piece_actions(&mut valid_actions, piece_board);
self.has_non_passing_like_action(valid_actions)
} {
true
} else {
false
}
} else {
true
};
if has_move {
None
} else if self.p1_turn_to_move {
Some(Terminal::SilverWin)
} else {
Some(Terminal::GoldWin)
}
}
pub fn valid_actions(&self) -> Vec<Action> {
self.valid_actions_(true)
}
pub fn valid_actions_no_rep(&self) -> Vec<Action> {
self.valid_actions_(false)
}
pub fn piece_board_for_step(&self, step: usize) -> &PieceBoardState {
if step == self.current_step() {
return self.piece_board.piece_board();
}
let previous_piece_board = &self.unwrap_play_phase().previous_piece_boards_this_move[step];
previous_piece_board.piece_board()
}
pub fn piece_board(&self) -> &PieceBoardState {
self.piece_board.piece_board()
}
pub fn is_p1_turn_to_move(&self) -> bool {
self.p1_turn_to_move
}
pub fn move_number(&self) -> usize {
self.move_number
}
pub fn current_step(&self) -> usize {
self.unwrap_play_phase().step()
}
pub fn is_play_phase(&self) -> bool {
matches!(&self.phase, Phase::PlayPhase(_))
}
pub fn trapped_animal_for_action(&self, action: &Action) -> Option<(Square, Piece, bool)> {
let mut piece_board_state = self.piece_board().clone();
if let Action::Move(square, direction) = action {
PieceBoard::move_piece(&mut piece_board_state, square, direction);
let trapped_animal_bits = piece_board_state.trapped_piece_bits();
if trapped_animal_bits != 0 {
let square = Square::from_bit_board(trapped_animal_bits);
let piece = piece_board_state.piece_type_at_square(&square).unwrap();
let is_p1_piece =
piece_board_state.bits_for_piece(piece, true) & square.as_bit_board() != 0;
return Some((square, piece, is_p1_piece));
}
}
None
}
pub fn can_pass(&self, check_repititions: bool) -> bool {
self.as_play_phase().map_or(false, |play_phase| {
play_phase.step() >= 1
&& !play_phase.push_pull_state.is_must_complete_push()
&& (!check_repititions
|| ((self.unwrap_play_phase().initial_hash_of_move
!= self.hash.exclude_step(play_phase.step()))
&& !hash_history_contains_hash_twice(
&play_phase.hash_history,
&self.hash.pass(play_phase.step()),
)))
})
}
pub fn transposition_hash(&self) -> u64 {
match &self.phase {
Phase::PlayPhase(play_phase) => self
.hash
.board_state_hash_with_push_pull_state(play_phase.push_pull_state),
Phase::PlacePhase => self.hash.board_state_hash(),
}
}
fn valid_actions_(&self, check_repititions: bool) -> Vec<Action> {
if let Phase::PlayPhase(play_phase) = &self.phase {
let piece_board = self.piece_board();
let mut valid_actions = if play_phase.push_pull_state.is_must_complete_push() {
self.must_complete_push_actions(piece_board)
} else {
let mut valid_actions = Vec::with_capacity(50);
self.extend_with_push_piece_actions(&mut valid_actions, piece_board);
self.extend_with_pull_piece_actions(&mut valid_actions, piece_board);
self.extend_with_valid_curr_player_piece_moves(&mut valid_actions, piece_board);
if self.can_pass(check_repititions) {
valid_actions.push(Action::Pass);
}
valid_actions
};
if check_repititions {
self.remove_passing_like_actions(&mut valid_actions);
}
valid_actions
} else {
self.valid_placement()
}
}
fn valid_placement(&self) -> Vec<Action> {
let mut actions = Vec::with_capacity(6);
let piece_board = &self.piece_board();
let curr_player_pieces = self.curr_player_piece_mask(piece_board);
if piece_board.elephants & curr_player_pieces == 0 {
actions.push(Action::Place(Piece::Elephant));
}
if piece_board.camels & curr_player_pieces == 0 {
actions.push(Action::Place(Piece::Camel));
}
if (piece_board.horses & curr_player_pieces).count_ones() < 2 {
actions.push(Action::Place(Piece::Horse));
}
if (piece_board.dogs & curr_player_pieces).count_ones() < 2 {
actions.push(Action::Place(Piece::Dog));
}
if (piece_board.cats & curr_player_pieces).count_ones() < 2 {
actions.push(Action::Place(Piece::Cat));
}
if (piece_board.rabbits & curr_player_pieces).count_ones() < 8 {
actions.push(Action::Place(Piece::Rabbit));
}
actions
}
fn extend_with_valid_curr_player_piece_moves(
&self,
valid_actions: &mut Vec<Action>,
piece_board: &PieceBoardState,
) {
let non_frozen_pieces = self.curr_player_non_frozen_pieces(piece_board);
for direction in Direction::ALL.iter() {
let unoccupied_directions = can_move_in_direction(direction, piece_board);
let invalid_rabbit_moves = self.invalid_rabbit_moves(direction, piece_board);
let valid_curr_piece_moves =
unoccupied_directions & non_frozen_pieces & !invalid_rabbit_moves;
if valid_curr_piece_moves != 0 {
let squares = map_bit_board_to_squares(valid_curr_piece_moves);
valid_actions.extend(squares.into_iter().map(|s| Action::Move(s, *direction)));
}
}
}
fn extend_with_pull_piece_actions(
&self,
valid_actions: &mut Vec<Action>,
piece_board: &PieceBoardState,
) {
if let Some(play_phase) = self.as_play_phase() {
if let Some((square, piece)) = play_phase.push_pull_state.as_possible_pull() {
let opp_piece_mask = self.opponent_piece_mask(piece_board);
let lesser_opp_pieces = self.lesser_pieces(piece, piece_board) & opp_piece_mask;
let square_bit = square.as_bit_board();
for direction in Direction::ALL.iter() {
if shift_pieces_in_direction(lesser_opp_pieces, direction) & square_bit != 0 {
let source_opp_piece_square = Square::from_bit_board(
shift_pieces_in_opp_direction(square_bit, direction),
);
let action = Action::Move(source_opp_piece_square, *direction);
if !valid_actions.contains(&action) {
valid_actions.push(action);
}
}
}
}
}
}
fn extend_with_push_piece_actions(
&self,
valid_actions: &mut Vec<Action>,
piece_board: &PieceBoardState,
) {
if let Some(play_phase) = self.as_play_phase() {
if play_phase.push_pull_state.can_push() && play_phase.step() < 3 {
let predator_piece_mask = self.curr_player_non_frozen_pieces(piece_board);
let opp_piece_mask = self.opponent_piece_mask(piece_board);
let opp_threatened_pieces =
self.threatened_pieces(predator_piece_mask, opp_piece_mask, piece_board);
if opp_threatened_pieces != 0 {
for direction in Direction::ALL.iter() {
let unoccupied_directions = can_move_in_direction(direction, piece_board);
let valid_push_moves = unoccupied_directions & opp_threatened_pieces;
if valid_push_moves != 0 {
let squares = map_bit_board_to_squares(valid_push_moves);
valid_actions
.extend(squares.into_iter().map(|s| Action::Move(s, *direction)));
}
}
}
}
}
}
fn must_complete_push_actions(&self, piece_board: &PieceBoardState) -> Vec<Action> {
let play_phase = self.unwrap_play_phase();
let (square, pushed_piece) = play_phase.push_pull_state.unwrap_must_complete_push();
let curr_player_non_frozen_piece_mask = self.curr_player_non_frozen_pieces(piece_board);
let square_bit = square.as_bit_board();
let mut valid_actions = vec![];
for direction in Direction::ALL.iter() {
let pushing_piece_bit = shift_pieces_in_opp_direction(square_bit, direction)
& curr_player_non_frozen_piece_mask;
if pushing_piece_bit != 0
&& piece_type_at_bit(pushing_piece_bit, piece_board) > pushed_piece
{
valid_actions.push(Action::Move(
Square::from_bit_board(pushing_piece_bit),
*direction,
));
}
}
valid_actions
}
fn place(&self, piece: Piece) -> Self {
let piece_board = &self.piece_board();
let placement_bit = piece_board.placement_bit();
let mut new_elephants = piece_board.elephants;
let mut new_camels = piece_board.camels;
let mut new_horses = piece_board.horses;
let mut new_dogs = piece_board.dogs;
let mut new_cats = piece_board.cats;
let mut new_rabbits = piece_board.rabbits;
match piece {
Piece::Elephant => new_elephants |= placement_bit,
Piece::Camel => new_camels |= placement_bit,
Piece::Horse => new_horses |= placement_bit,
Piece::Dog => new_dogs |= placement_bit,
Piece::Cat => new_cats |= placement_bit,
Piece::Rabbit => new_rabbits |= placement_bit,
}
let new_p1_pieces = piece_board.p1_pieces
| if self.p1_turn_to_move {
placement_bit
} else {
0
};
let new_piece_board = PieceBoard::new(
new_p1_pieces,
new_elephants,
new_camels,
new_horses,
new_dogs,
new_cats,
new_rabbits,
);
let switch_players = placement_bit == LAST_P1_PLACEMENT_MASK;
let switch_phases = placement_bit == LAST_P2_PLACEMENT_MASK;
let new_p1_turn_to_move = if switch_players {
false
} else if switch_phases {
true
} else {
self.p1_turn_to_move
};
let new_hash = self.hash.place_piece(
piece,
Square::from_bit_board(placement_bit),
self.p1_turn_to_move,
switch_players,
switch_phases,
);
let new_phase = if switch_phases {
let hash_history = List::new();
let hash_history = hash_history.append(new_hash);
Phase::PlayPhase(PlayPhase::initial(new_hash, hash_history))
} else {
Phase::PlacePhase
};
let new_move_number = if switch_phases { 2 } else { 1 };
Self {
p1_turn_to_move: new_p1_turn_to_move,
phase: new_phase,
piece_board: new_piece_board,
move_number: new_move_number,
hash: new_hash,
}
}
fn pass(&self) -> Self {
let hash = self.hash.pass(self.current_step());
let play_phase = self.unwrap_play_phase();
let new_hash_history = if play_phase.piece_trapped_this_turn {
List::new()
} else {
play_phase.hash_history.clone()
};
let new_hash_history = new_hash_history.append(hash);
GameState {
phase: Phase::PlayPhase(PlayPhase::initial(hash, new_hash_history)),
p1_turn_to_move: !self.p1_turn_to_move,
move_number: self.move_number + if self.p1_turn_to_move { 0 } else { 1 },
piece_board: self.piece_board.clone(),
hash,
}
}
fn move_piece(&self, square: &Square, direction: &Direction) -> Self {
let curr_play_phase = self.unwrap_play_phase();
let curr_step = self.current_step();
let is_last_step = curr_step >= 3;
let new_action = Action::Move(*square, *direction);
let (new_piece_board_state, new_animal_was_trapped) =
self.piece_board.take_action(&new_action);
let new_p1_turn_to_move = if is_last_step {
!self.p1_turn_to_move
} else {
self.p1_turn_to_move
};
let new_step = if is_last_step { 0 } else { curr_step + 1 };
let new_move_number = self.move_number
+ if is_last_step && new_p1_turn_to_move {
1
} else {
0
};
let new_hash =
self.hash
.move_piece(self, &new_piece_board_state, new_step, new_p1_turn_to_move);
let new_hash_history = if new_animal_was_trapped {
List::new()
} else {
curr_play_phase.hash_history.clone()
};
let new_play_phase = if is_last_step {
let hash_history = new_hash_history.append(new_hash);
PlayPhase::initial(new_hash, hash_history)
} else {
let new_previous_piece_boards_this_move = self.next_piece_boards_this_move();
let new_push_pull_state = self.next_push_pull_state(square, direction);
PlayPhase {
initial_hash_of_move: curr_play_phase.initial_hash_of_move,
push_pull_state: new_push_pull_state,
hash_history: new_hash_history,
previous_piece_boards_this_move: new_previous_piece_boards_this_move,
piece_trapped_this_turn: curr_play_phase.piece_trapped_this_turn
| new_animal_was_trapped,
}
};
GameState {
p1_turn_to_move: new_p1_turn_to_move,
move_number: new_move_number,
phase: Phase::PlayPhase(new_play_phase),
piece_board: PieceBoard(new_piece_board_state),
hash: new_hash,
}
}
pub fn unwrap_play_phase(&self) -> &PlayPhase {
self.as_play_phase()
.expect("Expected phase to be PlayPhase")
}
pub fn as_play_phase(&self) -> Option<&PlayPhase> {
match &self.phase {
Phase::PlayPhase(play_phase) => Some(play_phase),
_ => None,
}
}
fn next_piece_boards_this_move(&self) -> Vec<PieceBoard> {
let play_phase = self.unwrap_play_phase();
let step = play_phase.step();
let mut previous_piece_boards = Vec::with_capacity(step + 1);
previous_piece_boards.extend(play_phase.previous_piece_boards_this_move.iter().cloned());
previous_piece_boards.push(self.piece_board.clone());
previous_piece_boards
}
fn next_push_pull_state(&self, square: &Square, direction: &Direction) -> PushPullState {
let source_square_bit = square.as_bit_board();
let piece_board = &self.piece_board();
let is_opponent_piece = self.is_their_piece(source_square_bit, piece_board);
let play_phase = self.unwrap_play_phase();
let piece_type_at_bit = piece_type_at_bit(source_square_bit, piece_board);
if is_opponent_piece
&& !self.move_can_be_counted_as_pull(source_square_bit, direction, piece_board)
{
PushPullState::MustCompletePush(*square, piece_type_at_bit)
} else if !is_opponent_piece
&& !play_phase.push_pull_state.is_must_complete_push()
&& piece_type_at_bit != Piece::Rabbit
{
PushPullState::PossiblePull(*square, piece_type_at_bit)
} else {
PushPullState::None
}
}
fn move_can_be_counted_as_pull(
&self,
new_move_square_bit: u64,
direction: &Direction,
piece_board: &PieceBoardState,
) -> bool {
let play_phase = self.unwrap_play_phase();
if let PushPullState::PossiblePull(prev_move_square, my_piece) = &play_phase.push_pull_state
{
if prev_move_square.as_bit_board() == shift_in_direction(new_move_square_bit, direction)
{
let their_piece = piece_type_at_bit(new_move_square_bit, piece_board);
if my_piece > &their_piece {
return true;
}
}
}
false
}
fn is_their_piece(&self, square_bit: u64, piece_board: &PieceBoardState) -> bool {
let is_p1_piece = square_bit & piece_board.p1_pieces != 0;
self.p1_turn_to_move ^ is_p1_piece
}
fn curr_player_non_frozen_pieces(&self, piece_board: &PieceBoardState) -> u64 {
let opp_piece_mask = self.opponent_piece_mask(piece_board);
let curr_player_piece_mask = !opp_piece_mask & piece_board.all_pieces;
let threatened_pieces =
self.threatened_pieces(opp_piece_mask, curr_player_piece_mask, piece_board);
curr_player_piece_mask & (!threatened_pieces | supported_pieces(curr_player_piece_mask))
}
fn threatened_pieces(
&self,
predator_piece_mask: u64,
prey_piece_mask: u64,
piece_board: &PieceBoardState,
) -> u64 {
let predator_elephant_influence =
influenced_squares(piece_board.elephants & predator_piece_mask);
let predator_camel_influence = influenced_squares(piece_board.camels & predator_piece_mask);
let predator_horse_influence = influenced_squares(piece_board.horses & predator_piece_mask);
let predator_dog_influence = influenced_squares(piece_board.dogs & predator_piece_mask);
let predator_cat_influence = influenced_squares(piece_board.cats & predator_piece_mask);
let camel_threats = predator_elephant_influence;
let horse_threats = camel_threats | predator_camel_influence;
let dog_threats = horse_threats | predator_horse_influence;
let cat_threats = dog_threats | predator_dog_influence;
let rabbit_threats = cat_threats | predator_cat_influence;
let threatened_pieces = (piece_board.camels & camel_threats)
| (piece_board.horses & horse_threats)
| (piece_board.dogs & dog_threats)
| (piece_board.cats & cat_threats)
| (piece_board.rabbits & rabbit_threats);
threatened_pieces & prey_piece_mask
}
fn curr_player_piece_mask(&self, piece_board: &PieceBoardState) -> u64 {
if self.p1_turn_to_move {
piece_board.p1_pieces
} else {
!piece_board.p1_pieces & piece_board.all_pieces
}
}
fn opponent_piece_mask(&self, piece_board: &PieceBoardState) -> u64 {
if self.p1_turn_to_move {
!piece_board.p1_pieces & piece_board.all_pieces
} else {
piece_board.p1_pieces
}
}
fn rabbit_at_goal(&self, piece_board: &PieceBoardState) -> Option<Terminal> {
let p1_objective_met = piece_board.p1_pieces & piece_board.rabbits & P1_OBJECTIVE_MASK != 0;
let p2_objective_met =
!piece_board.p1_pieces & piece_board.rabbits & P2_OBJECTIVE_MASK != 0;
if p1_objective_met || p2_objective_met {
let last_to_move_is_p1 = !self.p1_turn_to_move;
let last_to_move_objective_met = if last_to_move_is_p1 {
p1_objective_met
} else {
p2_objective_met
};
let p1_won = !(last_to_move_is_p1 ^ last_to_move_objective_met);
Some(if p1_won {
Terminal::GoldWin
} else {
Terminal::SilverWin
})
} else {
None
}
}
fn lost_all_rabbits(&self, piece_board: &PieceBoardState) -> Option<Terminal> {
let p1_lost_rabbits = piece_board.p1_pieces & piece_board.rabbits == 0;
let p2_lost_rabbits = !piece_board.p1_pieces & piece_board.rabbits == 0;
if p1_lost_rabbits || p2_lost_rabbits {
let last_to_move_is_p1 = !self.p1_turn_to_move;
let last_to_move_objective_met = if last_to_move_is_p1 {
p2_lost_rabbits
} else {
p1_lost_rabbits
};
let p1_won = !(last_to_move_is_p1 ^ last_to_move_objective_met);
Some(if p1_won {
Terminal::GoldWin
} else {
Terminal::SilverWin
})
} else {
None
}
}
#[allow(clippy::let_and_return)]
fn invalid_rabbit_moves(&self, direction: &Direction, piece_board: &PieceBoardState) -> u64 {
let backward_direction = if self.p1_turn_to_move {
Direction::Down
} else {
Direction::Up
};
if *direction == backward_direction {
let players_rabbits = if self.p1_turn_to_move {
piece_board.p1_pieces
} else {
!piece_board.p1_pieces
} & piece_board.rabbits;
players_rabbits
} else {
0
}
}
fn lesser_pieces(&self, piece: Piece, piece_board: &PieceBoardState) -> u64 {
match piece {
Piece::Rabbit => 0,
Piece::Cat => piece_board.rabbits,
Piece::Dog => piece_board.rabbits | piece_board.cats,
Piece::Horse => piece_board.rabbits | piece_board.cats | piece_board.dogs,
Piece::Camel => {
piece_board.rabbits | piece_board.cats | piece_board.dogs | piece_board.horses
}
Piece::Elephant => {
piece_board.rabbits
| piece_board.cats
| piece_board.dogs
| piece_board.horses
| piece_board.camels
}
}
}
fn remove_passing_like_actions(&self, valid_actions: &mut Vec<Action>) {
let play_phase = self.unwrap_play_phase();
if play_phase.step() == 3 && !play_phase.piece_trapped_this_turn {
valid_actions.retain(|action| !self.is_passing_like_action(action));
}
}
fn is_passing_like_action(&self, action: &Action) -> bool {
let play_phase = self.unwrap_play_phase();
let initial_hash_of_move = play_phase.initial_hash_of_move;
let hash_history = &play_phase.hash_history;
if let Action::Move(_, _) = action {
let new_piece_board = &self.piece_board.take_action(action).0;
let new_hash_no_player_switch =
self.hash
.move_piece(self, new_piece_board, 0, self.is_p1_turn_to_move());
let new_hash_switch_players =
self.hash
.move_piece(self, new_piece_board, 0, !self.is_p1_turn_to_move());
if new_hash_no_player_switch == initial_hash_of_move
|| hash_history_contains_hash_twice(hash_history, &new_hash_switch_players)
{
return true;
}
}
false
}
fn has_non_passing_like_action(&self, valid_actions: Vec<Action>) -> bool {
if valid_actions.is_empty() {
return false;
}
let play_phase = self.unwrap_play_phase();
if play_phase.step() < 3 || play_phase.piece_trapped_this_turn {
return true;
}
for action in valid_actions.iter() {
if !self.is_passing_like_action(action) {
return true;
}
}
false
}
}
fn piece_type_at_bit(square_bit: u64, piece_board: &PieceBoardState) -> Piece {
if piece_board.rabbits & square_bit != 0 {
Piece::Rabbit
} else if piece_board.elephants & square_bit != 0 {
Piece::Elephant
} else if piece_board.camels & square_bit != 0 {
Piece::Camel
} else if piece_board.horses & square_bit != 0 {
Piece::Horse
} else if piece_board.dogs & square_bit != 0 {
Piece::Dog
} else {
Piece::Cat
}
}
fn animal_is_on_trap(piece_board: &PieceBoardState) -> bool {
(piece_board.all_pieces & TRAP_MASK) != 0
}
fn influenced_squares(piece_board: u64) -> u64 {
shift_pieces_up!(piece_board)
| shift_pieces_right!(piece_board)
| shift_pieces_down!(piece_board)
| shift_pieces_left!(piece_board)
}
fn both_player_unsupported_piece_bits(piece_board: &PieceBoardState) -> u64 {
piece_board.all_pieces & !both_player_supported_pieces(piece_board)
}
fn both_player_supported_pieces(piece_board: &PieceBoardState) -> u64 {
let p1_pieces = piece_board.p1_pieces;
let p2_pieces = piece_board.all_pieces & !p1_pieces;
supported_pieces(p1_pieces) | supported_pieces(p2_pieces)
}
fn supported_pieces(piece_bits: u64) -> u64 {
let up_supported_pieces = piece_bits & shift_pieces_up!(piece_bits);
let right_supported_pieces = piece_bits & shift_pieces_right!(piece_bits);
let down_supported_pieces = piece_bits & shift_pieces_down!(piece_bits);
let left_supported_pieces = piece_bits & shift_pieces_left!(piece_bits);
up_supported_pieces | right_supported_pieces | down_supported_pieces | left_supported_pieces
}
fn can_move_in_direction(direction: &Direction, piece_board: &PieceBoardState) -> u64 {
let empty_squares = !piece_board.all_pieces;
shift_pieces_in_opp_direction(empty_squares, direction)
}
fn shift_piece_in_direction(
piece_board: u64,
source_square_bit: u64,
direction: &Direction,
) -> u64 {
shift_in_direction(piece_board & source_square_bit, direction)
| piece_board & !source_square_bit
}
fn shift_pieces_in_opp_direction(bits: u64, direction: &Direction) -> u64 {
match direction {
Direction::Up => shift_pieces_down!(bits),
Direction::Right => shift_pieces_left!(bits),
Direction::Down => shift_pieces_up!(bits),
Direction::Left => shift_pieces_right!(bits),
}
}
fn shift_pieces_in_direction(bits: u64, direction: &Direction) -> u64 {
match direction {
Direction::Up => shift_pieces_up!(bits),
Direction::Right => shift_pieces_right!(bits),
Direction::Down => shift_pieces_down!(bits),
Direction::Left => shift_pieces_left!(bits),
}
}
fn shift_in_direction(bits: u64, direction: &Direction) -> u64 {
match direction {
Direction::Up => shift_up!(bits),
Direction::Right => shift_right!(bits),
Direction::Down => shift_down!(bits),
Direction::Left => shift_left!(bits),
}
}
fn hash_history_contains_hash_twice(hash_history: &List<Zobrist>, hash: &Zobrist) -> bool {
hash_history.iter().filter(|h| *h == hash).count() >= 2
}
impl PushPullState {
fn is_must_complete_push(&self) -> bool {
matches!(self, PushPullState::MustCompletePush(_, _))
}
fn unwrap_must_complete_push(&self) -> (Square, Piece) {
match self {
PushPullState::MustCompletePush(square, piece) => (*square, *piece),
_ => panic!("Expected PushPullState to be MustCompletePush"),
}
}
pub fn as_possible_pull(&self) -> Option<(Square, Piece)> {
match self {
PushPullState::PossiblePull(square, piece) => Some((*square, *piece)),
_ => None,
}
}
fn can_push(&self) -> bool {
!matches!(self, PushPullState::MustCompletePush(_, _))
}
}
impl PlayPhase {
pub fn initial(initial_hash_of_move: Zobrist, hash_history: List<Zobrist>) -> Self {
PlayPhase {
previous_piece_boards_this_move: Vec::with_capacity(0),
push_pull_state: PushPullState::None,
initial_hash_of_move,
hash_history,
piece_trapped_this_turn: false,
}
}
pub fn new(
initial_hash_of_move: Zobrist,
hash_history: List<Zobrist>,
previous_piece_boards_this_move: Vec<PieceBoard>,
push_pull_state: PushPullState,
piece_trapped_this_turn: bool,
) -> Self {
PlayPhase {
previous_piece_boards_this_move,
push_pull_state,
initial_hash_of_move,
hash_history,
piece_trapped_this_turn,
}
}
pub fn previous_piece_boards(&self) -> &[PieceBoard] {
&self.previous_piece_boards_this_move
}
pub fn push_pull_state(&self) -> PushPullState {
self.push_pull_state
}
pub fn piece_trapped_this_turn(&self) -> bool {
self.piece_trapped_this_turn
}
pub fn step(&self) -> usize {
self.previous_piece_boards_this_move.len()
}
pub fn hash_history(&self) -> &List<Zobrist> {
&self.hash_history
}
}