Skip to main content

chess/square/
plays.rs

1use crate::{Piece, Player, Role, board::Bitboard};
2
3use super::{Direction, Square};
4
5use Player::*;
6use Role::*;
7
8/// Square Play API.
9impl Square {
10    pub const fn attacks(self, piece: Piece, occupied: Bitboard) -> Bitboard {
11        match piece.role {
12            Pawn => self.pawn_attack_plays(piece.player),
13            Knight => self.knight_plays(),
14            Bishop => self.bishop_sight(occupied),
15            Rook => self.rook_sight(occupied),
16            Queen => self.queen_sight(occupied),
17            King => self.king_plays(),
18        }
19    }
20
21    pub const fn king_plays(self) -> Bitboard {
22        const KING_ATTACKS: [Direction; 8] = {
23            use Direction::*;
24            [North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest]
25        };
26
27        self.checked_add_vector_const(&KING_ATTACKS)
28    }
29
30    pub const fn knight_plays(self) -> Bitboard {
31        const KNIGHT_MOVES: [Direction; 8] = {
32            use Direction::*;
33            [
34                KnightNorthEast,
35                KnightEastNorth,
36                KnightEastSouth,
37                KnightSouthEast,
38                KnightSouthWest,
39                KnightWestSouth,
40                KnightWestNorth,
41                KnightNorthWest,
42            ]
43        };
44
45        self.checked_add_vector_const(&KNIGHT_MOVES)
46    }
47
48    pub const fn pawn_attack_plays(self, player: Player) -> Bitboard {
49        const WHITE_PAWN_ATTACKS: [Direction; 2] = {
50            use Direction::*;
51            [NorthWest, NorthEast]
52        };
53        const BLACK_PAWN_ATTACKS: [Direction; 2] = {
54            use Direction::*;
55            [SouthWest, SouthEast]
56        };
57
58        match player {
59            White => self.checked_add_vector_const(&WHITE_PAWN_ATTACKS),
60            Black => self.checked_add_vector_const(&BLACK_PAWN_ATTACKS),
61        }
62    }
63
64    pub const fn bishop_sight(self, occupied: Bitboard) -> Bitboard {
65        SliderSights::get().bishop_sight(self, occupied)
66    }
67
68    pub const fn rook_sight(self, occupied: Bitboard) -> Bitboard {
69        SliderSights::get().rook_sight(self, occupied)
70    }
71
72    pub const fn queen_sight(self, occupied: Bitboard) -> Bitboard {
73        self.bishop_sight(occupied).union(self.rook_sight(occupied))
74    }
75}
76
77mod bishop {
78    use super::*;
79
80    pub const DIRECTIONS: [Direction; 4] = {
81        use Direction::*;
82        [NorthEast, SouthEast, SouthWest, NorthWest]
83    };
84
85    pub const fn projector(square: Square) -> &'static Projector<9> {
86        &BISHOP_PROJECTOR[square as usize]
87    }
88
89    pub const fn blockers(square: Square) -> Bitboard {
90        const BLOCKERS: SliderBlockers = SliderBlockers::new(&DIRECTIONS);
91
92        BLOCKERS.get(square)
93    }
94}
95
96mod rook {
97    use super::*;
98
99    pub const DIRECTIONS: [Direction; 4] = {
100        use Direction::*;
101        [North, East, South, West]
102    };
103
104    pub const fn projector(square: Square) -> &'static Projector<12> {
105        &ROOK_PROJECTOR[square as usize]
106    }
107
108    pub const fn blockers(square: Square) -> Bitboard {
109        const BLOCKERS: SliderBlockers = SliderBlockers::new(&DIRECTIONS);
110
111        BLOCKERS.get(square)
112    }
113}
114
115struct Projector<const B: u32> {
116    pub factor: u64,
117    pub offset: usize,
118}
119
120impl<const B: u32> Projector<B> {
121    const fn new(factor: u64, offset: usize) -> Self {
122        Self { factor, offset }
123    }
124
125    // Compress the "occupied squares" bitboard down to 88772 entries
126    const fn index(&self, bitboard: Bitboard) -> usize {
127        (self.factor.wrapping_mul(bitboard.0) >> (64 - B)) as usize + self.offset
128    }
129}
130
131pub struct SliderSights([Bitboard; 88772]);
132
133impl SliderSights {
134    pub const fn get() -> &'static Self {
135        #[cfg(rust_chess_compile_time_slider_sights)]
136        #[allow(long_running_const_eval)]
137        static SIGHTS: SliderSights = SliderSights::compute();
138        #[cfg(not(rust_chess_compile_time_slider_sights))]
139        static SIGHTS: SliderSights = include!("SLIDER-SIGHTS.rs");
140
141        &SIGHTS
142    }
143
144    pub const fn as_array(&self) -> &[Bitboard; 88772] {
145        &self.0
146    }
147
148    // All the squares a bishop in square "sees", assuming the given occupied squares.
149    // Includes the first occupied squarie blocking further sight
150    pub const fn bishop_sight(&self, square: Square, occupied: Bitboard) -> Bitboard {
151        let blockers = bishop::blockers(square);
152        let index = bishop::projector(square).index(occupied.intersection(blockers));
153        self.0[index]
154    }
155
156    pub const fn rook_sight(&self, square: Square, occupied: Bitboard) -> Bitboard {
157        let blockers = rook::blockers(square);
158        let index = rook::projector(square).index(occupied.intersection(blockers));
159        self.0[index]
160    }
161}
162
163#[cfg(any(rust_chess_compile_time_slider_sights, test))]
164impl SliderSights {
165    pub const fn compute() -> Self {
166        let mut this = Self([Bitboard::EMPTY; 88772]);
167        finite_for!(square in Square {
168            this.project_bishop_sights(square);
169            this.project_rook_sights(square);
170        });
171        this
172    }
173
174    const fn project_bishop_sights(&mut self, square: Square) {
175        self.project_slider_sights(
176            square,
177            &bishop::DIRECTIONS,
178            bishop::blockers(square),
179            bishop::projector(square),
180        );
181    }
182
183    const fn project_rook_sights(&mut self, square: Square) {
184        self.project_slider_sights(
185            square,
186            &rook::DIRECTIONS,
187            rook::blockers(square),
188            rook::projector(square),
189        );
190    }
191
192    const fn at(&self, index: usize) -> Bitboard {
193        self.0[index]
194    }
195
196    const fn set(&mut self, index: usize, attack: Bitboard) {
197        self.0[index] = attack;
198    }
199
200    const fn project_slider_sights<const B: u32>(
201        &mut self,
202        square: Square,
203        directions: &[Direction],
204        blockers: Bitboard,
205        projector: &Projector<B>,
206    ) {
207        let mut occupied = Bitboard::EMPTY;
208        loop {
209            let index = projector.index(occupied);
210            let sight = Self::sight(square, occupied, directions);
211            // sanity check: we are not overwriting an existing attack
212            // due to hash / magic index failing by clashing.
213            assert!(self.at(index).is_empty() || self.at(index).eq(sight));
214            self.set(index, sight);
215            occupied = blockers.next_subset(occupied);
216            if occupied.is_empty() {
217                break;
218            }
219        }
220    }
221
222    // This is NOT computed directly for every "slider" attack from a given square.
223    // Instead, it's used to precompute the slider attack table.
224    const fn sight(square: Square, occupied: Bitboard, directions: &[Direction]) -> Bitboard {
225        let mut sight = Bitboard::EMPTY;
226        let mut i = 0;
227        while i < directions.len() {
228            let direction = directions[i];
229            let mut square = square;
230            while let Some(target) = square.checked_add(direction) {
231                sight.append(Bitboard::from_square(target));
232                // hit an occupied square
233                if occupied.contains(target) {
234                    break;
235                }
236                square = target;
237            }
238            i += 1;
239        }
240        sight
241    }
242}
243
244#[allow(dead_code)]
245struct SliderBlockers([Bitboard; 64]);
246
247#[allow(dead_code)]
248impl SliderBlockers {
249    const fn new(directions: &[Direction]) -> Self {
250        let mut blockers = [Bitboard::EMPTY; 64];
251        finite_for!(square in Square {
252            blockers[square as usize] = Self::ray_blockers(square, directions);
253        });
254        Self(blockers)
255    }
256
257    const fn get(&self, square: Square) -> Bitboard {
258        self.0[square as usize]
259    }
260
261    const fn ray_blockers(square: Square, directions: &[Direction]) -> Bitboard {
262        let mut blockers = Bitboard::EMPTY;
263        let mut i = 0;
264        while i < directions.len() {
265            let direction = directions[i];
266            let mut target = square.checked_add(direction);
267            while let Some(square) = target {
268                target = square.checked_add(direction);
269                if target.is_some() {
270                    blockers.append(Bitboard::from_square(square));
271                }
272            }
273            i += 1;
274        }
275        blockers
276    }
277}
278
279// Fixed shift white magics found by Volker Annuss.
280// From: http://www.talkchess.com/forum/viewtopic.php?p=727500&t=64790
281
282const BISHOP_PROJECTOR: [Projector<9>; 64] = [
283    Projector::new(0x007f_bfbf_bfbf_bfff, 5378),
284    Projector::new(0x0000_a060_4010_07fc, 4093),
285    Projector::new(0x0001_0040_0802_0000, 4314),
286    Projector::new(0x0000_8060_0400_0000, 6587),
287    Projector::new(0x0000_1004_0000_0000, 6491),
288    Projector::new(0x0000_21c1_00b2_0000, 6330),
289    Projector::new(0x0000_0400_4100_8000, 5609),
290    Projector::new(0x0000_0fb0_203f_ff80, 22236),
291    Projector::new(0x0000_0401_0040_1004, 6106),
292    Projector::new(0x0000_0200_8020_0802, 5625),
293    Projector::new(0x0000_0040_1020_2000, 16785),
294    Projector::new(0x0000_0080_6004_0000, 16817),
295    Projector::new(0x0000_0044_0200_0000, 6842),
296    Projector::new(0x0000_0008_0100_8000, 7003),
297    Projector::new(0x0000_07ef_e0bf_ff80, 4197),
298    Projector::new(0x0000_0008_2082_0020, 7356),
299    Projector::new(0x0000_4000_8080_8080, 4602),
300    Projector::new(0x0002_1f01_0040_0808, 4538),
301    Projector::new(0x0001_8000_c06f_3fff, 29531),
302    Projector::new(0x0000_2582_0080_1000, 45393),
303    Projector::new(0x0000_2400_8084_0000, 12420),
304    Projector::new(0x0000_1800_0c03_fff8, 15763),
305    Projector::new(0x0000_0a58_4020_8020, 5050),
306    Projector::new(0x0000_0200_0820_8020, 4346),
307    Projector::new(0x0000_8040_0081_0100, 6074),
308    Projector::new(0x0001_0119_0080_2008, 7866),
309    Projector::new(0x0000_8040_0081_0100, 32139),
310    Projector::new(0x0001_0040_3c04_03ff, 57673),
311    Projector::new(0x0007_8402_a880_2000, 55365),
312    Projector::new(0x0000_1010_0080_4400, 15818),
313    Projector::new(0x0000_0808_0010_4100, 5562),
314    Projector::new(0x0000_4004_c008_2008, 6390),
315    Projector::new(0x0001_0101_2000_8020, 7930),
316    Projector::new(0x0000_8080_9a00_4010, 13329),
317    Projector::new(0x0007_fefe_0881_0010, 7170),
318    Projector::new(0x0003_ff0f_833f_c080, 27267),
319    Projector::new(0x007f_e080_1900_3042, 53787),
320    Projector::new(0x003f_ffef_ea00_3000, 5097),
321    Projector::new(0x0000_1010_1000_2080, 6643),
322    Projector::new(0x0000_8020_0508_0804, 6138),
323    Projector::new(0x0000_8080_80a8_0040, 7418),
324    Projector::new(0x0000_1041_0020_0040, 7898),
325    Projector::new(0x0003_ffdf_7f83_3fc0, 42012),
326    Projector::new(0x0000_0088_4045_0020, 57350),
327    Projector::new(0x0000_7ffc_8018_0030, 22813),
328    Projector::new(0x007f_ffdd_8014_0028, 56693),
329    Projector::new(0x0002_0080_200a_0004, 5818),
330    Projector::new(0x0000_1010_1010_0020, 7098),
331    Projector::new(0x0007_ffdf_c180_5000, 4451),
332    Projector::new(0x0003_ffef_e0c0_2200, 4709),
333    Projector::new(0x0000_0008_2080_6000, 4794),
334    Projector::new(0x0000_0000_0840_3000, 13364),
335    Projector::new(0x0000_0001_0020_2000, 4570),
336    Projector::new(0x0000_0040_4080_2000, 4282),
337    Projector::new(0x0004_0100_4010_0400, 14964),
338    Projector::new(0x0000_6020_6018_03f4, 4026),
339    Projector::new(0x0003_ffdf_dfc2_8048, 4826),
340    Projector::new(0x0000_0008_2082_0020, 7354),
341    Projector::new(0x0000_0000_0820_8060, 4848),
342    Projector::new(0x0000_0000_0080_8020, 15946),
343    Projector::new(0x0000_0000_0100_2020, 14932),
344    Projector::new(0x0000_0004_0100_2008, 16588),
345    Projector::new(0x0000_0040_4040_4040, 6905),
346    Projector::new(0x007f_ff9f_df7f_f813, 16076),
347];
348
349const ROOK_PROJECTOR: [Projector<12>; 64] = [
350    Projector::new(0x0028_0077_ffeb_fffe, 26304),
351    Projector::new(0x2004_0102_0109_7fff, 35520),
352    Projector::new(0x0010_0200_1005_3fff, 38592),
353    Projector::new(0x0040_0400_0800_4002, 8026),
354    Projector::new(0x7fd0_0441_ffff_d003, 22196),
355    Projector::new(0x4020_0088_87df_fffe, 80870),
356    Projector::new(0x0040_0088_8847_ffff, 76747),
357    Projector::new(0x0068_00fb_ff75_fffd, 30400),
358    Projector::new(0x0000_2801_0113_ffff, 11115),
359    Projector::new(0x0020_0402_01fc_ffff, 18205),
360    Projector::new(0x007f_e800_42ff_ffe8, 53577),
361    Projector::new(0x0000_1800_217f_ffe8, 62724),
362    Projector::new(0x0000_1800_073f_ffe8, 34282),
363    Projector::new(0x0000_1800_e05f_ffe8, 29196),
364    Projector::new(0x0000_1800_602f_ffe8, 23806),
365    Projector::new(0x0000_3000_2fff_ffa0, 49481),
366    Projector::new(0x0030_0018_010b_ffff, 2410),
367    Projector::new(0x0003_000c_0085_fffb, 36498),
368    Projector::new(0x0004_0008_0201_0008, 24478),
369    Projector::new(0x0004_0020_2002_0004, 10074),
370    Projector::new(0x0001_0020_0200_2001, 79315),
371    Projector::new(0x0001_0010_0080_1040, 51779),
372    Projector::new(0x0000_0040_4000_8001, 13586),
373    Projector::new(0x0000_0068_00cd_fff4, 19323),
374    Projector::new(0x0040_2000_1008_0010, 70612),
375    Projector::new(0x0000_0800_1004_0010, 83652),
376    Projector::new(0x0004_0100_0802_0008, 63110),
377    Projector::new(0x0000_0400_2020_0200, 34496),
378    Projector::new(0x0002_0080_1010_0100, 84966),
379    Projector::new(0x0000_0080_2001_0020, 54341),
380    Projector::new(0x0000_0080_2020_0040, 60421),
381    Projector::new(0x0000_8200_2000_4020, 86402),
382    Projector::new(0x00ff_fd18_0030_0030, 50245),
383    Projector::new(0x007f_ff7f_bfd4_0020, 76622),
384    Projector::new(0x003f_ffbd_0018_0018, 84676),
385    Projector::new(0x001f_ffde_8018_0018, 78757),
386    Projector::new(0x000f_ffe0_bfe8_0018, 37346),
387    Projector::new(0x0001_0000_8020_2001, 370),
388    Projector::new(0x0003_fffb_ff98_0180, 42182),
389    Projector::new(0x0001_fffd_ff90_00e0, 45385),
390    Projector::new(0x00ff_fefe_ebff_d800, 61659),
391    Projector::new(0x007f_fff7_ffc0_1400, 12790),
392    Projector::new(0x003f_ffbf_e4ff_e800, 16762),
393    Projector::new(0x001f_fff0_1fc0_3000, 0),
394    Projector::new(0x000f_ffe7_f8bf_e800, 38380),
395    Projector::new(0x0007_ffdf_df3f_f808, 11098),
396    Projector::new(0x0003_fff8_5fff_a804, 21803),
397    Projector::new(0x0001_fffd_75ff_a802, 39189),
398    Projector::new(0x00ff_ffd7_ffeb_ffd8, 58628),
399    Projector::new(0x007f_ff75_ff7f_bfd8, 44116),
400    Projector::new(0x003f_ff86_3fbf_7fd8, 78357),
401    Projector::new(0x001f_ffbf_dfd7_ffd8, 44481),
402    Projector::new(0x000f_fff8_1028_0028, 64134),
403    Projector::new(0x0007_ffd7_f7fe_ffd8, 41759),
404    Projector::new(0x0003_fffc_0c48_0048, 1394),
405    Projector::new(0x0001_ffff_afd7_ffd8, 40910),
406    Projector::new(0x00ff_ffe4_ffdf_a3ba, 66516),
407    Projector::new(0x007f_ffef_7ff3_d3da, 3897),
408    Projector::new(0x003f_ffbf_dfef_f7fa, 3930),
409    Projector::new(0x001f_ffef_f7fb_fc22, 72934),
410    Projector::new(0x0000_0204_0800_1001, 72662),
411    Projector::new(0x0007_fffe_ffff_77fd, 56325),
412    Projector::new(0x0003_ffff_bf7d_feec, 66501),
413    Projector::new(0x0001_ffff_9dff_a333, 14826),
414];