Skip to main content

arimaa_engine/
square.rs

1use anyhow::anyhow;
2use std::fmt;
3use std::str::FromStr;
4
5use super::Direction;
6use super::bits::{for_each_bit_idx, single_bit_index};
7use super::constants::{ASCII_LETTER_A, BOARD_HEIGHT, BOARD_SIZE, BOARD_WIDTH};
8
9#[derive(Hash, Eq, PartialEq, Clone, Copy, Default, Ord, PartialOrd)]
10pub struct Square(u8);
11
12impl Square {
13    pub fn new(column: char, row: usize) -> Self {
14        let index = (column as u8 - ASCII_LETTER_A) + (BOARD_HEIGHT - row) as u8 * 8;
15        Square(index)
16    }
17
18    pub fn from_index(index: u8) -> Self {
19        Square(index)
20    }
21
22    pub fn from_bit_board(board: u64) -> Self {
23        Square(single_bit_index(board as u128) as u8)
24    }
25
26    pub fn from_multiple_bits(board: u64) -> Vec<Self> {
27        let mut squares = Vec::with_capacity(board.count_ones() as usize);
28
29        for_each_bit_idx(board, |idx| squares.push(Square::from_index(idx as u8)));
30
31        squares
32    }
33
34    pub fn as_bit_board(&self) -> u64 {
35        1 << self.0
36    }
37
38    pub fn get_index(&self) -> usize {
39        self.0 as usize
40    }
41
42    pub fn invert(&self) -> Self {
43        Square((BOARD_HEIGHT * BOARD_WIDTH) as u8 - 1 - self.0)
44    }
45
46    pub fn vertical_symmetry(&self) -> Self {
47        Square(self.0 ^ 7)
48    }
49
50    pub fn column_char(&self) -> char {
51        let index = self.0 as usize;
52        let column = index % BOARD_WIDTH;
53
54        (ASCII_LETTER_A + column as u8) as char
55    }
56
57    pub fn row(&self) -> u8 {
58        let index = self.0 as usize;
59        let row = BOARD_HEIGHT - (index / BOARD_WIDTH);
60        row as u8
61    }
62
63    pub fn shift_in_direction(&self, direction: Direction) -> Self {
64        let new_index = match direction {
65            Direction::Up => self.0 - BOARD_WIDTH as u8,
66            Direction::Right => self.0 + 1,
67            Direction::Down => self.0 + BOARD_WIDTH as u8,
68            Direction::Left => self.0 - 1,
69        };
70
71        Self::from_index(new_index)
72    }
73
74    pub fn can_shift_in_direction(&self, direction: Direction) -> bool {
75        match direction {
76            Direction::Up => self.0 >= BOARD_WIDTH as u8,
77            Direction::Right => self.0 % BOARD_WIDTH as u8 != 7,
78            Direction::Down => (self.0 + BOARD_WIDTH as u8) < (BOARD_SIZE as u8),
79            Direction::Left => !self.0.is_multiple_of(BOARD_WIDTH as u8),
80        }
81    }
82
83    pub fn is_adjacent(&self, square: &Self) -> bool {
84        let column_diff = isize::abs(self.column_char() as isize - square.column_char() as isize);
85        let row_diff = i8::abs(self.row() as i8 - square.row() as i8);
86
87        column_diff == 1 && row_diff == 1
88    }
89}
90
91impl fmt::Display for Square {
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        let column = self.column_char();
94        let row = self.row();
95
96        write!(f, "{column}{row}", column = column, row = row)
97    }
98}
99
100impl fmt::Debug for Square {
101    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102        write!(f, "{}", self)
103    }
104}
105
106impl FromStr for Square {
107    type Err = anyhow::Error;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        let chars: Vec<char> = s.chars().collect();
111
112        if chars.len() == 2 {
113            let column = chars[0];
114            let row = chars[1];
115
116            if let Ok(row) = row.to_string().parse() {
117                let column_as_num = column as u8 - ASCII_LETTER_A + 1;
118                if column_as_num >= 1 && column_as_num <= BOARD_WIDTH as u8 && (1..=BOARD_HEIGHT).contains(&row) {
119                    return Ok(Square::new(column, row));
120                }
121            }
122        }
123
124        Err(anyhow!("Invalid value for square"))
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    fn assert_can_shift_in_direction(invalid_squares: &[&str], direction: Direction) {
133        for idx in 0..64 {
134            let square = Square::from_index(idx);
135            if invalid_squares.contains(&square.to_string().as_str()) {
136                assert!(!square.can_shift_in_direction(direction));
137            } else {
138                assert!(square.can_shift_in_direction(direction));
139            }
140        }
141    }
142
143    #[test]
144    fn test_can_shift_in_direction_up() {
145        let invalid_squares = ["a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8"];
146        let direction = Direction::Up;
147
148        assert_can_shift_in_direction(&invalid_squares, direction);
149    }
150
151    #[test]
152    fn test_can_shift_in_direction_right() {
153        let invalid_squares = ["h8", "h7", "h6", "h5", "h4", "h3", "h2", "h1"];
154        let direction = Direction::Right;
155
156        assert_can_shift_in_direction(&invalid_squares, direction);
157    }
158
159    #[test]
160    fn test_can_shift_in_direction_down() {
161        let invalid_squares = ["a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1"];
162        let direction = Direction::Down;
163
164        assert_can_shift_in_direction(&invalid_squares, direction);
165    }
166
167    #[test]
168    fn test_can_shift_in_direction_left() {
169        let invalid_squares = ["a8", "a7", "a6", "a5", "a4", "a3", "a2", "a1"];
170        let direction = Direction::Left;
171
172        assert_can_shift_in_direction(&invalid_squares, direction);
173    }
174}