chess_lab/common/constants/position.rs
1use std::ops;
2
3/// Represents a position on the board.
4/// The position is represented by a column and a row.
5///
6/// # Examples
7/// ```
8/// use chess_lab::constants::Position;
9///
10/// let pos = Position::new(0, 0);
11///
12/// assert_eq!(pos.to_string(), "a1");
13///
14/// let pos = Position::from_string("a1");
15///
16/// assert_eq!(pos.col, 0);
17/// assert_eq!(pos.row, 0);
18/// ```
19///
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct Position {
22 pub col: u8,
23 pub row: u8,
24}
25
26impl Position {
27 /// Creates a new position
28 ///
29 /// # Arguments
30 /// * `col`: The column of the position (between 0 and 7)
31 /// * `row`: The row of the position (between 0 and 7)
32 ///
33 /// # Returns
34 /// A new position
35 ///
36 /// # Panics
37 /// Panics if the column or row is out of bounds
38 ///
39 /// # Examples
40 /// ```
41 /// use chess_lab::constants::Position;
42 ///
43 /// let pos = Position::new(0, 0);
44 ///
45 /// assert_eq!(pos.col, 0);
46 /// assert_eq!(pos.row, 0);
47 /// ```
48 ///
49 pub fn new(col: u8, row: u8) -> Position {
50 assert!(col < 8 && row < 8, "Position out of bounds");
51 Position { col, row }
52 }
53
54 /// Creates a new position from a string
55 ///
56 /// # Arguments
57 /// * `s`: The string representation of the position
58 ///
59 /// # Returns
60 /// A new position
61 ///
62 /// # Panics
63 /// Panics if the string is not a valid position
64 ///
65 /// # Examples
66 /// ```
67 /// use chess_lab::constants::Position;
68 ///
69 /// let pos = Position::from_string("a1");
70 ///
71 /// assert_eq!(pos.col, 0);
72 /// assert_eq!(pos.row, 0);
73 /// ```
74 ///
75 pub fn from_string(s: &str) -> Position {
76 assert!(s.len() == 2, "Invalid position string");
77 if s.len() != 2 {
78 assert!(false, "Invalid position string");
79 }
80 let col = s.chars().nth(0).unwrap() as u8 - 'a' as u8;
81 let row = s.chars().nth(1).unwrap() as u8 - '1' as u8;
82 Position::new(col, row)
83 }
84
85 /// Converts the position to a string
86 ///
87 /// # Returns
88 /// The string representation of the position
89 ///
90 /// # Examples
91 /// ```
92 /// use chess_lab::constants::Position;
93 ///
94 /// let pos = Position::new(0, 0);
95 ///
96 /// assert_eq!(pos.to_bitboard(), 0x0000000000000001);
97 /// ```
98 ///
99 pub fn to_bitboard(&self) -> u64 {
100 1 << (self.row * 8 + self.col)
101 }
102
103 /// Converts a bitboard to a list of positions
104 ///
105 /// # Arguments
106 /// * `bitboard`: The bitboard to convert
107 ///
108 /// # Returns
109 /// A list of positions
110 ///
111 /// # Examples
112 /// ```
113 /// use chess_lab::constants::Position;
114 ///
115 /// let positions = Position::from_bitboard(0x0000000000000001);
116 ///
117 /// assert_eq!(positions.len(), 1);
118 /// assert_eq!(positions[0].to_string(), "a1");
119 /// ```
120 ///
121 pub fn from_bitboard(bitboard: u64) -> Vec<Position> {
122 let mut positions = Vec::new();
123 let mut bitboard = bitboard;
124 while bitboard != 0 {
125 let pos = bitboard.trailing_zeros() as u8;
126 positions.push(Position::new(pos % 8, pos / 8));
127 bitboard &= bitboard - 1;
128 }
129 positions
130 }
131
132 /// Gets the direction between two positions
133 ///
134 /// # Arguments
135 /// * `other`: The other position
136 ///
137 /// # Returns
138 /// The direction between the two positions
139 ///
140 /// # Examples
141 /// ```
142 /// use chess_lab::constants::Position;
143 ///
144 /// let pos1 = Position::new(0, 0);
145 /// let pos2 = Position::new(1, 1);
146 ///
147 /// let direction = pos1.direction(&pos2);
148 ///
149 /// assert_eq!(direction, (1, 1));
150 /// ```
151 pub fn direction(&self, other: &Position) -> (i8, i8) {
152 let mut col = other.col as i8 - self.col as i8;
153 let mut row = other.row as i8 - self.row as i8;
154
155 col = if col == 0 { 0 } else { col / col.abs() };
156 row = if row == 0 { 0 } else { row / row.abs() };
157 (col, row)
158 }
159}
160
161impl ops::Add<(i8, i8)> for &Position {
162 type Output = Position;
163
164 /// Adds a certain offset to the position
165 ///
166 /// # Arguments
167 /// * `other`: The offset to add
168 ///
169 /// # Returns
170 /// The new position
171 ///
172 /// # Examples
173 /// ```
174 /// use chess_lab::constants::Position;
175 ///
176 /// let pos = Position::new(0, 0);
177 /// let new_pos = &pos + (1, 1);
178 ///
179 /// assert_eq!(new_pos.col, 1);
180 /// assert_eq!(new_pos.row, 1);
181 /// ```
182 ///
183 fn add(self, other: (i8, i8)) -> Position {
184 Position::new(
185 (self.col as i8 + other.0) as u8,
186 (self.row as i8 + other.1) as u8,
187 )
188 }
189}
190
191impl ops::Sub<&Position> for &Position {
192 type Output = (i8, i8);
193
194 /// Gets the offset between two positions
195 ///
196 /// # Arguments
197 /// * `other`: The other position
198 ///
199 /// # Returns
200 /// The offset between the two positions
201 ///
202 /// # Examples
203 /// ```
204 /// use chess_lab::constants::Position;
205 ///
206 /// let pos1 = Position::new(0, 0);
207 /// let pos2 = Position::new(1, 1);
208 ///
209 /// let offset = &pos1 - &pos2;
210 ///
211 /// assert_eq!(offset, (-1, -1));
212 /// ```
213 ///
214 fn sub(self, other: &Position) -> (i8, i8) {
215 (
216 self.col as i8 - other.col as i8,
217 self.row as i8 - other.row as i8,
218 )
219 }
220}
221
222impl ops::Sub<(i8, i8)> for &Position {
223 type Output = Position;
224
225 /// Subtracts a certain offset from the position
226 ///
227 /// # Arguments
228 /// * `other`: The offset to subtract
229 ///
230 /// # Returns
231 /// The new position
232 ///
233 /// # Examples
234 /// ```
235 /// use chess_lab::constants::Position;
236 ///
237 /// let pos = Position::new(1, 1);
238 /// let new_pos = &pos - (1, 1);
239 ///
240 /// assert_eq!(new_pos.col, 0);
241 /// assert_eq!(new_pos.row, 0);
242 /// ```
243 ///
244 fn sub(self, other: (i8, i8)) -> Position {
245 Position {
246 col: (self.col as i8 - other.0) as u8,
247 row: (self.row as i8 - other.1) as u8,
248 }
249 }
250}
251
252impl ToString for Position {
253 /// Converts the position to a string
254 ///
255 /// # Returns
256 /// The string representation of the position
257 ///
258 /// # Examples
259 /// ```
260 /// use chess_lab::constants::Position;
261 ///
262 /// let pos = Position::new(0, 0);
263 ///
264 /// assert_eq!(pos.to_string(), "a1");
265 /// ```
266 ///
267 fn to_string(&self) -> String {
268 format!(
269 "{}{}",
270 ('a' as u8 + self.col) as char,
271 ('1' as u8 + self.row) as char
272 )
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::Position;
279
280 #[test]
281 fn test_position() {
282 let pos = Position::new(0, 0);
283 assert_eq!(pos.to_string(), "a1");
284 let pos = Position::new(7, 7);
285 assert_eq!(pos.to_string(), "h8");
286 let pos = Position::from_string("a1");
287 assert_eq!(pos.col, 0);
288 assert_eq!(pos.row, 0);
289 let pos = Position::from_string("h8");
290 assert_eq!(pos.col, 7);
291 assert_eq!(pos.row, 7);
292 }
293
294 #[test]
295 fn test_position_to_bitboard() {
296 let pos = Position::new(0, 0);
297 assert_eq!(pos.to_bitboard(), 0x0000000000000001);
298 let pos = Position::new(7, 7);
299 assert_eq!(pos.to_bitboard(), 0x8000000000000000);
300 }
301
302 #[test]
303 fn test_position_from_bitboard() {
304 let positions = Position::from_bitboard(0x0000000000000001);
305 let pos = positions.first().unwrap();
306 assert_eq!(pos.to_string(), "a1");
307 assert_eq!(positions.len(), 1);
308 let positions = Position::from_bitboard(0x8000000000000000);
309 let pos = positions.first().unwrap();
310 assert_eq!(pos.to_string(), "h8");
311 assert_eq!(positions.len(), 1);
312 }
313
314 #[test]
315 fn test_position_sub() {
316 let pos1 = Position::new(1, 1);
317 let pos2 = Position::new(0, 0);
318 let pos3 = &pos1 - &pos2;
319 assert_eq!(pos3.0, 1);
320 assert_eq!(pos3.1, 1);
321 }
322
323 #[test]
324 fn test_position_add_tuple() {
325 let pos1 = Position::new(0, 0);
326 let pos2 = (1, 1);
327 let pos3 = &pos1 + pos2;
328 assert_eq!(pos3.to_string(), "b2");
329 }
330
331 #[test]
332 fn test_position_sub_tuple() {
333 let pos1 = Position::new(1, 1);
334 let pos2 = (1, 1);
335 let pos3 = &pos1 - pos2;
336 assert_eq!(pos3.to_string(), "a1");
337 }
338
339 #[test]
340 fn test_direction() {
341 let pos1 = Position::new(0, 0);
342 let pos2 = Position::new(1, 1);
343 let dir = pos1.direction(&pos2);
344 assert_eq!(dir, (1, 1));
345 }
346}