chess/
position.rs

1use std::ops::Deref;
2use std::ops::Index;
3use std::ops::IndexMut;
4
5use crate::fen::FEN;
6use crate::mailbox;
7use crate::movegen::*;
8use crate::zobrist;
9use crate::zobrist::PositionHash;
10
11pub(crate) const ABOVE_BELOW: usize = 8; // 8 indexes from i is the square directly above/below in the pos64 array
12
13#[derive(Debug, PartialEq, Clone, Copy)]
14pub struct Pos64([Square; 64]);
15impl Index<usize> for Pos64 {
16    type Output = Square;
17
18    fn index(&self, index: usize) -> &Self::Output {
19        &self.0[index]
20    }
21}
22impl IndexMut<usize> for Pos64 {
23    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
24        &mut self.0[index]
25    }
26}
27impl Deref for Pos64 {
28    type Target = [Square; 64];
29
30    fn deref(&self) -> &Self::Target {
31        &self.0
32    }
33}
34impl Default for Pos64 {
35    fn default() -> Self {
36        Self([Square::Empty; 64])
37    }
38}
39impl Pos64 {
40    // is a pawn of colour 'pawn_colour' is either side of square at index i, used for setting polyglot en passant flag
41    #[inline(always)]
42    pub fn polyglot_is_pawn_beside(&self, i: usize, pawn_colour: PieceColour) -> bool {
43        let piece = Piece {
44            pcolour: pawn_colour,
45            ptype: PieceType::Pawn,
46        };
47        let left = mailbox::next_mailbox_number(i, -1);
48        // valid mailbox index
49        if left >= 0 {
50            if let Square::Piece(p) = &self[left as usize] {
51                if p == &piece {
52                    return true;
53                }
54            }
55        }
56        let right = mailbox::next_mailbox_number(i, 1);
57        // valid mailbox index
58        if right >= 0 {
59            if let Square::Piece(p) = &self[right as usize] {
60                if p == &piece {
61                    return true;
62                }
63            }
64        }
65        false
66    }
67}
68
69#[derive(Debug, PartialEq, Clone)]
70pub struct AttackMap(Vec<Move>);
71impl AttackMap {
72    const fn new() -> Self {
73        Self(Vec::new())
74    }
75
76    fn new_no_alloc() -> Self {
77        Self(Vec::with_capacity(0))
78    }
79
80    fn clear(&mut self) {
81        self.0.clear();
82    }
83}
84
85#[derive(Debug, PartialEq, Eq, Clone, Copy)]
86pub struct DefendMap([bool; 64]);
87impl DefendMap {
88    const fn new() -> Self {
89        Self([false; 64])
90    }
91
92    fn clear(&mut self) {
93        self.0 = [false; 64];
94    }
95}
96
97impl MoveMap for DefendMap {
98    fn add_move(&mut self, mv: &Move) {
99        self.0[mv.to] = true;
100    }
101}
102
103impl MoveMap for AttackMap {
104    fn add_move(&mut self, mv: &Move) {
105        self.0.push(*mv);
106    }
107}
108
109#[derive(Debug, Clone)]
110pub struct Position {
111    pub pos64: Pos64,
112    pub side: PieceColour,
113    pub movegen_flags: MovegenFlags,
114    defend_map: DefendMap, // map of squares opposite colour is defending
115    attack_map: AttackMap, // map of moves from attacking side
116    wking_idx: usize,
117    bking_idx: usize,
118}
119impl Position {
120    // new board with starting Position
121    pub fn new_starting() -> Self {
122        let mut pos: Pos64 = Pos64::default();
123
124        let movegen_flags = MovegenFlags {
125            white_castle_short: true,
126            white_castle_long: true,
127            black_castle_short: true,
128            black_castle_long: true,
129            en_passant: None,
130            polyglot_en_passant: None,
131        };
132
133        pos[0] = Square::Piece(Piece {
134            pcolour: PieceColour::Black,
135            ptype: PieceType::Rook,
136        });
137        pos[1] = Square::Piece(Piece {
138            pcolour: PieceColour::Black,
139            ptype: PieceType::Knight,
140        });
141        pos[2] = Square::Piece(Piece {
142            pcolour: PieceColour::Black,
143            ptype: PieceType::Bishop,
144        });
145        pos[3] = Square::Piece(Piece {
146            pcolour: PieceColour::Black,
147            ptype: PieceType::Queen,
148        });
149        pos[4] = Square::Piece(Piece {
150            pcolour: PieceColour::Black,
151            ptype: PieceType::King,
152        });
153        pos[5] = Square::Piece(Piece {
154            pcolour: PieceColour::Black,
155            ptype: PieceType::Bishop,
156        });
157        pos[6] = Square::Piece(Piece {
158            pcolour: PieceColour::Black,
159            ptype: PieceType::Knight,
160        });
161        pos[7] = Square::Piece(Piece {
162            pcolour: PieceColour::Black,
163            ptype: PieceType::Rook,
164        });
165        for i in 8..16 {
166            pos[i] = Square::Piece(Piece {
167                pcolour: PieceColour::Black,
168                ptype: PieceType::Pawn,
169            });
170        }
171        for i in 16..48 {
172            pos[i] = Square::Empty;
173        }
174        for i in 48..56 {
175            pos[i] = Square::Piece(Piece {
176                pcolour: PieceColour::White,
177                ptype: PieceType::Pawn,
178            });
179        }
180        pos[56] = Square::Piece(Piece {
181            pcolour: PieceColour::White,
182            ptype: PieceType::Rook,
183        });
184        pos[57] = Square::Piece(Piece {
185            pcolour: PieceColour::White,
186            ptype: PieceType::Knight,
187        });
188        pos[58] = Square::Piece(Piece {
189            pcolour: PieceColour::White,
190            ptype: PieceType::Bishop,
191        });
192        pos[59] = Square::Piece(Piece {
193            pcolour: PieceColour::White,
194            ptype: PieceType::Queen,
195        });
196        pos[60] = Square::Piece(Piece {
197            pcolour: PieceColour::White,
198            ptype: PieceType::King,
199        });
200        pos[61] = Square::Piece(Piece {
201            pcolour: PieceColour::White,
202            ptype: PieceType::Bishop,
203        });
204        pos[62] = Square::Piece(Piece {
205            pcolour: PieceColour::White,
206            ptype: PieceType::Knight,
207        });
208        pos[63] = Square::Piece(Piece {
209            pcolour: PieceColour::White,
210            ptype: PieceType::Rook,
211        });
212
213        let side = PieceColour::White;
214
215        let mut new = Self {
216            pos64: pos,
217            side,
218            movegen_flags,
219            defend_map: DefendMap::new(),
220            attack_map: AttackMap::new(),
221            wking_idx: 60,
222            bking_idx: 4,
223        };
224        new.gen_maps();
225        new
226    }
227
228    pub(crate) fn new_from_pub_parts(
229        pos64: Pos64,
230        side: PieceColour,
231        movegen_flags: MovegenFlags,
232    ) -> Self {
233        let mut new = Self {
234            pos64,
235            side,
236            movegen_flags,
237            defend_map: DefendMap::new(),
238            attack_map: AttackMap::new(),
239            wking_idx: 0,
240            bking_idx: 0,
241        };
242        new.update_king_idx();
243        new.gen_maps();
244        new
245    }
246
247    // Assumes a legal move, no legality checks are done, so no bounds checking is done here
248    pub fn new_position(&self, mv: &Move) -> Self {
249        let mut new_pos = self.clone();
250        new_pos.set_en_passant_flag(mv);
251        new_pos.set_castle_flags(mv);
252        new_pos.set_king_position(mv);
253
254        match mv.move_type {
255            MoveType::EnPassant(ep_capture) => {
256                // en passant, 'to' square is different from the captured square
257                new_pos.pos64[ep_capture] = Square::Empty;
258            }
259            MoveType::Castle(castle_mv) => {
260                new_pos.pos64[castle_mv.rook_to] = new_pos.pos64[castle_mv.rook_from];
261                new_pos.pos64[castle_mv.rook_from] = Square::Empty;
262            }
263            MoveType::Promotion(ptype, _) => match &mut new_pos.pos64[mv.from] {
264                Square::Piece(p) => {
265                    p.ptype = ptype;
266                }
267                Square::Empty => {
268                    unreachable!();
269                }
270            },
271            _ => {}
272        }
273
274        new_pos.pos64[mv.to] = new_pos.pos64[mv.from];
275        new_pos.pos64[mv.from] = Square::Empty;
276
277        new_pos.toggle_side();
278        new_pos.gen_maps();
279        new_pos
280    }
281
282    #[inline(always)]
283    pub fn pos_hash(&self) -> PositionHash {
284        zobrist::pos_hash(self)
285    }
286
287    #[inline(always)]
288    fn update_king_idx(&mut self) {
289        for (i, s) in self.pos64.iter().enumerate() {
290            if let Square::Piece(p) = s {
291                if p.ptype == PieceType::King {
292                    if p.pcolour == PieceColour::White {
293                        self.wking_idx = i;
294                    } else {
295                        self.bking_idx = i;
296                    }
297                }
298            }
299        }
300    }
301
302    // TODO maybe consolidate all movegen flag updates into one place if possible?
303    #[inline(always)]
304    fn set_king_position(&mut self, mv: &Move) {
305        if mv.piece.ptype == PieceType::King {
306            if mv.piece.pcolour == PieceColour::White {
307                self.wking_idx = mv.to;
308            } else {
309                self.bking_idx = mv.to;
310            }
311        }
312    }
313    // clone function for is_move_legal. Avoids expensive clone attack map
314    #[inline(always)]
315    fn test_clone(&self) -> Self {
316        Self {
317            pos64: self.pos64,
318            side: self.side,
319            movegen_flags: self.movegen_flags,
320            defend_map: self.defend_map,
321            // create new attack map with empty vec, because it's not needed for testing legality.
322            attack_map: AttackMap::new_no_alloc(),
323            wking_idx: self.wking_idx,
324            bking_idx: self.bking_idx,
325        }
326    }
327
328    pub fn is_move_legal(&self, mv: &Move) -> bool {
329        // TODO defend map only used for castling, so maybe look at where defend map is necessary
330
331        // tests before cloning position
332        if mv.piece.ptype == PieceType::King {
333            if self.is_defended(mv.to) {
334                return false;
335            }
336            if let MoveType::Castle(castle_mv) = mv.move_type {
337                // if any square the king moves from, through or to are defended, move isnt legal
338                return !(self.is_defended(castle_mv.king_squares.0)
339                    || self.is_defended(castle_mv.king_squares.1)
340                    || self.is_defended(castle_mv.king_squares.2));
341            }
342        }
343
344        let mut test_pos = self.test_clone();
345        test_pos.set_king_position(mv);
346
347        if let MoveType::EnPassant(ep_capture) = mv.move_type {
348            test_pos.pos64[ep_capture] = Square::Empty;
349        }
350
351        test_pos.pos64[mv.to] = test_pos.pos64[mv.from];
352        test_pos.pos64[mv.from] = Square::Empty;
353
354        !movegen_in_check(&test_pos.pos64, test_pos.get_king_idx())
355    }
356
357    #[inline(always)]
358    fn toggle_side(&mut self) {
359        self.side = if self.side == PieceColour::White {
360            PieceColour::Black
361        } else {
362            PieceColour::White
363        };
364    }
365
366    #[inline(always)]
367    const fn is_defended(&self, i: usize) -> bool {
368        self.defend_map.0[i]
369    }
370
371    #[inline(always)]
372    fn get_king_idx(&self) -> usize {
373        if self.side == PieceColour::White {
374            self.wking_idx
375        } else {
376            self.bking_idx
377        }
378    }
379
380    #[inline(always)]
381    pub fn is_in_check(&self) -> bool {
382        self.is_defended(self.get_king_idx())
383    }
384
385    pub fn get_pseudo_legal_moves(&self) -> &Vec<Move> {
386        &self.attack_map.0
387    }
388
389    pub fn get_legal_moves(&self) -> Vec<&Move> {
390        let mut legal_moves = Vec::with_capacity(self.attack_map.0.len());
391        for mv in &self.attack_map.0 {
392            if self.is_move_legal(mv) {
393                legal_moves.push(mv);
394            }
395        }
396        legal_moves
397    }
398
399    // sets enpassant movegen flag to Some(idx of pawn that can be captured), if the move is a double pawn push
400    #[inline(always)]
401    fn set_en_passant_flag(&mut self, mv: &Move) {
402        if mv.move_type == MoveType::DoublePawnPush {
403            // if the pawn is beside an enemy pawn, set the polyglot en passant flag
404            if self.pos64.polyglot_is_pawn_beside(mv.to, !self.side) {
405                self.movegen_flags.polyglot_en_passant = Some(mv.to);
406            } else {
407                self.movegen_flags.polyglot_en_passant = None;
408            }
409            self.movegen_flags.en_passant = Some(mv.to);
410        } else {
411            self.movegen_flags.en_passant = None;
412        }
413    }
414
415    #[inline(always)]
416    fn set_castle_flags(&mut self, mv: &Move) {
417        if mv.piece.ptype == PieceType::King {
418            if mv.piece.pcolour == PieceColour::White {
419                self.movegen_flags.white_castle_long = false;
420                self.movegen_flags.white_castle_short = false;
421            } else {
422                self.movegen_flags.black_castle_long = false;
423                self.movegen_flags.black_castle_short = false;
424            }
425        }
426        match mv.from {
427            LONG_BLACK_ROOK_START => {
428                self.movegen_flags.black_castle_long = false;
429            }
430            LONG_WHITE_ROOK_START => {
431                self.movegen_flags.white_castle_long = false;
432            }
433            SHORT_BLACK_ROOK_START => {
434                self.movegen_flags.black_castle_short = false;
435            }
436            SHORT_WHITE_ROOK_START => {
437                self.movegen_flags.white_castle_short = false;
438            }
439            _ => {}
440        }
441        // if a rook is captured
442        match mv.to {
443            LONG_BLACK_ROOK_START => {
444                self.movegen_flags.black_castle_long = false;
445            }
446            LONG_WHITE_ROOK_START => {
447                self.movegen_flags.white_castle_long = false;
448            }
449            SHORT_BLACK_ROOK_START => {
450                self.movegen_flags.black_castle_short = false;
451            }
452            SHORT_WHITE_ROOK_START => {
453                self.movegen_flags.white_castle_short = false;
454            }
455            _ => {}
456        }
457    }
458
459    pub(crate) fn gen_maps(&mut self) {
460        self.defend_map.clear();
461        self.attack_map.clear();
462
463        let pos64 = &self.pos64;
464        let movegen_flags = &self.movegen_flags;
465        let side = self.side;
466        for (i, s) in pos64.iter().enumerate() {
467            if let Square::Piece(p) = s {
468                let is_defending = p.pcolour != side;
469                let map: &mut dyn MoveMap = if is_defending {
470                    &mut self.defend_map
471                } else {
472                    &mut self.attack_map
473                };
474                movegen(pos64, movegen_flags, *p, i, is_defending, map);
475            }
476        }
477    }
478}
479
480impl From<FEN> for Position {
481    fn from(fen: FEN) -> Self {
482        Self::new_from_pub_parts(fen.pos64(), fen.side(), fen.movegen_flags())
483    }
484}