Skip to main content

arimaa_engine/
piece_board.rs

1use super::map_bit_board_to_squares;
2use super::{Direction, Path, Piece, PushPullDirection, Square};
3
4#[rustfmt::skip]
5pub const TRAP_MASK: u64 = 0b__00000000__00000000__00100100__00000000__00000000__00100100__00000000__00000000;
6#[rustfmt::skip]
7const P1_PLACEMENT_MASK: u64 = 0b__11111111__11111111__00000000__00000000__00000000__00000000__00000000__00000000;
8#[rustfmt::skip]
9const P2_PLACEMENT_MASK: u64 = 0b__00000000__00000000__00000000__00000000__00000000__00000000__11111111__11111111;
10
11#[rustfmt::skip]
12pub const LEFT_COLUMN_MASK: u64 = 0b__00000001__00000001__00000001__00000001__00000001__00000001__00000001__00000001;
13#[rustfmt::skip]
14pub const RIGHT_COLUMN_MASK: u64 = 0b__10000000__10000000__10000000__10000000__10000000__10000000__10000000__10000000;
15#[rustfmt::skip]
16pub const TOP_ROW_MASK: u64 = 0b__00000000__00000000__00000000__00000000__00000000__00000000__00000000__11111111;
17#[rustfmt::skip]
18pub const BOTTOM_ROW_MASK: u64 = 0b__11111111__00000000__00000000__00000000__00000000__00000000__00000000__00000000;
19
20#[derive(Clone, Debug)]
21
22pub struct PieceBoard {
23    pub p1_pieces: u64,
24    pub all_pieces: u64,
25    pub elephants: u64,
26    pub camels: u64,
27    pub horses: u64,
28    pub dogs: u64,
29    pub cats: u64,
30    pub rabbits: u64,
31}
32
33impl PieceBoard {
34    pub fn initial() -> Self {
35        PieceBoard {
36            p1_pieces: 0,
37            all_pieces: 0,
38            elephants: 0,
39            camels: 0,
40            horses: 0,
41            dogs: 0,
42            cats: 0,
43            rabbits: 0,
44        }
45    }
46
47    pub fn new(p1_pieces: u64, elephants: u64, camels: u64, horses: u64, dogs: u64, cats: u64, rabbits: u64) -> Self {
48        Self {
49            p1_pieces,
50            elephants,
51            camels,
52            horses,
53            dogs,
54            cats,
55            rabbits,
56            all_pieces: rabbits | cats | dogs | horses | camels | elephants,
57        }
58    }
59
60    pub fn get_bits_for_piece(&self, piece: Piece, p1_pieces: bool) -> u64 {
61        let player_piece_mask = if p1_pieces { self.p1_pieces } else { !self.p1_pieces & self.all_pieces };
62        self.get_bits_by_piece_type(piece) & player_piece_mask
63    }
64
65    pub fn get_player_piece_mask(&self, p1_pieces: bool) -> u64 {
66        if p1_pieces { self.p1_pieces } else { !self.p1_pieces & self.all_pieces }
67    }
68
69    pub fn get_bits_by_piece_type(&self, piece: Piece) -> u64 {
70        match piece {
71            Piece::Elephant => self.elephants,
72            Piece::Camel => self.camels,
73            Piece::Horse => self.horses,
74            Piece::Dog => self.dogs,
75            Piece::Cat => self.cats,
76            Piece::Rabbit => self.rabbits,
77        }
78    }
79
80    pub fn bits_by_piece_type_mut(&mut self, piece: Piece) -> &mut u64 {
81        match piece {
82            Piece::Elephant => &mut self.elephants,
83            Piece::Camel => &mut self.camels,
84            Piece::Horse => &mut self.horses,
85            Piece::Dog => &mut self.dogs,
86            Piece::Cat => &mut self.cats,
87            Piece::Rabbit => &mut self.rabbits,
88        }
89    }
90
91    pub fn get_trapped_piece_bits(&self) -> u64 {
92        let animal_is_on_trap = animal_is_on_trap(self);
93
94        if animal_is_on_trap {
95            let unsupported_piece_bits = get_both_player_unsupported_piece_bits(self);
96            unsupported_piece_bits & TRAP_MASK
97        } else {
98            0
99        }
100    }
101
102    pub fn get_piece_type_at_square(&self, square: &Square) -> Option<Piece> {
103        let square_bit = square.as_bit_board();
104        if square_bit & self.all_pieces != 0 { Some(get_piece_type_at_bit(square_bit, self)) } else { None }
105    }
106
107    pub fn get_curr_player_frozen_pieces(&self, p1_turn_to_move: bool) -> u64 {
108        let opp_piece_mask = self.get_opponent_piece_mask(p1_turn_to_move);
109        let curr_player_piece_mask = !opp_piece_mask & self.all_pieces;
110        let threatened_pieces = get_threatened_pieces(opp_piece_mask, curr_player_piece_mask, self);
111
112        curr_player_piece_mask & threatened_pieces & !get_supported_pieces(curr_player_piece_mask)
113    }
114
115    pub fn get_curr_player_non_frozen_pieces(&self, p1_turn_to_move: bool) -> u64 {
116        let opp_piece_mask = self.get_opponent_piece_mask(p1_turn_to_move);
117        let curr_player_piece_mask = !opp_piece_mask & self.all_pieces;
118        let threatened_pieces = get_threatened_pieces(opp_piece_mask, curr_player_piece_mask, self);
119
120        curr_player_piece_mask & (!threatened_pieces | get_supported_pieces(curr_player_piece_mask))
121    }
122
123    pub fn get_curr_player_piece_mask(&self, p1_turn_to_move: bool) -> u64 {
124        if p1_turn_to_move { self.p1_pieces } else { !self.p1_pieces & self.all_pieces }
125    }
126
127    pub fn get_opponent_piece_mask(&self, p1_turn_to_move: bool) -> u64 {
128        if p1_turn_to_move { !self.p1_pieces & self.all_pieces } else { self.p1_pieces }
129    }
130
131    pub fn push_pull_piece(&mut self, square: Square, direction: PushPullDirection) -> bool {
132        let mut piece_trapped = false;
133
134        for (square, direction) in direction.get_steps(square) {
135            piece_trapped |= self.move_piece(square, direction);
136        }
137
138        piece_trapped
139    }
140
141    pub fn move_piece_on_path(&mut self, square: Square, path: Path) -> bool {
142        let mut piece_trapped = false;
143        let mut square = square;
144
145        for direction in path.iter() {
146            piece_trapped |= self.move_piece(square, direction);
147            square = square.shift_in_direction(direction);
148        }
149
150        piece_trapped
151    }
152
153    pub fn move_piece_update_board(&mut self, square: Square, direction: Direction) {
154        let source_square_bit = square.as_bit_board();
155
156        self.elephants = shift_piece_in_direction(self.elephants, source_square_bit, direction);
157        self.camels = shift_piece_in_direction(self.camels, source_square_bit, direction);
158        self.horses = shift_piece_in_direction(self.horses, source_square_bit, direction);
159        self.dogs = shift_piece_in_direction(self.dogs, source_square_bit, direction);
160        self.cats = shift_piece_in_direction(self.cats, source_square_bit, direction);
161        self.rabbits = shift_piece_in_direction(self.rabbits, source_square_bit, direction);
162        self.p1_pieces = shift_piece_in_direction(self.p1_pieces, source_square_bit, direction);
163        self.all_pieces = shift_piece_in_direction(self.all_pieces, source_square_bit, direction);
164    }
165
166    pub fn move_piece(&mut self, square: Square, direction: Direction) -> bool {
167        self.move_piece_update_board(square, direction);
168        self.remove_trapped_pieces()
169    }
170
171    pub fn remove_trapped_pieces(&mut self) -> bool {
172        let trapped_animal_bits = self.get_trapped_piece_bits();
173        let piece_trapped = trapped_animal_bits != 0;
174
175        if piece_trapped {
176            let untrapped_animal_bits = !trapped_animal_bits;
177            self.elephants &= untrapped_animal_bits;
178            self.camels &= untrapped_animal_bits;
179            self.horses &= untrapped_animal_bits;
180            self.dogs &= untrapped_animal_bits;
181            self.cats &= untrapped_animal_bits;
182            self.rabbits &= untrapped_animal_bits;
183            self.p1_pieces &= untrapped_animal_bits;
184            self.all_pieces &= untrapped_animal_bits;
185        }
186
187        piece_trapped
188    }
189
190    pub fn get_vertical_symmetry(&self) -> Self {
191        Self::new(
192            get_bit_board_vertical_symmetry(self.p1_pieces),
193            get_bit_board_vertical_symmetry(self.elephants),
194            get_bit_board_vertical_symmetry(self.camels),
195            get_bit_board_vertical_symmetry(self.horses),
196            get_bit_board_vertical_symmetry(self.dogs),
197            get_bit_board_vertical_symmetry(self.cats),
198            get_bit_board_vertical_symmetry(self.rabbits),
199        )
200    }
201
202    pub fn place(&mut self, piece: Piece, square: Square, p1_piece: bool) {
203        let placement_bit = square.as_bit_board();
204
205        assert!(placement_bit & self.empty_placements(p1_piece) != 0, "Piece was placed on a non-empty or non placement square");
206
207        self.fill(piece, placement_bit, p1_piece);
208    }
209
210    pub fn fill(&mut self, piece: Piece, mask: u64, p1_piece: bool) {
211        *self.bits_by_piece_type_mut(piece) |= mask;
212
213        self.all_pieces |= mask;
214
215        if p1_piece {
216            self.p1_pieces |= mask;
217        }
218    }
219
220    pub fn empty_placements(&self, p1_placements: bool) -> u64 {
221        let placement_bits = if p1_placements { P1_PLACEMENT_MASK } else { P2_PLACEMENT_MASK };
222
223        !self.all_pieces & placement_bits
224    }
225}
226
227fn get_bit_board_vertical_symmetry(bit_board: u64) -> u64 {
228    map_bit_board_to_squares(bit_board).iter().map(|s| s.vertical_symmetry()).fold(0, |r, s| r | s.as_bit_board())
229}
230
231impl PartialEq for PieceBoard {
232    fn eq(&self, other: &Self) -> bool {
233        self.p1_pieces == other.p1_pieces
234            && self.elephants == other.elephants
235            && self.camels == other.camels
236            && self.horses == other.horses
237            && self.dogs == other.dogs
238            && self.cats == other.cats
239            && self.rabbits == other.rabbits
240    }
241}
242
243pub struct ExcludedSquaresWithDirections {
244    pub none: u64,
245    pub north: u64,
246    pub east: u64,
247    pub south: u64,
248    pub west: u64,
249}
250
251pub fn get_supported_square_exclusions(curr_player_piece_mask: u64) -> ExcludedSquaresWithDirections {
252    let supported_squares_n = shift_pieces_up!(curr_player_piece_mask);
253    let supported_squares_e = shift_pieces_right!(curr_player_piece_mask);
254    let supported_squares_s = shift_pieces_down!(curr_player_piece_mask);
255    let supported_squares_w = shift_pieces_left!(curr_player_piece_mask);
256    let north = supported_squares_e | supported_squares_s | supported_squares_w;
257    let east = supported_squares_n | supported_squares_s | supported_squares_w;
258    let south = supported_squares_n | supported_squares_e | supported_squares_w;
259    let west = supported_squares_n | supported_squares_e | supported_squares_s;
260    let none = north | south;
261
262    ExcludedSquaresWithDirections { north, east, south, west, none }
263}
264
265pub fn get_excluded_direction(i: usize, directions: &[Direction], exclusions: &ExcludedSquaresWithDirections) -> u64 {
266    // Exclude the supported_squares for the cases where a piece would be able to support itself after the first move.
267    // We check for i == 1 since a piece is only in a position to support itself after the first step.
268    // We can exclude support depending on the direction since it is not possible to come from a square that has a piece which is supporting in the matching direction.
269    if i == 1 { get_exclusion_for_direction(directions[0], exclusions) } else { exclusions.none }
270}
271
272pub fn get_exclusion_for_direction(direction: Direction, exclusions: &ExcludedSquaresWithDirections) -> u64 {
273    match direction {
274        Direction::Up => exclusions.north,
275        Direction::Right => exclusions.east,
276        Direction::Down => exclusions.south,
277        Direction::Left => exclusions.west,
278    }
279}
280
281pub fn get_threatened_pieces(predator_piece_mask: u64, prey_piece_mask: u64, piece_board: &PieceBoard) -> u64 {
282    let predator_elephant_influence = get_influenced_squares(piece_board.elephants & predator_piece_mask);
283    let predator_camel_influence = get_influenced_squares(piece_board.camels & predator_piece_mask);
284    let predator_horse_influence = get_influenced_squares(piece_board.horses & predator_piece_mask);
285    let predator_dog_influence = get_influenced_squares(piece_board.dogs & predator_piece_mask);
286    let predator_cat_influence = get_influenced_squares(piece_board.cats & predator_piece_mask);
287
288    let camel_threats = predator_elephant_influence;
289    let horse_threats = camel_threats | predator_camel_influence;
290    let dog_threats = horse_threats | predator_horse_influence;
291    let cat_threats = dog_threats | predator_dog_influence;
292    let rabbit_threats = cat_threats | predator_cat_influence;
293
294    let threatened_pieces =
295        (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);
296
297    threatened_pieces & prey_piece_mask
298}
299
300pub fn get_threatened_pieces_for_dir(predator_piece_mask: u64, prey_piece_mask: u64, direction: Direction, piece_board: &PieceBoard) -> u64 {
301    let predator_elephant_influence = shift_pieces_in_direction(piece_board.elephants & predator_piece_mask, direction);
302    let predator_camel_influence = shift_pieces_in_direction(piece_board.camels & predator_piece_mask, direction);
303    let predator_horse_influence = shift_pieces_in_direction(piece_board.horses & predator_piece_mask, direction);
304    let predator_dog_influence = shift_pieces_in_direction(piece_board.dogs & predator_piece_mask, direction);
305    let predator_cat_influence = shift_pieces_in_direction(piece_board.cats & predator_piece_mask, direction);
306
307    let camel_threats = predator_elephant_influence;
308    let horse_threats = camel_threats | predator_camel_influence;
309    let dog_threats = horse_threats | predator_horse_influence;
310    let cat_threats = dog_threats | predator_dog_influence;
311    let rabbit_threats = cat_threats | predator_cat_influence;
312
313    let threatened_pieces =
314        (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);
315
316    threatened_pieces & prey_piece_mask
317}
318
319pub fn get_threats_for_piece_type(predator_piece_mask: u64, piece_type: Piece, piece_board: &PieceBoard) -> u64 {
320    match piece_type {
321        Piece::Elephant => 0,
322        Piece::Camel => get_influenced_squares(piece_board.elephants & predator_piece_mask),
323        Piece::Horse => get_influenced_squares((piece_board.elephants | piece_board.camels) & predator_piece_mask),
324        Piece::Dog => get_influenced_squares((piece_board.elephants | piece_board.camels | piece_board.horses) & predator_piece_mask),
325        Piece::Cat => get_influenced_squares((piece_board.elephants | piece_board.camels | piece_board.horses | piece_board.dogs) & predator_piece_mask),
326        Piece::Rabbit => get_influenced_squares((piece_board.elephants | piece_board.camels | piece_board.horses | piece_board.dogs | piece_board.cats) & predator_piece_mask),
327    }
328}
329
330pub fn get_influenced_squares(piece_board: u64) -> u64 {
331    shift_pieces_up!(piece_board) | shift_pieces_right!(piece_board) | shift_pieces_down!(piece_board) | shift_pieces_left!(piece_board)
332}
333
334pub fn can_move_in_direction(direction: Direction, empty_squares: u64) -> u64 {
335    shift_pieces_in_opp_direction(direction, empty_squares)
336}
337
338fn shift_pieces_in_opp_direction(direction: Direction, bits: u64) -> u64 {
339    match direction {
340        Direction::Up => shift_pieces_down!(bits),
341        Direction::Right => shift_pieces_left!(bits),
342        Direction::Down => shift_pieces_up!(bits),
343        Direction::Left => shift_pieces_right!(bits),
344    }
345}
346
347pub fn shift_pieces_in_direction(bits: u64, direction: Direction) -> u64 {
348    match direction {
349        Direction::Up => shift_pieces_up!(bits),
350        Direction::Right => shift_pieces_right!(bits),
351        Direction::Down => shift_pieces_down!(bits),
352        Direction::Left => shift_pieces_left!(bits),
353    }
354}
355
356fn get_piece_type_at_bit(square_bit: u64, piece_board: &PieceBoard) -> Piece {
357    if piece_board.rabbits & square_bit != 0 {
358        Piece::Rabbit
359    } else if piece_board.elephants & square_bit != 0 {
360        Piece::Elephant
361    } else if piece_board.camels & square_bit != 0 {
362        Piece::Camel
363    } else if piece_board.horses & square_bit != 0 {
364        Piece::Horse
365    } else if piece_board.dogs & square_bit != 0 {
366        Piece::Dog
367    } else {
368        Piece::Cat
369    }
370}
371
372fn animal_is_on_trap(piece_board: &PieceBoard) -> bool {
373    (piece_board.all_pieces & TRAP_MASK) != 0
374}
375
376fn get_both_player_unsupported_piece_bits(piece_board: &PieceBoard) -> u64 {
377    piece_board.all_pieces & !get_both_player_supported_pieces(piece_board)
378}
379
380fn get_both_player_supported_pieces(piece_board: &PieceBoard) -> u64 {
381    let p1_pieces = piece_board.p1_pieces;
382    let p2_pieces = piece_board.all_pieces & !p1_pieces;
383
384    get_supported_pieces(p1_pieces) | get_supported_pieces(p2_pieces)
385}
386
387pub fn get_supported_pieces(piece_bits: u64) -> u64 {
388    let up_supported_pieces = piece_bits & shift_pieces_up!(piece_bits);
389    let right_supported_pieces = piece_bits & shift_pieces_right!(piece_bits);
390    let down_supported_pieces = piece_bits & shift_pieces_down!(piece_bits);
391    let left_supported_pieces = piece_bits & shift_pieces_left!(piece_bits);
392
393    up_supported_pieces | right_supported_pieces | down_supported_pieces | left_supported_pieces
394}
395
396fn shift_piece_in_direction(piece_board: u64, source_square_bit: u64, direction: Direction) -> u64 {
397    shift_in_direction(piece_board & source_square_bit, direction) | piece_board & !source_square_bit
398}
399
400fn shift_in_direction(bits: u64, direction: Direction) -> u64 {
401    match direction {
402        Direction::Up => shift_up!(bits),
403        Direction::Right => shift_right!(bits),
404        Direction::Down => shift_down!(bits),
405        Direction::Left => shift_left!(bits),
406    }
407}