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}