chess/
square.rs

1use std::cmp::max;
2use std::fmt;
3use std::str::FromStr;
4
5use crate::{
6    Color, Direction, Error, File, Rank, BOARD_CELL_PX_SIZE, BOARD_SIZE, NUM_FILES, NUM_RANKS,
7};
8
9/// Represent a square on the chess board.
10#[rustfmt::skip]
11#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
12#[repr(u8)]
13pub enum Square {
14    A1, B1, C1, D1, E1, F1, G1, H1,
15    A2, B2, C2, D2, E2, F2, G2, H2,
16    A3, B3, C3, D3, E3, F3, G3, H3,
17    A4, B4, C4, D4, E4, F4, G4, H4,
18    A5, B5, C5, D5, E5, F5, G5, H5,
19    A6, B6, C6, D6, E6, F6, G6, H6,
20    A7, B7, C7, D7, E7, F7, G7, H7,
21    A8, B8, C8, D8, E8, F8, G8, H8,
22}
23
24/// Numbers of [`Square`].
25pub const NUM_SQUARES: usize = (BOARD_SIZE.0 * BOARD_SIZE.1) as usize;
26
27/// Enumerate all [`Square`].
28#[rustfmt::skip]
29pub const ALL_SQUARES: [Square; NUM_SQUARES] = [
30    Square::A1, Square::B1, Square::C1, Square::D1, Square::E1, Square::F1, Square::G1, Square::H1,
31    Square::A2, Square::B2, Square::C2, Square::D2, Square::E2, Square::F2, Square::G2, Square::H2,
32    Square::A3, Square::B3, Square::C3, Square::D3, Square::E3, Square::F3, Square::G3, Square::H3,
33    Square::A4, Square::B4, Square::C4, Square::D4, Square::E4, Square::F4, Square::G4, Square::H4,
34    Square::A5, Square::B5, Square::C5, Square::D5, Square::E5, Square::F5, Square::G5, Square::H5,
35    Square::A6, Square::B6, Square::C6, Square::D6, Square::E6, Square::F6, Square::G6, Square::H6,
36    Square::A7, Square::B7, Square::C7, Square::D7, Square::E7, Square::F7, Square::G7, Square::H7,
37    Square::A8, Square::B8, Square::C8, Square::D8, Square::E8, Square::F8, Square::G8, Square::H8,
38];
39
40impl Square {
41    /// Create a new [`Square`], from an index.
42    ///
43    /// # Panics
44    ///
45    /// Panic if the index is not in the range 0..=63.
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// use chess::Square;
51    ///
52    /// assert_eq!(Square::new(0), Square::A1);
53    /// assert_eq!(Square::new(63), Square::H8);
54    /// ```
55    #[inline]
56    pub fn new(index: usize) -> Self {
57        ALL_SQUARES[index]
58    }
59
60    /// Convert this [`Square`] into a [`usize`].
61    #[inline]
62    pub fn to_index(&self) -> usize {
63        *self as usize
64    }
65
66    /// Make a square from [`File`] and [`Rank`].
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use chess::{File, Rank, Square};
72    ///
73    /// // Make the A1 square
74    /// let square = Square::make_square(File::A, Rank::First);
75    /// ```
76    #[inline]
77    pub fn make_square(file: File, rank: Rank) -> Square {
78        Square::new(file.to_index() + rank.to_index() * BOARD_SIZE.0 as usize)
79    }
80
81    /// Transform a screen coordinate into a [`Square`].
82    ///
83    /// > **Reciprocal**: see [`Square::to_screen`].
84    ///
85    /// The result depend of:
86    /// - [`BOARD_SIZE`]
87    /// - [`BOARD_CELL_PX_SIZE`]
88    #[inline]
89    pub fn from_screen(x: f32, y: f32) -> Square {
90        // Transpose to grid space
91        let x = x / BOARD_CELL_PX_SIZE.0;
92        let y = y / BOARD_CELL_PX_SIZE.1;
93
94        // transpose to Square (return the y-axis)
95        let y = BOARD_SIZE.1 - y as i16 - 1;
96        Square::make_square(File::new(x as usize), Rank::new(y as usize))
97    }
98
99    /// Transform a [`Square`] into a screen coordinate.
100    ///
101    /// > **Reciprocal**: see [`Square::from_screen`].
102    ///
103    /// The result depend of:
104    /// - [`BOARD_SIZE`]
105    /// - [`BOARD_CELL_PX_SIZE`]
106    #[inline]
107    pub fn to_screen(&self) -> (f32, f32) {
108        // transpose to grid space (return the y-axis)
109        let x = self.file().to_index() as f32;
110        let y = (BOARD_SIZE.1 as usize - self.rank().to_index() - 1) as f32;
111
112        // Transpose to screen space
113        let x = x * BOARD_CELL_PX_SIZE.0;
114        let y = y * BOARD_CELL_PX_SIZE.1;
115        (x, y)
116    }
117
118    /// Return the [`File`] of this square.
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// use chess::{File, Rank, Square};
124    ///
125    /// let square = Square::make_square(File::D, Rank::Seventh);
126    ///
127    /// assert_eq!(square.file(), File::D);
128    /// ```
129    #[inline]
130    pub fn file(&self) -> File {
131        File::new(self.to_index() % NUM_FILES)
132    }
133
134    /// Return the "relative" [`File`] of this square according the side.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use chess::{Color, File, Square};
140    ///
141    /// assert_eq!(Square::A1.file_for(Color::White), File::A);
142    /// assert_eq!(Square::A1.file_for(Color::Black), File::H);
143    /// ```
144    #[inline]
145    pub fn file_for(&self, color: Color) -> File {
146        let file = self.file();
147        match color {
148            Color::White => file,
149            Color::Black => File::new(NUM_FILES - file.to_index() - 1),
150        }
151    }
152
153    /// Return the [`Rank`] of this square.
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use chess::{File, Rank, Square};
159    ///
160    /// let square = Square::make_square(File::D, Rank::Seventh);
161    ///
162    /// assert_eq!(square.rank(), Rank::Seventh);
163    /// ```
164    #[inline]
165    pub fn rank(&self) -> Rank {
166        Rank::new(self.to_index() / NUM_RANKS)
167    }
168
169    /// Return the "relative" [`Rank`] of this square according the side.
170    /// (i.e. return ranks for black)
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use chess::{Color, Rank, Square};
176    ///
177    /// assert_eq!(Square::E1.rank_for(Color::White), Rank::First);
178    /// assert_eq!(Square::E8.rank_for(Color::White), Rank::Eighth);
179    /// assert_eq!(Square::E2.rank_for(Color::Black), Rank::Seventh);
180    /// assert_eq!(Square::E7.rank_for(Color::Black), Rank::Second);
181    /// ```
182    #[inline]
183    pub fn rank_for(&self, color: Color) -> Rank {
184        let rank = self.rank();
185        match color {
186            Color::White => rank,
187            Color::Black => Rank::new(NUM_RANKS - rank.to_index() - 1),
188        }
189    }
190
191    /// Go one [`Rank`] up.
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// use chess::Square;
197    ///
198    /// assert_eq!(Square::B2.up(), Square::B3);
199    /// ```
200    #[inline]
201    pub fn up(&self) -> Self {
202        Square::make_square(self.file(), self.rank().up())
203    }
204
205    /// Go *n* [`Rank`] up.
206    ///
207    /// # Examples
208    ///
209    /// ```
210    /// use chess::Square;
211    ///
212    /// assert_eq!(Square::B2.n_up(3), Square::B5);
213    /// ```
214    #[inline]
215    pub fn n_up(&self, n: usize) -> Self {
216        let mut square = *self;
217        for _ in 0..n {
218            square = square.up();
219        }
220        square
221    }
222
223    /// Go one [`Rank`] forward according to the side.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use chess::{Color, Square};
229    ///
230    /// assert_eq!(Square::B2.forward(Color::White), Square::B3);
231    /// assert_eq!(Square::B2.forward(Color::Black), Square::B1);
232    /// ```
233    #[inline]
234    pub fn forward(&self, color: Color) -> Self {
235        match color {
236            Color::White => Square::make_square(self.file(), self.rank().up()),
237            Color::Black => Square::make_square(self.file(), self.rank().down()),
238        }
239    }
240
241    /// Go *n* [`Rank`] forward according to the side.
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// use chess::{Color, Square};
247    ///
248    /// assert_eq!(Square::B2.n_forward(Color::White, 2), Square::B4);
249    /// assert_eq!(Square::B8.n_forward(Color::Black, 5), Square::B3);
250    /// ```
251    #[inline]
252    pub fn n_forward(&self, color: Color, n: usize) -> Self {
253        let mut square = *self;
254        for _ in 0..n {
255            square = square.forward(color);
256        }
257        square
258    }
259
260    /// Go one [`Rank`] down.
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// use chess::Square;
266    ///
267    /// assert_eq!(Square::B2.down(), Square::B1);
268    /// ```
269    #[inline]
270    pub fn down(&self) -> Self {
271        Square::make_square(self.file(), self.rank().down())
272    }
273
274    /// Go *n* [`Rank`] down.
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use chess::{Color, Square};
280    ///
281    /// assert_eq!(Square::B4.n_down(2), Square::B2);
282    /// ```
283    #[inline]
284    pub fn n_down(&self, n: usize) -> Self {
285        let mut square = *self;
286        for _ in 0..n {
287            square = square.down();
288        }
289        square
290    }
291
292    /// Go one [`Rank`] backward according to the side.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// use chess::{Color, Square};
298    ///
299    /// assert_eq!(Square::B2.backward(Color::White), Square::B1);
300    /// assert_eq!(Square::B2.backward(Color::Black), Square::B3);
301    /// ```
302    #[inline]
303    pub fn backward(&self, color: Color) -> Self {
304        match color {
305            Color::White => Square::make_square(self.file(), self.rank().down()),
306            Color::Black => Square::make_square(self.file(), self.rank().up()),
307        }
308    }
309
310    /// Go *n* [`Rank`] backward according to the side.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use chess::{Color, Square};
316    ///
317    /// assert_eq!(Square::B4.n_backward(Color::White, 2), Square::B2);
318    /// assert_eq!(Square::B3.n_backward(Color::Black, 5), Square::B8);
319    /// ```
320    #[inline]
321    pub fn n_backward(&self, color: Color, n: usize) -> Self {
322        let mut square = *self;
323        for _ in 0..n {
324            square = square.backward(color);
325        }
326        square
327    }
328
329    /// Go one [`File`] to the right.
330    ///
331    /// # Examples
332    ///
333    /// ```
334    /// use chess::Square;
335    ///
336    /// assert_eq!(Square::B2.right(), Square::C2);
337    /// ```
338    #[inline]
339    pub fn right(&self) -> Self {
340        Square::make_square(self.file().right(), self.rank())
341    }
342
343    /// Go *n* [`File`] to the right.
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// use chess::{Color, Square};
349    ///
350    /// assert_eq!(Square::A4.n_right(3), Square::D4);
351    /// ```
352    #[inline]
353    pub fn n_right(&self, n: usize) -> Self {
354        let mut square = *self;
355        for _ in 0..n {
356            square = square.right();
357        }
358        square
359    }
360
361    /// Go one [`File`] right according to the side.
362    ///
363    /// # Examples
364    ///
365    /// ```
366    /// use chess::{Color, Square};
367    ///
368    /// assert_eq!(Square::B2.right_for(Color::White), Square::C2);
369    /// assert_eq!(Square::B2.right_for(Color::Black), Square::A2);
370    /// ```
371    #[inline]
372    pub fn right_for(&self, color: Color) -> Self {
373        match color {
374            Color::White => Square::make_square(self.file().right(), self.rank()),
375            Color::Black => Square::make_square(self.file().left(), self.rank()),
376        }
377    }
378
379    /// Go *n* [`File`] to the right according to the side.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// use chess::{Color, Square};
385    ///
386    /// assert_eq!(Square::A4.n_right_for(Color::White, 3), Square::D4);
387    /// assert_eq!(Square::D4.n_right_for(Color::Black, 3), Square::A4);
388    /// ```
389    #[inline]
390    pub fn n_right_for(&self, color: Color, n: usize) -> Self {
391        let mut square = *self;
392        for _ in 0..n {
393            square = square.right_for(color);
394        }
395        square
396    }
397
398    /// Go one [`File`] to the left.
399    ///
400    /// # Examples
401    ///
402    /// ```
403    /// use chess::Square;
404    ///
405    /// assert_eq!(Square::B2.left(), Square::A2);
406    /// ```
407    #[inline]
408    pub fn left(&self) -> Self {
409        Square::make_square(self.file().left(), self.rank())
410    }
411
412    /// Go *n* [`File`] to the left.
413    ///
414    /// # Examples
415    ///
416    /// ```
417    /// use chess::{Color, Square};
418    ///
419    /// assert_eq!(Square::D4.n_left(3), Square::A4);
420    /// ```
421    #[inline]
422    pub fn n_left(&self, n: usize) -> Self {
423        let mut square = *self;
424        for _ in 0..n {
425            square = square.left();
426        }
427        square
428    }
429
430    /// Go one [`File`] left according to the side.
431    ///
432    /// # Examples
433    ///
434    /// ```
435    /// use chess::{Color, Square};
436    ///
437    /// assert_eq!(Square::B2.left_for(Color::White), Square::A2);
438    /// assert_eq!(Square::B2.left_for(Color::Black), Square::C2);
439    /// ```
440    #[inline]
441    pub fn left_for(&self, color: Color) -> Self {
442        match color {
443            Color::White => Square::make_square(self.file().left(), self.rank()),
444            Color::Black => Square::make_square(self.file().right(), self.rank()),
445        }
446    }
447
448    /// Go *n* [`File`] to the left according to the side.
449    ///
450    /// # Examples
451    ///
452    /// ```
453    /// use chess::{Color, Square};
454    ///
455    /// assert_eq!(Square::D4.n_left_for(Color::White, 3), Square::A4);
456    /// assert_eq!(Square::A4.n_left_for(Color::Black, 3), Square::D4);
457    /// ```
458    #[inline]
459    pub fn n_left_for(&self, color: Color, n: usize) -> Self {
460        let mut square = *self;
461        for _ in 0..n {
462            square = square.left_for(color);
463        }
464        square
465    }
466
467    /// Go one [`Square`] in the given direction.
468    ///
469    /// # Examples
470    ///
471    /// ```
472    /// use chess::{Direction, Square};
473    ///
474    /// assert_eq!(Square::B2.follow_direction(Direction::Up), Square::B3);
475    /// assert_eq!(Square::B2.follow_direction(Direction::DownRight), Square::C1);
476    /// ```
477    #[inline]
478    pub fn follow_direction(&self, direction: Direction) -> Self {
479        match direction {
480            Direction::Up => self.up(),
481            Direction::UpRight => self.up().right(),
482            Direction::Right => self.right(),
483            Direction::DownRight => self.down().right(),
484            Direction::Down => self.down(),
485            Direction::DownLeft => self.down().left(),
486            Direction::Left => self.left(),
487            Direction::UpLeft => self.up().left(),
488        }
489    }
490
491    /// The distance between the two squares, i.e. the number of king steps
492    /// to get from one square to the other.
493    ///
494    /// # Examples
495    ///
496    /// ```
497    /// use chess::Square;
498    ///
499    /// assert_eq!(Square::A2.distance(Square::B5), 3);
500    /// ```
501    pub fn distance(&self, other: Square) -> u32 {
502        max(
503            self.file().distance(other.file()),
504            self.rank().distance(other.rank()),
505        )
506    }
507}
508
509impl fmt::Display for Square {
510    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
511        write!(
512            f,
513            "{}{}",
514            (b'a' + (self.file() as u8)) as char,
515            (b'1' + (self.rank() as u8)) as char
516        )
517    }
518}
519
520impl FromStr for Square {
521    type Err = Error;
522
523    fn from_str(s: &str) -> Result<Self, Self::Err> {
524        if s.len() < 2 {
525            return Err(Error::InvalidSquare);
526        }
527        let ch: Vec<char> = s.chars().collect();
528        match ch[0] {
529            'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {}
530            _ => return Err(Error::InvalidSquare),
531        }
532        match ch[1] {
533            '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {}
534            _ => return Err(Error::InvalidSquare),
535        }
536        Ok(Square::make_square(
537            File::new((ch[0] as usize) - ('a' as usize)),
538            Rank::new((ch[1] as usize) - ('1' as usize)),
539        ))
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use super::*;
546
547    #[test]
548    fn test_square() {
549        for file in (0..8).map(File::new) {
550            for rank in (0..8).map(Rank::new) {
551                let square = Square::make_square(file, rank);
552                assert_eq!(square.file(), file);
553                assert_eq!(square.rank(), rank);
554            }
555        }
556    }
557}