chess_engine/
position.rs

1use super::Color;
2use alloc::{
3    string::{String, ToString},
4    vec::Vec,
5};
6
7pub const A1: Position = Position::new(0, 0);
8pub const A2: Position = Position::new(1, 0);
9pub const A3: Position = Position::new(2, 0);
10pub const A4: Position = Position::new(3, 0);
11pub const A5: Position = Position::new(4, 0);
12pub const A6: Position = Position::new(5, 0);
13pub const A7: Position = Position::new(6, 0);
14pub const A8: Position = Position::new(7, 0);
15
16pub const B1: Position = Position::new(0, 1);
17pub const B2: Position = Position::new(1, 1);
18pub const B3: Position = Position::new(2, 1);
19pub const B4: Position = Position::new(3, 1);
20pub const B5: Position = Position::new(4, 1);
21pub const B6: Position = Position::new(5, 1);
22pub const B7: Position = Position::new(6, 1);
23pub const B8: Position = Position::new(7, 1);
24
25pub const C1: Position = Position::new(0, 2);
26pub const C2: Position = Position::new(1, 2);
27pub const C3: Position = Position::new(2, 2);
28pub const C4: Position = Position::new(3, 2);
29pub const C5: Position = Position::new(4, 2);
30pub const C6: Position = Position::new(5, 2);
31pub const C7: Position = Position::new(6, 2);
32pub const C8: Position = Position::new(7, 2);
33
34pub const D1: Position = Position::new(0, 3);
35pub const D2: Position = Position::new(1, 3);
36pub const D3: Position = Position::new(2, 3);
37pub const D4: Position = Position::new(3, 3);
38pub const D5: Position = Position::new(4, 3);
39pub const D6: Position = Position::new(5, 3);
40pub const D7: Position = Position::new(6, 3);
41pub const D8: Position = Position::new(7, 3);
42
43pub const E1: Position = Position::new(0, 4);
44pub const E2: Position = Position::new(1, 4);
45pub const E3: Position = Position::new(2, 4);
46pub const E4: Position = Position::new(3, 4);
47pub const E5: Position = Position::new(4, 4);
48pub const E6: Position = Position::new(5, 4);
49pub const E7: Position = Position::new(6, 4);
50pub const E8: Position = Position::new(7, 4);
51
52pub const F1: Position = Position::new(0, 5);
53pub const F2: Position = Position::new(1, 5);
54pub const F3: Position = Position::new(2, 5);
55pub const F4: Position = Position::new(3, 5);
56pub const F5: Position = Position::new(4, 5);
57pub const F6: Position = Position::new(5, 5);
58pub const F7: Position = Position::new(6, 5);
59pub const F8: Position = Position::new(7, 5);
60
61pub const G1: Position = Position::new(0, 6);
62pub const G2: Position = Position::new(1, 6);
63pub const G3: Position = Position::new(2, 6);
64pub const G4: Position = Position::new(3, 6);
65pub const G5: Position = Position::new(4, 6);
66pub const G6: Position = Position::new(5, 6);
67pub const G7: Position = Position::new(6, 6);
68pub const G8: Position = Position::new(7, 6);
69
70pub const H1: Position = Position::new(0, 7);
71pub const H2: Position = Position::new(1, 7);
72pub const H3: Position = Position::new(2, 7);
73pub const H4: Position = Position::new(3, 7);
74pub const H5: Position = Position::new(4, 7);
75pub const H6: Position = Position::new(5, 7);
76pub const H7: Position = Position::new(6, 7);
77pub const H8: Position = Position::new(7, 7);
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
80pub struct Position {
81    row: i32,
82    col: i32,
83}
84
85impl core::fmt::Display for Position {
86    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
87        write!(
88            f,
89            "{}{}",
90            match self.col {
91                0 => 'a',
92                1 => 'b',
93                2 => 'c',
94                3 => 'd',
95                4 => 'e',
96                5 => 'f',
97                6 => 'g',
98                7 => 'h',
99                _ => '?',
100            },
101            self.row + 1
102        )
103    }
104}
105
106impl Position {
107    /// Return the starting position for a given color's king.
108    #[inline]
109    pub const fn king_pos(color: Color) -> Self {
110        match color {
111            Color::White => Self::new(0, 4),
112            Color::Black => Self::new(7, 4),
113        }
114    }
115
116    /// Return the starting position for a given color's queen.
117    #[inline]
118    pub const fn queen_pos(color: Color) -> Self {
119        match color {
120            Color::White => Self::new(0, 3),
121            Color::Black => Self::new(7, 3),
122        }
123    }
124
125    /// Create a `Position` from its respective row or column number.
126    /// The row and column numbers can be any of 0, 1, 2, 3, 4, 5, 6, or 7.
127    /// 
128    /// Examples:
129    /// - `A1 = Position::new(0, 0)`
130    /// - `A8 = Position::new(7, 0)`
131    /// - `H1 = Position::new(0, 7)`
132    /// - `H8 = Position::new(7, 7)`
133    #[inline]
134    pub const fn new(row: i32, col: i32) -> Self {
135        Self { row, col }
136    }
137
138    /// Parse a position from PGN. This simply just supports positions like
139    /// `e4` and `D8`.
140    pub fn pgn(s: &str) -> Result<Self, String> {
141        let s = s.trim().to_lowercase();
142        let col = s.chars().next().ok_or(format!("invalid pgn `{}`", s))?;
143        let row = s
144            .chars()
145            .nth(1)
146            .ok_or(format!("invalid pgn `{}`", s))?
147            .to_string()
148            .parse::<u32>()
149            .map_err(|_| format!("invalid pgn `{}`", s))? as i32;
150        let c = match col {
151            'a' => 0,
152            'b' => 1,
153            'c' => 2,
154            'd' => 3,
155            'e' => 4,
156            'f' => 5,
157            'g' => 6,
158            'h' => 7,
159            _ => return Err(format!("invalid column character `{}`", col)),
160        };
161
162        if 1 <= row || row <= 8 {
163            Ok(Self::new(row - 1, c))
164        } else {
165            Err(format!("invalid row number `{}`", row))
166        }
167    }
168
169    /// Is this position a valid spot on the board?
170    #[inline]
171    pub fn is_on_board(&self) -> bool {
172        !self.is_off_board()
173    }
174
175    /// Is this position NOT a valid spot on the board?
176    #[inline]
177    pub fn is_off_board(&self) -> bool {
178        self.row < 0 || self.row > 7 || self.col < 0 || self.col > 7
179    }
180
181    /// Get the row number of the position.
182    /// This can be any of 0, 1, 2, 3, 4, 5, 6, or 7.
183    #[inline]
184    pub fn get_row(&self) -> i32 {
185        self.row
186    }
187
188    #[inline]
189    pub fn get_col(&self) -> i32 {
190        self.col
191    }
192
193    #[inline]
194    fn add_row(&self, drow: i32) -> Self {
195        let mut result = *self;
196        result.row += drow;
197        result
198    }
199
200    #[inline]
201    fn add_col(&self, dcol: i32) -> Self {
202        let mut result = *self;
203        result.col += dcol;
204        result
205    }
206
207    /// Is this position diagonal to another position?
208    #[inline]
209    pub fn is_diagonal_to(&self, other: Self) -> bool {
210        // Algorithm for determining whether or not two squares are diagonal
211        // https://math.stackexchange.com/questions/1194565/how-to-know-if-two-points-are-diagonally-aligned
212        (self.col - other.col).abs() == (self.row - other.row).abs()
213    }
214
215    /// Get the diagonal distance between two positions
216    #[inline]
217    fn diagonal_distance(&self, other: Self) -> i32 {
218        (self.col - other.col).abs()
219    }
220    
221    /// Is this position orthogonal to another position?
222    #[inline]
223    pub fn is_orthogonal_to(&self, other: Self) -> bool {
224        (self.col == other.col) || (self.row == other.row)
225    }
226
227    /// Get the orthogonal distance between two positions
228    #[inline]
229    fn orthogonal_distance(&self, other: Self) -> i32 {
230        (self.col - other.col).abs() + (self.row - other.row).abs()
231    }
232
233    /// Is this position adjacent to another position?
234    /// 
235    /// Adjacent positions have either:
236    /// 1. A diagonal distance of one from each other
237    /// 2. An orthogonal distance of one from each other
238    #[inline]
239    pub fn is_adjacent_to(&self, other: Self) -> bool {
240        if self.is_orthogonal_to(other) {
241            self.orthogonal_distance(other) == 1
242        } else if self.is_diagonal_to(other) {
243            self.diagonal_distance(other) == 1
244        } else {
245            false
246        }
247    }
248
249    /// Is this position beneath another position on the board?
250    /// Pieces "beneath" other pieces on the board have lower ranks.
251    /// 
252    /// So, for example, A7 is below A8.
253    #[inline]
254    pub fn is_below(&self, other: Self) -> bool {
255        self.row < other.row
256    }
257
258    /// Is this position above another position on the board?
259    /// Pieces "above" other pieces on the board have higher ranks.
260    /// 
261    /// So, for example, A8 is above A8.
262    #[inline]
263    pub fn is_above(&self, other: Self) -> bool {
264        self.row > other.row
265    }
266
267    /// Is this position left of another position on the board?
268    /// Pieces "left of" other pieces on the board have a lower
269    /// lexigraphical column character.
270    /// 
271    /// So, for example, A8 is left of B8.
272    #[inline]
273    pub fn is_left_of(&self, other: Self) -> bool {
274        self.col < other.col
275    }
276
277    /// Is this position right of another position on the board?
278    /// Pieces "right of" other pieces on the board have a higher
279    /// lexigraphical column character.
280    /// 
281    /// So, for example, B8 is right of A8.
282    #[inline]
283    pub fn is_right_of(&self, other: Self) -> bool {
284        self.col > other.col
285    }
286
287    /// Get the position directly below this position.
288    /// 
289    /// IMPORTANT NOTE: This will NOT check for positions
290    /// off of the board! You could easily get an invalid
291    /// position if you do not check with the `is_on_board`
292    /// method!
293    #[inline]
294    pub fn next_below(&self) -> Self {
295        Self::new(self.row - 1, self.col)
296    }
297
298    /// Get the position directly above this position.
299    /// 
300    /// IMPORTANT NOTE: This will NOT check for positions
301    /// off of the board! You could easily get an invalid
302    /// position if you do not check with the `is_on_board`
303    /// method!
304    #[inline]
305    pub fn next_above(&self) -> Self {
306        Self::new(self.row + 1, self.col)
307    }
308
309    /// Get the next square upwards from a respective player's
310    /// pawn.
311    /// 
312    /// IMPORTANT NOTE: This will NOT check for positions
313    /// off of the board! You could easily get an invalid
314    /// position if you do not check with the `is_on_board`
315    /// method!
316    #[inline]
317    pub fn pawn_up(&self, ally_color: Color) -> Self {
318        match ally_color {
319            Color::White => self.next_above(),
320            Color::Black => self.next_below(),
321        }
322    }
323
324    /// Get the next square backwards from a respective player's
325    /// pawn.
326    /// 
327    /// IMPORTANT NOTE: This will NOT check for positions
328    /// off of the board! You could easily get an invalid
329    /// position if you do not check with the `is_on_board`
330    /// method!
331    #[inline]
332    pub fn pawn_back(&self, ally_color: Color) -> Self {
333        self.pawn_up(!ally_color)
334    }
335    
336    /// Get the position directly left of this position.
337    /// 
338    /// IMPORTANT NOTE: This will NOT check for positions
339    /// off of the board! You could easily get an invalid
340    /// position if you do not check with the `is_on_board`
341    /// method!
342    #[inline]
343    pub fn next_left(&self) -> Self {
344        Self::new(self.row, self.col - 1)
345    }
346
347    /// Get the position directly right of this position.
348    /// 
349    /// IMPORTANT NOTE: This will NOT check for positions
350    /// off of the board! You could easily get an invalid
351    /// position if you do not check with the `is_on_board`
352    /// method!
353    #[inline]
354    pub fn next_right(&self) -> Self {
355        Self::new(self.row, self.col + 1)
356    }
357
358    /// Is this pawn on the starting rank for the respective player?
359    #[inline]
360    pub fn is_starting_pawn(&self, color: Color) -> bool {
361        match color {
362            Color::White => self.row == 1,
363            Color::Black => self.row == 6,
364        }
365    }
366
367    /// Is this the starting position of the kingside rook?
368    #[inline]
369    pub fn is_kingside_rook(&self) -> bool {
370        (self.row == 0 || self.row == 7) && self.col == 7
371    }
372    
373    /// Is this the starting position of the queenside rook?
374    #[inline]
375    pub fn is_queenside_rook(&self) -> bool {
376        (self.row == 0 || self.row == 7) && self.col == 0
377    }
378
379    /// Get the list of positions from this position to another
380    /// position, moving diagonally.
381    /// 
382    /// This does _not_ include the `from` position, and includes the `to` position.
383    pub fn diagonals_to(&self, to: Self) -> Vec<Self> {
384        if !self.is_diagonal_to(to) {
385            return Vec::new();
386        }
387
388        let row_step;
389        let col_step;
390        if self.is_left_of(to) {
391            col_step = 1;
392        } else {
393            col_step = -1;
394        }
395
396        if self.is_below(to) {
397            row_step = 1;
398        } else {
399            row_step = -1;
400        }
401
402        let mut acc = *self;
403        let mut result = Vec::new();
404        for _ in 0..self.diagonal_distance(to) {
405            acc = acc.add_row(row_step).add_col(col_step);
406            result.push(acc);
407        }
408
409        result
410    }
411
412    /// Get the list of positions from this position to another
413    /// position, moving orthogonally.
414    /// 
415    /// This does _not_ include the `from` position, and includes the `to` position.
416    pub fn orthogonals_to(&self, to: Self) -> Vec<Self> {
417        if !self.is_orthogonal_to(to) {
418            return Vec::new();
419        }
420        let mut row_step = 0;
421        let mut col_step = 0;
422        if self.is_left_of(to) {
423            col_step = 1;
424        } else if self.is_right_of(to) {
425            col_step = -1;
426        } else if self.is_above(to) {
427            row_step = -1;
428        } else if self.is_below(to) {
429            row_step = 1;
430        }
431
432        let mut acc = *self;
433        let mut result = Vec::new();
434
435        for _ in 0..self.orthogonal_distance(to) {
436            acc = acc.add_row(row_step).add_col(col_step);
437            result.push(acc);
438        }
439
440        result
441    }
442
443    #[inline]
444    pub fn is_knight_move(&self, other: Self) -> bool {
445        (self.row - other.row).abs() == 2 && (self.col - other.col).abs() == 1
446            || (self.row - other.row).abs() == 1 && (self.col - other.col).abs() == 2
447    }
448}