Skip to main content

chess/square/
geometry.rs

1use core::ops;
2
3use crate::board::Bitboard;
4
5use super::Square;
6
7#[derive(Clone, Copy)]
8#[repr(i8)]
9// This is an enum and not e.g. Direction(i8) so that it is a closed set and
10// matches can be exhaustive.
11pub enum Direction {
12    North = direction(0, 1),
13    East = direction(1, 0),
14    South = direction(0, -1),
15    West = direction(-1, 0),
16    NorthNorth = direction(0, 2),
17    SouthSouth = direction(0, -2),
18
19    NorthEast = direction(1, 1),
20    NorthWest = direction(-1, 1),
21    SouthEast = direction(1, -1),
22    SouthWest = direction(-1, -1),
23
24    KnightNorthEast = direction(1, 2),
25    KnightNorthWest = direction(-1, 2),
26    KnightEastNorth = direction(2, 1),
27    KnightEastSouth = direction(2, -1),
28    KnightSouthEast = direction(1, -2),
29    KnightSouthWest = direction(-1, -2),
30    KnightWestNorth = direction(-2, 1),
31    KnightWestSouth = direction(-2, -1),
32}
33
34const fn direction(file: i8, rank: i8) -> i8 {
35    rank * 8 + file
36}
37
38impl Direction {
39    #[inline]
40    pub const fn reverse(self) -> Direction {
41        use Direction::*;
42
43        match self {
44            North => South,
45            East => West,
46            South => North,
47            West => East,
48            NorthNorth => SouthSouth,
49            SouthSouth => NorthNorth,
50            NorthEast => SouthWest,
51            NorthWest => SouthEast,
52            SouthEast => NorthWest,
53            SouthWest => NorthEast,
54            KnightNorthEast => KnightSouthWest,
55            KnightNorthWest => KnightSouthEast,
56            KnightEastNorth => KnightWestSouth,
57            KnightEastSouth => KnightWestNorth,
58            KnightSouthEast => KnightNorthWest,
59            KnightSouthWest => KnightNorthEast,
60            KnightWestNorth => KnightEastSouth,
61            KnightWestSouth => KnightEastNorth,
62        }
63    }
64}
65
66/// Square Geometry API.
67impl Square {
68    pub const fn checked_add(self, direction: Direction) -> Option<Square> {
69        let square = self as i8;
70        let target = square + direction as i8;
71        // Equivalent to splitting square + step into file + rank,
72        // adding coordinates, and then checking if file + rank are in 0..=7
73        let file_diff = (target & 0x7) - (square & 0x7);
74        if target >= 0 && target < 64 && file_diff >= -2 && file_diff <= 2 {
75            Some(Square::panicky_from_index(target as u8))
76        } else {
77            None
78        }
79    }
80
81    // the full line through the two squares
82    pub const fn full_ray(self, other: Square) -> Bitboard {
83        Bitboard::FULL_RAYS[self as usize][other as usize]
84    }
85
86    // The row-major half-open interval [min(self, other), max(self, other)).
87    //
88    // For d2, g5, after also removing the first square:
89    //
90    // 8  . . . . . . . .
91    // 7  . . . . . . . .
92    // 6  . . . . . . . .
93    // 5  x x x x x x . .
94    // 4  x x x x x x x x
95    // 3  x x x x x x x x
96    // 2  . . . . x x x x
97    // 1  . . . . . . . .
98    //
99    //    a b c d e f g h
100    const fn index_range(self, other: Square) -> Bitboard {
101        Bitboard((!0 << self as u32) ^ (!0 << other as u32))
102    }
103
104    // The row-major squares after this one, excluding self..
105    // For d2, this includes e2..h8 and excludes a1..d2.
106    //
107    // For d2:
108    //
109    // 8  x x x x x x x x
110    // 7  x x x x x x x x
111    // 6  x x x x x x x x
112    // 5  x x x x x x x x
113    // 4  x x x x x x x x
114    // 3  x x x x x x x x
115    // 2  . . . . x x x x
116    // 1  . . . . . . . .
117    //
118    //    a b c d e f g h
119    pub const fn index_after(self) -> Bitboard {
120        Bitboard(!0 << (self as u32 + 1))
121    }
122
123    // The row-major squares before this one, excluding self.
124    // For d2, this includes a1..c2 and excludes d2..h8.
125    pub const fn index_before(self) -> Bitboard {
126        Bitboard((1 << self as u32) - 1)
127    }
128
129    pub const fn east(self) -> Bitboard {
130        self.index_after().intersection(Bitboard::from_rank(self.rank()))
131    }
132
133    pub const fn west(self) -> Bitboard {
134        self.index_before().intersection(Bitboard::from_rank(self.rank()))
135    }
136
137    pub const fn between(self, other: Square) -> Bitboard {
138        // Intersecting the index range with the geometric ray leaves only the
139        // ray segment between the endpoints.
140        self.full_ray(other).intersection(self.index_range(other)).without_first()
141    }
142
143    pub const fn aligned(self, b: Square, c: Square) -> bool {
144        self.full_ray(b).contains(c)
145    }
146}
147
148impl ops::Add<Direction> for Square {
149    type Output = Option<Square>;
150
151    fn add(self, direction: Direction) -> Option<Square> {
152        self.checked_add(direction)
153    }
154}
155
156impl ops::Add<&[Direction]> for Square {
157    type Output = Bitboard;
158
159    fn add(self, directions: &[Direction]) -> Bitboard {
160        self.checked_add_vector_const(directions)
161    }
162}