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}