viriformat/chess/board/
mod.rs

1pub mod movegen;
2pub mod validation;
3
4use std::fmt::{Debug, Display, Formatter};
5
6use anyhow::{Context, bail};
7
8use arrayvec::ArrayVec;
9use movegen::RAY_BETWEEN;
10use rand::{rngs::ThreadRng, seq::IndexedRandom};
11
12use crate::{
13    chess::{
14        board::movegen::{
15            MoveList, bishop_attacks, king_attacks, knight_attacks, pawn_attacks, rook_attacks,
16        },
17        chessmove::Move,
18        piece::{Black, Col, Colour, Piece, PieceType, White},
19        squareset::SquareSet,
20        types::{CastlingRights, CheckState, File, Rank, Square, Undo},
21    },
22    makemove::{hash_castling, hash_ep, hash_piece, hash_side},
23};
24
25use crate::chess::piecelayout::{PieceLayout, Threats};
26
27#[derive(Clone, Copy, Debug)]
28pub struct MovedPiece {
29    pub from: Square,
30    pub to: Square,
31    pub piece: Piece,
32}
33
34/// Struct representing some unmaterialised feature update made as part of a move.
35#[derive(Debug, Copy, Clone, PartialEq, Eq)]
36pub struct FeatureUpdate {
37    pub sq: Square,
38    pub piece: Piece,
39}
40
41impl Display for FeatureUpdate {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        write!(f, "{piece} on {sq}", piece = self.piece, sq = self.sq)
44    }
45}
46
47#[derive(PartialEq, Eq, Clone, Debug, Default)]
48pub struct UpdateBuffer {
49    add: ArrayVec<FeatureUpdate, 2>,
50    sub: ArrayVec<FeatureUpdate, 2>,
51}
52
53impl UpdateBuffer {
54    pub fn move_piece(&mut self, from: Square, to: Square, piece: Piece) {
55        self.add.push(FeatureUpdate { sq: to, piece });
56        self.sub.push(FeatureUpdate { sq: from, piece });
57    }
58
59    pub fn clear_piece(&mut self, sq: Square, piece: Piece) {
60        self.sub.push(FeatureUpdate { sq, piece });
61    }
62
63    pub fn add_piece(&mut self, sq: Square, piece: Piece) {
64        self.add.push(FeatureUpdate { sq, piece });
65    }
66
67    pub fn adds(&self) -> &[FeatureUpdate] {
68        &self.add[..]
69    }
70
71    pub fn subs(&self) -> &[FeatureUpdate] {
72        &self.sub[..]
73    }
74}
75
76#[derive(Clone, PartialEq, Eq)]
77pub struct Board {
78    /// The square-sets of all the pieces on the board.
79    pub pieces: PieceLayout,
80    /// An array to accelerate `Board::piece_at()`.
81    pub piece_array: [Option<Piece>; 64],
82    /// The side to move.
83    side: Colour,
84    /// The en passant square.
85    ep_sq: Option<Square>,
86    /// A mask of the rooks that can castle.
87    castle_perm: CastlingRights,
88    /// The number of half moves made since the last capture or pawn advance.
89    fifty_move_counter: u8,
90    /// The number of half moves made since the start of the game.
91    ply: usize,
92
93    /// The Zobrist hash of the board.
94    key: u64,
95    /// The Zobrist hash of the pawns on the board.
96    pawn_key: u64,
97    /// The Zobrist hash of the non-pawns on the board, split by side.
98    non_pawn_key: [u64; 2],
99    /// The Zobrist hash of the minor pieces on the board.
100    minor_key: u64,
101    /// The Zobrist hash of the major pieces on the board.
102    major_key: u64,
103
104    /// Squares that the opponent attacks.
105    threats: Threats,
106
107    // Denotes the variant.
108    chess960: bool,
109
110    height: usize,
111    history: Vec<Undo>,
112}
113
114impl Debug for Board {
115    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("Board")
117            .field("piece_array", &self.piece_array)
118            .field("side", &self.side)
119            .field("ep_sq", &self.ep_sq)
120            .field("fifty_move_counter", &self.fifty_move_counter)
121            .field("height", &self.height)
122            .field("ply", &self.ply)
123            .field("key", &self.key)
124            .field("threats", &self.threats)
125            .field("castle_perm", &self.castle_perm)
126            .finish_non_exhaustive()
127    }
128}
129
130impl Board {
131    pub const STARTING_FEN: &'static str =
132        "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
133    pub const STARTING_FEN_960: &'static str =
134        "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1";
135
136    pub fn new() -> Self {
137        let mut out = Self {
138            pieces: PieceLayout::NULL,
139            piece_array: [None; 64],
140            side: Colour::White,
141            ep_sq: None,
142            fifty_move_counter: 0,
143            height: 0,
144            ply: 0,
145            key: 0,
146            pawn_key: 0,
147            non_pawn_key: [0; 2],
148            minor_key: 0,
149            major_key: 0,
150            threats: Threats::default(),
151            castle_perm: CastlingRights::NONE,
152            history: Vec::new(),
153            chess960: true,
154        };
155        out.reset();
156        out
157    }
158
159    pub fn to_bulletformat(
160        &self,
161        wdl: u8,
162        eval: i16,
163    ) -> Result<bulletformat::ChessBoard, anyhow::Error> {
164        let mut bbs = [0; 8];
165        let piece_layout = &self.pieces;
166        bbs[0] = piece_layout.occupied_co(Colour::White).inner();
167        bbs[1] = piece_layout.occupied_co(Colour::Black).inner();
168        bbs[2] = piece_layout.of_type(PieceType::Pawn).inner();
169        bbs[3] = piece_layout.of_type(PieceType::Knight).inner();
170        bbs[4] = piece_layout.of_type(PieceType::Bishop).inner();
171        bbs[5] = piece_layout.of_type(PieceType::Rook).inner();
172        bbs[6] = piece_layout.of_type(PieceType::Queen).inner();
173        bbs[7] = piece_layout.of_type(PieceType::King).inner();
174        let bulletformat = bulletformat::ChessBoard::from_raw(
175            bbs,
176            (self.turn() != Colour::White).into(),
177            eval,
178            f32::from(wdl) / 2.0,
179        )
180        .map_err(|e| anyhow::anyhow!(e))
181        .with_context(|| "Failed to convert raw components into bulletformat::ChessBoard.")?;
182        Ok(bulletformat)
183    }
184
185    pub const fn ep_sq(&self) -> Option<Square> {
186        self.ep_sq
187    }
188
189    pub fn ep_sq_mut(&mut self) -> &mut Option<Square> {
190        &mut self.ep_sq
191    }
192
193    pub fn turn_mut(&mut self) -> &mut Colour {
194        &mut self.side
195    }
196
197    pub fn halfmove_clock_mut(&mut self) -> &mut u8 {
198        &mut self.fifty_move_counter
199    }
200
201    pub fn set_fullmove_clock(&mut self, fullmove_clock: u16) {
202        self.ply = (fullmove_clock as usize - 1) * 2 + usize::from(self.side == Colour::Black);
203    }
204
205    pub const fn zobrist_key(&self) -> u64 {
206        self.key
207    }
208
209    pub const fn pawn_key(&self) -> u64 {
210        self.pawn_key
211    }
212
213    pub fn non_pawn_key(&self, colour: Colour) -> u64 {
214        self.non_pawn_key[colour]
215    }
216
217    pub const fn minor_key(&self) -> u64 {
218        self.minor_key
219    }
220
221    pub const fn major_key(&self) -> u64 {
222        self.major_key
223    }
224
225    #[cfg(debug_assertions)]
226    pub const fn all_keys(&self) -> (u64, u64, [u64; 2], u64, u64) {
227        (
228            self.key,
229            self.pawn_key,
230            self.non_pawn_key,
231            self.minor_key,
232            self.major_key,
233        )
234    }
235
236    pub fn n_men(&self) -> u8 {
237        #![allow(clippy::cast_possible_truncation)]
238        self.pieces.occupied().count() as u8
239    }
240
241    pub const fn ply(&self) -> usize {
242        self.ply
243    }
244
245    pub const fn threats(&self) -> &Threats {
246        &self.threats
247    }
248
249    pub fn king_sq(&self, side: Colour) -> Square {
250        debug_assert!(side == Colour::White || side == Colour::Black);
251        debug_assert_eq!(self.pieces.king::<White>().count(), 1);
252        debug_assert_eq!(self.pieces.king::<Black>().count(), 1);
253        let sq = match side {
254            Colour::White => self.pieces.king::<White>().first(),
255            Colour::Black => self.pieces.king::<Black>().first(),
256        };
257        debug_assert_eq!(self.pieces.piece_at(sq).unwrap().colour(), side);
258        debug_assert_eq!(
259            self.pieces.piece_at(sq).unwrap().piece_type(),
260            PieceType::King
261        );
262        sq
263    }
264
265    pub const fn in_check(&self) -> bool {
266        self.threats.checkers.non_empty()
267    }
268
269    pub fn zero_height(&mut self) {
270        self.height = 0;
271    }
272
273    pub const fn height(&self) -> usize {
274        self.height
275    }
276
277    pub const fn turn(&self) -> Colour {
278        self.side
279    }
280
281    pub const fn castling_rights(&self) -> CastlingRights {
282        self.castle_perm
283    }
284
285    pub fn castling_rights_mut(&mut self) -> &mut CastlingRights {
286        &mut self.castle_perm
287    }
288
289    pub fn generate_pos_keys(&self) -> (u64, u64, [u64; 2], u64, u64) {
290        let mut key = 0;
291        let mut pawn_key = 0;
292        let mut non_pawn_key = [0; 2];
293        let mut minor_key = 0;
294        let mut major_key = 0;
295        self.pieces.visit_pieces(|sq, piece| {
296            hash_piece(&mut key, piece, sq);
297            if piece.piece_type() == PieceType::Pawn {
298                hash_piece(&mut pawn_key, piece, sq);
299            } else {
300                hash_piece(&mut non_pawn_key[piece.colour()], piece, sq);
301                if piece.piece_type() == PieceType::King {
302                    hash_piece(&mut major_key, piece, sq);
303                    hash_piece(&mut minor_key, piece, sq);
304                } else if matches!(piece.piece_type(), PieceType::Queen | PieceType::Rook) {
305                    hash_piece(&mut major_key, piece, sq);
306                } else {
307                    hash_piece(&mut minor_key, piece, sq);
308                }
309            }
310        });
311
312        if self.side == Colour::White {
313            hash_side(&mut key);
314        }
315
316        if let Some(ep_sq) = self.ep_sq {
317            hash_ep(&mut key, ep_sq);
318        }
319
320        hash_castling(&mut key, self.castle_perm);
321
322        debug_assert!(self.fifty_move_counter <= 100);
323
324        (key, pawn_key, non_pawn_key, minor_key, major_key)
325    }
326
327    pub fn regenerate_zobrist(&mut self) {
328        (
329            self.key,
330            self.pawn_key,
331            self.non_pawn_key,
332            self.minor_key,
333            self.major_key,
334        ) = self.generate_pos_keys();
335    }
336
337    pub fn regenerate_threats(&mut self) {
338        self.threats = self.generate_threats(self.side.flip());
339    }
340
341    pub fn generate_threats(&self, side: Colour) -> Threats {
342        if side == Colour::White {
343            self.generate_threats_from::<White>()
344        } else {
345            self.generate_threats_from::<Black>()
346        }
347    }
348
349    pub fn generate_threats_from<C: Col>(&self) -> Threats {
350        let mut threats = SquareSet::EMPTY;
351        let mut checkers = SquareSet::EMPTY;
352
353        let their_pawns = self.pieces.pawns::<C>();
354        let their_knights = self.pieces.knights::<C>();
355        let their_diags = self.pieces.diags::<C>();
356        let their_orthos = self.pieces.orthos::<C>();
357        let their_king = self.king_sq(C::COLOUR);
358        let blockers = self.pieces.occupied();
359
360        // compute threats
361        threats |= pawn_attacks::<C>(their_pawns);
362
363        for sq in their_knights {
364            threats |= knight_attacks(sq);
365        }
366        for sq in their_diags {
367            threats |= bishop_attacks(sq, blockers);
368        }
369        for sq in their_orthos {
370            threats |= rook_attacks(sq, blockers);
371        }
372
373        threats |= king_attacks(their_king);
374
375        // compute checkers
376        let our_king = self.king_sq(C::Opposite::COLOUR);
377        let king_bb = our_king.as_set();
378        let backwards_from_king = pawn_attacks::<C::Opposite>(king_bb);
379        checkers |= backwards_from_king & their_pawns;
380
381        let knight_attacks = knight_attacks(our_king);
382
383        checkers |= knight_attacks & their_knights;
384
385        let diag_attacks = bishop_attacks(our_king, blockers);
386
387        checkers |= diag_attacks & their_diags;
388
389        let ortho_attacks = rook_attacks(our_king, blockers);
390
391        checkers |= ortho_attacks & their_orthos;
392
393        Threats {
394            all: threats,
395            /* pawn: pawn_threats, minor: minor_threats, rook: rook_threats, */ checkers,
396        }
397    }
398
399    pub fn reset(&mut self) {
400        self.pieces.reset();
401        self.piece_array = [None; 64];
402        self.side = Colour::White;
403        self.ep_sq = None;
404        self.fifty_move_counter = 0;
405        self.height = 0;
406        self.ply = 0;
407        self.castle_perm = CastlingRights::NONE;
408        self.key = 0;
409        self.pawn_key = 0;
410        self.threats = Threats::default();
411        self.chess960 = true;
412        self.history.clear();
413    }
414
415    pub fn set_frc_idx(&mut self, scharnagl: usize) {
416        #![allow(clippy::cast_possible_truncation)]
417        assert!(scharnagl < 960, "scharnagl index out of range");
418        let backrank = Self::get_scharnagl_backrank(scharnagl);
419        self.reset();
420        for (&piece_type, file) in backrank.iter().zip(File::all()) {
421            let sq = Square::from_rank_file(Rank::One, file);
422            self.add_piece(sq, Piece::new(Colour::White, piece_type));
423        }
424        for file in File::all() {
425            // add pawns
426            let sq = Square::from_rank_file(Rank::Two, file);
427            self.add_piece(sq, Piece::new(Colour::White, PieceType::Pawn));
428        }
429        for (&piece_type, file) in backrank.iter().zip(File::all()) {
430            let sq = Square::from_rank_file(Rank::Eight, file);
431            self.add_piece(sq, Piece::new(Colour::Black, piece_type));
432        }
433        for file in File::all() {
434            // add pawns
435            let sq = Square::from_rank_file(Rank::Seven, file);
436            self.add_piece(sq, Piece::new(Colour::Black, PieceType::Pawn));
437        }
438        let mut rook_indices = backrank.iter().enumerate().filter_map(|(i, &piece)| {
439            if piece == PieceType::Rook {
440                Some(i)
441            } else {
442                None
443            }
444        });
445        let queenside_file = rook_indices.next().unwrap();
446        let kingside_file = rook_indices.next().unwrap();
447        self.castle_perm = CastlingRights {
448            wk: Some(Square::from_rank_file(
449                Rank::One,
450                File::from_index(kingside_file as u8).unwrap(),
451            )),
452            wq: Some(Square::from_rank_file(
453                Rank::One,
454                File::from_index(queenside_file as u8).unwrap(),
455            )),
456            bk: Some(Square::from_rank_file(
457                Rank::Eight,
458                File::from_index(kingside_file as u8).unwrap(),
459            )),
460            bq: Some(Square::from_rank_file(
461                Rank::Eight,
462                File::from_index(queenside_file as u8).unwrap(),
463            )),
464        };
465        (
466            self.key,
467            self.pawn_key,
468            self.non_pawn_key,
469            self.minor_key,
470            self.major_key,
471        ) = self.generate_pos_keys();
472        self.threats = self.generate_threats(self.side.flip());
473    }
474
475    pub fn set_dfrc_idx(&mut self, scharnagl: usize) {
476        #![allow(clippy::cast_possible_truncation)]
477        assert!(scharnagl < 960 * 960, "double scharnagl index out of range");
478        let white_backrank = Self::get_scharnagl_backrank(scharnagl % 960);
479        let black_backrank = Self::get_scharnagl_backrank(scharnagl / 960);
480        self.reset();
481        for (&piece_type, file) in white_backrank.iter().zip(File::all()) {
482            let sq = Square::from_rank_file(Rank::One, file);
483            self.add_piece(sq, Piece::new(Colour::White, piece_type));
484        }
485        for file in File::all() {
486            // add pawns
487            let sq = Square::from_rank_file(Rank::Two, file);
488            self.add_piece(sq, Piece::new(Colour::White, PieceType::Pawn));
489        }
490        for (&piece_type, file) in black_backrank.iter().zip(File::all()) {
491            let sq = Square::from_rank_file(Rank::Eight, file);
492            self.add_piece(sq, Piece::new(Colour::Black, piece_type));
493        }
494        for file in File::all() {
495            // add pawns
496            let sq = Square::from_rank_file(Rank::Seven, file);
497            self.add_piece(sq, Piece::new(Colour::Black, PieceType::Pawn));
498        }
499        let mut white_rook_indices = white_backrank.iter().enumerate().filter_map(|(i, &piece)| {
500            if piece == PieceType::Rook {
501                Some(i)
502            } else {
503                None
504            }
505        });
506        let white_queenside_file = white_rook_indices.next().unwrap();
507        let white_kingside_file = white_rook_indices.next().unwrap();
508        let mut black_rook_indices = black_backrank.iter().enumerate().filter_map(|(i, &piece)| {
509            if piece == PieceType::Rook {
510                Some(i)
511            } else {
512                None
513            }
514        });
515        let black_queenside_file = black_rook_indices.next().unwrap();
516        let black_kingside_file = black_rook_indices.next().unwrap();
517        self.castle_perm = CastlingRights {
518            wk: Some(Square::from_rank_file(
519                Rank::One,
520                File::from_index(white_kingside_file as u8).unwrap(),
521            )),
522            wq: Some(Square::from_rank_file(
523                Rank::One,
524                File::from_index(white_queenside_file as u8).unwrap(),
525            )),
526            bk: Some(Square::from_rank_file(
527                Rank::Eight,
528                File::from_index(black_kingside_file as u8).unwrap(),
529            )),
530            bq: Some(Square::from_rank_file(
531                Rank::Eight,
532                File::from_index(black_queenside_file as u8).unwrap(),
533            )),
534        };
535        (
536            self.key,
537            self.pawn_key,
538            self.non_pawn_key,
539            self.minor_key,
540            self.major_key,
541        ) = self.generate_pos_keys();
542        self.threats = self.generate_threats(self.side.flip());
543    }
544
545    pub fn get_scharnagl_backrank(scharnagl: usize) -> [PieceType; 8] {
546        // White's starting array can be derived from its number N (0 ... 959) as follows (https://en.wikipedia.org/wiki/Fischer_random_chess_numbering_scheme#Direct_derivation):
547        // A. Divide N by 4, yielding quotient N2 and remainder B1. Place a Bishop upon the bright square corresponding to B1 (0=b, 1=d, 2=f, 3=h).
548        // B. Divide N2 by 4 again, yielding quotient N3 and remainder B2. Place a second Bishop upon the dark square corresponding to B2 (0=a, 1=c, 2=e, 3=g).
549        // C. Divide N3 by 6, yielding quotient N4 and remainder Q. Place the Queen according to Q, where 0 is the first free square starting from a, 1 is the second, etc.
550        // D. N4 will be a single digit, 0 ... 9. Ignoring Bishops and Queen, find the positions of two Knights within the remaining five spaces.
551        //    Place the Knights according to its value by consulting the following N5N table:
552        // DIGIT | Knight Positioning
553        //   0   | N N - - -
554        //   1   | N - N - -
555        //   2   | N - - N -
556        //   3   | N - - - N
557        //   4   | - N N - -
558        //   5   | - N - N -
559        //   6   | - N - - N
560        //   7   | - - N N -
561        //   8   | - - N - N
562        //   9   | - - - N N
563        // E. There are three blank squares remaining; place a Rook in each of the outer two and the King in the middle one.
564        let mut out = [None; 8];
565        let n = scharnagl;
566        let (n2, b1) = (n / 4, n % 4);
567        match b1 {
568            0 => out[File::B] = Some(PieceType::Bishop),
569            1 => out[File::D] = Some(PieceType::Bishop),
570            2 => out[File::F] = Some(PieceType::Bishop),
571            3 => out[File::H] = Some(PieceType::Bishop),
572            _ => unreachable!(),
573        }
574        let (n3, b2) = (n2 / 4, n2 % 4);
575        match b2 {
576            0 => out[File::A] = Some(PieceType::Bishop),
577            1 => out[File::C] = Some(PieceType::Bishop),
578            2 => out[File::E] = Some(PieceType::Bishop),
579            3 => out[File::G] = Some(PieceType::Bishop),
580            _ => unreachable!(),
581        }
582        let (n4, mut q) = (n3 / 6, n3 % 6);
583        for (idx, &piece) in out.iter().enumerate() {
584            if piece.is_none() {
585                if q == 0 {
586                    out[idx] = Some(PieceType::Queen);
587                    break;
588                }
589                q -= 1;
590            }
591        }
592        let remaining_slots = out.iter_mut().filter(|piece| piece.is_none());
593        let selection = match n4 {
594            0 => [0, 1],
595            1 => [0, 2],
596            2 => [0, 3],
597            3 => [0, 4],
598            4 => [1, 2],
599            5 => [1, 3],
600            6 => [1, 4],
601            7 => [2, 3],
602            8 => [2, 4],
603            9 => [3, 4],
604            _ => unreachable!(),
605        };
606        for (i, slot) in remaining_slots.enumerate() {
607            if i == selection[0] || i == selection[1] {
608                *slot = Some(PieceType::Knight);
609            }
610        }
611
612        out.iter_mut()
613            .filter(|piece| piece.is_none())
614            .zip([PieceType::Rook, PieceType::King, PieceType::Rook])
615            .for_each(|(slot, piece)| *slot = Some(piece));
616
617        out.map(Option::unwrap)
618    }
619
620    pub fn set_from_fen(&mut self, fen: &str, chess960: bool) -> anyhow::Result<()> {
621        if !fen.is_ascii() {
622            bail!(format!("FEN string is not ASCII: {fen}"));
623        }
624
625        let mut rank = Rank::Eight;
626        let mut file = File::A;
627
628        self.reset();
629
630        self.chess960 = chess960;
631
632        let fen_chars = fen.as_bytes();
633        let split_idx = fen_chars
634            .iter()
635            .position(|&c| c == b' ')
636            .with_context(|| format!("FEN string is missing space: {fen}"))?;
637        let (board_part, info_part) = fen_chars.split_at(split_idx);
638
639        for &c in board_part {
640            let mut count = 1;
641            let piece;
642            match c {
643                b'P' => piece = Some(Piece::WP),
644                b'R' => piece = Some(Piece::WR),
645                b'N' => piece = Some(Piece::WN),
646                b'B' => piece = Some(Piece::WB),
647                b'Q' => piece = Some(Piece::WQ),
648                b'K' => piece = Some(Piece::WK),
649                b'p' => piece = Some(Piece::BP),
650                b'r' => piece = Some(Piece::BR),
651                b'n' => piece = Some(Piece::BN),
652                b'b' => piece = Some(Piece::BB),
653                b'q' => piece = Some(Piece::BQ),
654                b'k' => piece = Some(Piece::BK),
655                b'1'..=b'8' => {
656                    piece = None;
657                    count = c - b'0';
658                }
659                b'/' => {
660                    rank = rank.sub(1).unwrap();
661                    file = File::A;
662                    continue;
663                }
664                c => {
665                    bail!(
666                        "FEN string is invalid, got unexpected character: \"{}\"",
667                        c as char
668                    );
669                }
670            }
671
672            for _ in 0..count {
673                let sq = Square::from_rank_file(rank, file);
674                if let Some(piece) = piece {
675                    // this is only ever run once, as count is 1 for non-empty pieces.
676                    self.add_piece(sq, piece);
677                }
678                file = file.add(1).unwrap_or(File::H);
679            }
680        }
681
682        let mut info_parts = info_part[1..].split(|&c| c == b' ');
683
684        self.set_side(info_parts.next())?;
685        self.set_castling(info_parts.next())?;
686        self.set_ep(info_parts.next())?;
687        self.set_halfmove(info_parts.next())?;
688        self.set_fullmove(info_parts.next())?;
689
690        (
691            self.key,
692            self.pawn_key,
693            self.non_pawn_key,
694            self.minor_key,
695            self.major_key,
696        ) = self.generate_pos_keys();
697        self.threats = self.generate_threats(self.side.flip());
698
699        Ok(())
700    }
701
702    pub fn set_startpos(&mut self, chess960: bool) {
703        let starting_fen = if self.chess960 {
704            Self::STARTING_FEN_960
705        } else {
706            Self::STARTING_FEN
707        };
708        self.set_from_fen(starting_fen, chess960)
709            .expect("for some reason, STARTING_FEN is now broken.");
710    }
711
712    #[cfg(test)]
713    pub fn from_fen(fen: &str, chess960: bool) -> anyhow::Result<Self> {
714        let mut out = Self::new();
715        out.set_from_fen(fen, chess960)?;
716        Ok(out)
717    }
718
719    #[cfg(test)]
720    pub fn from_frc_idx(scharnagl: usize) -> Self {
721        let mut out = Self::new();
722        out.set_frc_idx(scharnagl);
723        out
724    }
725
726    #[cfg(test)]
727    pub fn from_dfrc_idx(scharnagl: usize) -> Self {
728        let mut out = Self::new();
729        out.set_dfrc_idx(scharnagl);
730        out
731    }
732
733    fn set_side(&mut self, side_part: Option<&[u8]>) -> anyhow::Result<()> {
734        self.side = match side_part {
735            Some([b'w']) => Colour::White,
736            Some([b'b']) => Colour::Black,
737            Some(other) => {
738                bail!(format!(
739                    "FEN string is invalid, expected side to be 'w' or 'b', got \"{}\"",
740                    std::str::from_utf8(other).unwrap_or("<invalid utf8>")
741                ))
742            }
743            None => bail!("FEN string is invalid, expected side part."),
744        };
745        Ok(())
746    }
747
748    fn set_castling(&mut self, castling_part: Option<&[u8]>) -> anyhow::Result<()> {
749        match castling_part {
750            None => bail!("FEN string is invalid, expected castling part."),
751            Some(b"-") => self.castle_perm = CastlingRights::NONE,
752            Some(castling) if !self.chess960 => {
753                for &c in castling {
754                    match c {
755                        b'K' => self.castle_perm.wk = Some(Square::H1),
756                        b'Q' => self.castle_perm.wq = Some(Square::A1),
757                        b'k' => self.castle_perm.bk = Some(Square::H8),
758                        b'q' => self.castle_perm.bq = Some(Square::A8),
759                        _ => {
760                            bail!(format!(
761                                "FEN string is invalid, expected castling part to be of the form 'KQkq', got \"{}\"",
762                                std::str::from_utf8(castling).unwrap_or("<invalid utf8>")
763                            ))
764                        }
765                    }
766                }
767            }
768            Some(shredder_castling) => {
769                // valid shredder castling strings are of the form "AHah", "Bd"
770                let white_king = self.king_sq(Colour::White);
771                let black_king = self.king_sq(Colour::Black);
772                if white_king.rank() != Rank::One
773                    && shredder_castling.iter().any(u8::is_ascii_uppercase)
774                {
775                    bail!(format!(
776                        "FEN string is invalid, white king is not on the back rank, but got uppercase castling characters, implying present castling rights, got \"{}\"",
777                        std::str::from_utf8(shredder_castling).unwrap_or("<invalid utf8>")
778                    ));
779                }
780                if black_king.rank() != Rank::Eight
781                    && shredder_castling.iter().any(u8::is_ascii_lowercase)
782                {
783                    bail!(format!(
784                        "FEN string is invalid, black king is not on the back rank, but got lowercase castling characters, implying present castling rights, got \"{}\"",
785                        std::str::from_utf8(shredder_castling).unwrap_or("<invalid utf8>")
786                    ));
787                }
788                for &c in shredder_castling {
789                    match c {
790                        b'K' => self.castle_perm.wk = Some(Square::H1),
791                        b'Q' => self.castle_perm.wq = Some(Square::A1),
792                        b'k' => self.castle_perm.bk = Some(Square::H8),
793                        b'q' => self.castle_perm.bq = Some(Square::A8),
794                        c if c.is_ascii_uppercase() => {
795                            let file = File::from_index(c - b'A').unwrap();
796                            let king_file = white_king.file();
797                            if file == king_file {
798                                bail!(format!(
799                                    "FEN string is invalid, white king is on file {:?}, but got castling rights on that file - got \"{}\"",
800                                    king_file,
801                                    std::str::from_utf8(shredder_castling)
802                                        .unwrap_or("<invalid utf8>")
803                                ));
804                            }
805                            let sq = Square::from_rank_file(Rank::One, file);
806                            if file > king_file {
807                                // castling rights are to the right of the king, so it's "kingside" castling rights.
808                                self.castle_perm.wk = Some(sq);
809                            } else {
810                                // castling rights are to the left of the king, so it's "queenside" castling rights.
811                                self.castle_perm.wq = Some(sq);
812                            }
813                        }
814                        c if c.is_ascii_lowercase() => {
815                            let file = File::from_index(c - b'a').unwrap();
816                            let king_file = black_king.file();
817                            if file == king_file {
818                                bail!(format!(
819                                    "FEN string is invalid, black king is on file {:?}, but got castling rights on that file - got \"{}\"",
820                                    king_file,
821                                    std::str::from_utf8(shredder_castling)
822                                        .unwrap_or("<invalid utf8>")
823                                ));
824                            }
825                            let sq = Square::from_rank_file(Rank::Eight, file);
826                            if file > king_file {
827                                // castling rights are to the right of the king, so it's "kingside" castling rights.
828                                self.castle_perm.bk = Some(sq);
829                            } else {
830                                // castling rights are to the left of the king, so it's "queenside" castling rights.
831                                self.castle_perm.bq = Some(sq);
832                            }
833                        }
834                        _ => {
835                            bail!(format!(
836                                "FEN string is invalid, expected castling part to be of the form 'AHah', 'Bd', or '-', got \"{}\"",
837                                std::str::from_utf8(shredder_castling).unwrap_or("<invalid utf8>")
838                            ));
839                        }
840                    }
841                }
842            }
843        }
844
845        Ok(())
846    }
847
848    fn set_ep(&mut self, ep_part: Option<&[u8]>) -> anyhow::Result<()> {
849        match ep_part {
850            None => bail!("FEN string is invalid, expected en passant part.".to_string()),
851            Some([b'-']) => self.ep_sq = None,
852            Some(ep_sq) => {
853                if ep_sq.len() != 2 {
854                    bail!(format!(
855                        "FEN string is invalid, expected en passant part to be of the form 'a1', got \"{}\"",
856                        std::str::from_utf8(ep_sq).unwrap_or("<invalid utf8>")
857                    ));
858                }
859                let file = ep_sq[0] - b'a';
860                let rank = ep_sq[1] - b'1';
861                let file = File::from_index(file);
862                let rank = Rank::from_index(rank);
863                if !(file.is_some() && rank.is_some()) {
864                    bail!(format!(
865                        "FEN string is invalid, expected en passant part to be of the form 'a1', got \"{}\"",
866                        std::str::from_utf8(ep_sq).unwrap_or("<invalid utf8>")
867                    ));
868                }
869                self.ep_sq = Some(Square::from_rank_file(rank.unwrap(), file.unwrap()));
870            }
871        }
872
873        Ok(())
874    }
875
876    fn set_halfmove(&mut self, halfmove_part: Option<&[u8]>) -> anyhow::Result<()> {
877        match halfmove_part {
878            None => bail!("FEN string is invalid, expected halfmove clock part.".to_string()),
879            Some(halfmove_clock) => {
880                self.fifty_move_counter = std::str::from_utf8(halfmove_clock)
881                    .with_context(|| "FEN string is invalid, expected halfmove clock part to be valid UTF-8")?
882                    .parse::<u8>()
883                    .with_context(|| {
884                        format!(
885                            "FEN string is invalid, expected halfmove clock part to be a number, got \"{}\"",
886                            std::str::from_utf8(halfmove_clock).unwrap_or("<invalid utf8>")
887                        )
888                    })?;
889            }
890        }
891
892        Ok(())
893    }
894
895    fn set_fullmove(&mut self, fullmove_part: Option<&[u8]>) -> anyhow::Result<()> {
896        match fullmove_part {
897            None => bail!("FEN string is invalid, expected fullmove number part.".to_string()),
898            Some(fullmove_number) => {
899                let fullmove_number = std::str::from_utf8(fullmove_number)
900                    .with_context(
901                        || "FEN string is invalid, expected fullmove number part to be valid UTF-8",
902                    )?
903                    .parse::<usize>()
904                    .with_context(
905                        || "FEN string is invalid, expected fullmove number part to be a number",
906                    )?;
907                self.ply = (fullmove_number - 1) * 2;
908                if self.side == Colour::Black {
909                    self.ply += 1;
910                }
911            }
912        }
913
914        Ok(())
915    }
916
917    /// Determines if `sq` is attacked by `side`
918    pub fn sq_attacked(&self, sq: Square, side: Colour) -> bool {
919        if side == Colour::White {
920            self.sq_attacked_by::<White>(sq)
921        } else {
922            self.sq_attacked_by::<Black>(sq)
923        }
924    }
925
926    pub fn sq_attacked_by<C: Col>(&self, sq: Square) -> bool {
927        // we remove this check because the board actually *can*
928        // be in an inconsistent state when we call this, as it's
929        // used to determine if a move is legal, and we'd like to
930        // only do a lot of the make_move work *after* we've
931        // determined that the move is legal.
932        // #[cfg(debug_assertions)]
933        // self.check_validity().unwrap();
934
935        if C::WHITE == (self.side == Colour::Black) {
936            return self.threats.all.contains_square(sq);
937        }
938
939        let sq_bb = sq.as_set();
940        let our_pawns = self.pieces.pawns::<C>();
941        let our_knights = self.pieces.knights::<C>();
942        let our_diags = self.pieces.diags::<C>();
943        let our_orthos = self.pieces.orthos::<C>();
944        let our_king = self.pieces.king::<C>();
945        let blockers = self.pieces.occupied();
946
947        // pawns
948        let attacks = pawn_attacks::<C>(our_pawns);
949        if (attacks & sq_bb).non_empty() {
950            return true;
951        }
952
953        // knights
954        let knight_attacks_from_this_square = movegen::knight_attacks(sq);
955        if (our_knights & knight_attacks_from_this_square).non_empty() {
956            return true;
957        }
958
959        // bishops, queens
960        let diag_attacks_from_this_square = movegen::bishop_attacks(sq, blockers);
961        if (our_diags & diag_attacks_from_this_square).non_empty() {
962            return true;
963        }
964
965        // rooks, queens
966        let ortho_attacks_from_this_square = movegen::rook_attacks(sq, blockers);
967        if (our_orthos & ortho_attacks_from_this_square).non_empty() {
968            return true;
969        }
970
971        // king
972        let king_attacks_from_this_square = movegen::king_attacks(sq);
973        if (our_king & king_attacks_from_this_square).non_empty() {
974            return true;
975        }
976
977        false
978    }
979
980    /// Checks whether a move is pseudo-legal.
981    /// This means that it is a legal move, except for the fact that it might leave the king in check.
982    pub fn is_pseudo_legal(&self, m: Move) -> bool {
983        let from = m.from();
984        let to = m.to();
985
986        let moved_piece = self.piece_at(from);
987        let captured_piece = if m.is_castle() {
988            None
989        } else {
990            self.piece_at(to)
991        };
992        let is_pawn_double_push = self.is_double_pawn_push(m);
993
994        let Some(moved_piece) = moved_piece else {
995            return false;
996        };
997
998        if moved_piece.colour() != self.side {
999            return false;
1000        }
1001
1002        if let Some(captured_piece) = captured_piece
1003            && captured_piece.colour() == self.side
1004        {
1005            return false;
1006        }
1007
1008        if moved_piece.piece_type() != PieceType::Pawn
1009            && (is_pawn_double_push || m.is_ep() || m.is_promo())
1010        {
1011            return false;
1012        }
1013
1014        if moved_piece.piece_type() != PieceType::King && m.is_castle() {
1015            return false;
1016        }
1017
1018        if captured_piece.is_some() && is_pawn_double_push {
1019            return false;
1020        }
1021
1022        if m.is_castle() {
1023            return self.is_pseudo_legal_castling(m);
1024        }
1025
1026        if moved_piece.piece_type() == PieceType::Pawn {
1027            let should_be_promoting = to > Square::H7 || to < Square::A2;
1028            if should_be_promoting && !m.is_promo() {
1029                return false;
1030            }
1031            if m.is_ep() {
1032                return Some(to) == self.ep_sq;
1033            } else if is_pawn_double_push {
1034                if from.relative_to(self.side).rank() != Rank::Two {
1035                    return false;
1036                }
1037                let Some(one_forward) = from.pawn_push(self.side) else {
1038                    return false;
1039                };
1040                return self.piece_at(one_forward).is_none()
1041                    && Some(to) == one_forward.pawn_push(self.side);
1042            } else if captured_piece.is_none() {
1043                return Some(to) == from.pawn_push(self.side);
1044            }
1045            // pawn capture
1046            if self.side == Colour::White {
1047                return (pawn_attacks::<White>(from.as_set()) & to.as_set()).non_empty();
1048            }
1049            return (pawn_attacks::<Black>(from.as_set()) & to.as_set()).non_empty();
1050        }
1051
1052        (to.as_set()
1053            & movegen::attacks_by_type(moved_piece.piece_type(), from, self.pieces.occupied()))
1054        .non_empty()
1055    }
1056
1057    pub fn is_pseudo_legal_castling(&self, m: Move) -> bool {
1058        // illegal if:
1059        // - we're not moving the king
1060        // - we're not doing everything on the home rank
1061        // - we don't have castling rights on the target square
1062        // - we're in check
1063        // - there are pieces between the king and the rook
1064        // - the king passes through a square that is attacked by the opponent
1065        // - the king ends up in check (not checked here)
1066        let Some(moved) = self.piece_at(m.from()) else {
1067            return false;
1068        };
1069        if moved.piece_type() != PieceType::King {
1070            return false;
1071        }
1072        let home_rank = if self.side == Colour::White {
1073            SquareSet::RANK_1
1074        } else {
1075            SquareSet::RANK_8
1076        };
1077        if (m.to().as_set() & home_rank).is_empty() {
1078            return false;
1079        }
1080        if (m.from().as_set() & home_rank).is_empty() {
1081            return false;
1082        }
1083        let (king_dst, rook_dst) = if m.to() > m.from() {
1084            // kingside castling.
1085            if Some(m.to())
1086                != (if self.side == Colour::Black {
1087                    self.castle_perm.bk
1088                } else {
1089                    self.castle_perm.wk
1090                })
1091            {
1092                // the to-square doesn't match the castling rights
1093                // (it goes to the wrong place, or the rights don't exist)
1094                return false;
1095            }
1096            if self.side == Colour::Black {
1097                (Square::G8, Square::F8)
1098            } else {
1099                (Square::G1, Square::F1)
1100            }
1101        } else {
1102            // queenside castling.
1103            if Some(m.to())
1104                != (if self.side == Colour::Black {
1105                    self.castle_perm.bq
1106                } else {
1107                    self.castle_perm.wq
1108                })
1109            {
1110                // the to-square doesn't match the castling rights
1111                // (it goes to the wrong place, or the rights don't exist)
1112                return false;
1113            }
1114            if self.side == Colour::Black {
1115                (Square::C8, Square::D8)
1116            } else {
1117                (Square::C1, Square::D1)
1118            }
1119        };
1120
1121        // king_path is the path the king takes to get to its destination.
1122        let king_path = RAY_BETWEEN[m.from()][king_dst];
1123        // rook_path is the path the rook takes to get to its destination.
1124        let rook_path = RAY_BETWEEN[m.from()][m.to()];
1125        // castle_occ is the occupancy that "counts" for castling.
1126        let castle_occ = self.pieces.occupied() ^ m.from().as_set() ^ m.to().as_set();
1127
1128        (castle_occ & (king_path | rook_path | king_dst.as_set() | rook_dst.as_set())).is_empty()
1129            && !self.any_attacked(king_path | m.from().as_set(), self.side.flip())
1130    }
1131
1132    pub fn any_attacked(&self, squares: SquareSet, by: Colour) -> bool {
1133        if by == self.side.flip() {
1134            (squares & self.threats.all).non_empty()
1135        } else {
1136            for sq in squares {
1137                if self.sq_attacked(sq, by) {
1138                    return true;
1139                }
1140            }
1141            false
1142        }
1143    }
1144
1145    pub fn add_piece(&mut self, sq: Square, piece: Piece) {
1146        self.pieces.set_piece_at(sq, piece);
1147        *self.piece_at_mut(sq) = Some(piece);
1148    }
1149
1150    /// Gets the piece that will be moved by the given move.
1151    pub fn moved_piece(&self, m: Move) -> Option<Piece> {
1152        let idx = m.from();
1153        self.piece_array[idx]
1154    }
1155
1156    /// Gets the piece that will be captured by the given move.
1157    pub fn captured_piece(&self, m: Move) -> Option<Piece> {
1158        if m.is_castle() {
1159            return None;
1160        }
1161        let idx = m.to();
1162        self.piece_array[idx]
1163    }
1164
1165    /// Determines whether this move would be a capture in the current position.
1166    pub fn is_capture(&self, m: Move) -> bool {
1167        self.captured_piece(m).is_some()
1168    }
1169
1170    /// Determines whether this move would be a double pawn push in the current position.
1171    pub fn is_double_pawn_push(&self, m: Move) -> bool {
1172        let from_bb = m.from().as_set();
1173        if (from_bb & (SquareSet::RANK_2 | SquareSet::RANK_7)).is_empty() {
1174            return false;
1175        }
1176        let to_bb = m.to().as_set();
1177        if (to_bb & (SquareSet::RANK_4 | SquareSet::RANK_5)).is_empty() {
1178            return false;
1179        }
1180        let Some(piece_moved) = self.moved_piece(m) else {
1181            return false;
1182        };
1183        piece_moved.piece_type() == PieceType::Pawn
1184    }
1185
1186    /// Determines whether this move would be tactical in the current position.
1187    pub fn is_tactical(&self, m: Move) -> bool {
1188        m.is_promo() || m.is_ep() || self.is_capture(m)
1189    }
1190
1191    /// Gets the piece at the given square.
1192    pub fn piece_at(&self, sq: Square) -> Option<Piece> {
1193        self.piece_array[sq]
1194    }
1195
1196    /// Gets a mutable reference to the piece at the given square.
1197    pub fn piece_at_mut(&mut self, sq: Square) -> &mut Option<Piece> {
1198        &mut self.piece_array[sq]
1199    }
1200
1201    pub fn make_move_simple(&mut self, m: Move) -> bool {
1202        self.make_move_base(m, &mut UpdateBuffer::default())
1203    }
1204
1205    #[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
1206    pub fn make_move_base(&mut self, m: Move, update_buffer: &mut UpdateBuffer) -> bool {
1207        #[cfg(debug_assertions)]
1208        self.check_validity().unwrap();
1209
1210        let from = m.from();
1211        let mut to = m.to();
1212        let side = self.side;
1213        let Some(piece) = self.moved_piece(m) else {
1214            return false;
1215        };
1216        let captured = self.captured_piece(m);
1217
1218        let saved_state = Undo {
1219            castle_perm: self.castle_perm,
1220            ep_square: self.ep_sq,
1221            fifty_move_counter: self.fifty_move_counter,
1222            threats: self.threats,
1223            piece_layout: self.pieces,
1224            piece_array: self.piece_array,
1225            key: self.key,
1226            pawn_key: self.pawn_key,
1227            non_pawn_key: self.non_pawn_key,
1228            minor_key: self.minor_key,
1229            major_key: self.major_key,
1230        };
1231
1232        // from, to, and piece are valid unless this is a castling move,
1233        // as castling is encoded as king-captures-rook.
1234        // we sort out castling in a branch later, dw about it.
1235        if !m.is_castle() {
1236            if m.is_promo() {
1237                // just remove the source piece, as a different piece will be arriving here
1238                update_buffer.clear_piece(from, piece);
1239            } else {
1240                update_buffer.move_piece(from, to, piece);
1241            }
1242        }
1243
1244        if m.is_ep() {
1245            let clear_at = if side == Colour::White {
1246                to.sub(8)
1247            } else {
1248                to.add(8)
1249            }
1250            .unwrap();
1251            let to_clear = Piece::new(side.flip(), PieceType::Pawn);
1252            self.pieces.clear_piece_at(clear_at, to_clear);
1253            update_buffer.clear_piece(clear_at, to_clear);
1254        } else if m.is_castle() {
1255            self.pieces.clear_piece_at(from, piece);
1256            let (rook_from, rook_to) = match to {
1257                _ if Some(to) == self.castle_perm.wk => {
1258                    to = Square::G1;
1259                    (self.castle_perm.wk.unwrap(), Square::F1)
1260                }
1261                _ if Some(to) == self.castle_perm.wq => {
1262                    to = Square::C1;
1263                    (self.castle_perm.wq.unwrap(), Square::D1)
1264                }
1265                _ if Some(to) == self.castle_perm.bk => {
1266                    to = Square::G8;
1267                    (self.castle_perm.bk.unwrap(), Square::F8)
1268                }
1269                _ if Some(to) == self.castle_perm.bq => {
1270                    to = Square::C8;
1271                    (self.castle_perm.bq.unwrap(), Square::D8)
1272                }
1273                _ => {
1274                    panic!(
1275                        "Invalid castle move, to: {}, castle_perm: {}",
1276                        to,
1277                        self.castle_perm.display(false)
1278                    );
1279                }
1280            };
1281            if from != to {
1282                update_buffer.move_piece(from, to, piece);
1283            }
1284            if rook_from != rook_to {
1285                let rook = Piece::new(side, PieceType::Rook);
1286                self.pieces.move_piece(rook_from, rook_to, rook);
1287                update_buffer.move_piece(rook_from, rook_to, rook);
1288            }
1289        }
1290
1291        self.ep_sq = None;
1292
1293        self.fifty_move_counter += 1;
1294
1295        if let Some(captured) = captured {
1296            self.fifty_move_counter = 0;
1297            self.pieces.clear_piece_at(to, captured);
1298            update_buffer.clear_piece(to, captured);
1299        }
1300
1301        if piece.piece_type() == PieceType::Pawn {
1302            self.fifty_move_counter = 0;
1303            if self.is_double_pawn_push(m)
1304                && (m.to().as_set().west_one() | m.to().as_set().east_one())
1305                    & self.pieces.all_pawns()
1306                    & self.pieces.occupied_co(side.flip())
1307                    != SquareSet::EMPTY
1308            {
1309                if side == Colour::White {
1310                    self.ep_sq = from.add(8);
1311                    debug_assert!(self.ep_sq.unwrap().rank() == Rank::Three);
1312                } else {
1313                    self.ep_sq = from.sub(8);
1314                    debug_assert!(self.ep_sq.unwrap().rank() == Rank::Six);
1315                }
1316            }
1317        }
1318
1319        if let Some(promo) = m.promotion_type() {
1320            let promo = Piece::new(side, promo);
1321            debug_assert!(promo.piece_type().legal_promo());
1322            self.pieces.clear_piece_at(from, piece);
1323            self.pieces.set_piece_at(to, promo);
1324            update_buffer.add_piece(to, promo);
1325        } else if m.is_castle() {
1326            self.pieces.set_piece_at(to, piece); // stupid hack for piece-swapping
1327        } else {
1328            self.pieces.move_piece(from, to, piece);
1329        }
1330
1331        self.side = self.side.flip();
1332
1333        // reversed in_check fn, as we have now swapped sides
1334        if self.sq_attacked(self.king_sq(self.side.flip()), self.side) {
1335            // this would be a function but we run into borrow checker issues
1336            // because it's currently not smart enough to realize that we're
1337            // borrowing disjoint parts of the board.
1338            let Undo {
1339                ep_square,
1340                fifty_move_counter,
1341                piece_layout,
1342                ..
1343            } = saved_state;
1344
1345            // self.height -= 1;
1346            // self.ply -= 1;
1347            self.side = self.side.flip();
1348            // self.key = key;
1349            // self.pawn_key = pawn_key;
1350            // self.non_pawn_key = non_pawn_key;
1351            // self.minor_key = minor_key;
1352            // self.major_key = major_key;
1353            // self.material_key = material_key;
1354            // self.castle_perm = castle_perm;
1355            self.ep_sq = ep_square;
1356            self.fifty_move_counter = fifty_move_counter;
1357            // self.threats = threats;
1358            self.pieces = piece_layout;
1359            // self.piece_array = piece_array;
1360            return false;
1361        }
1362
1363        let mut key = self.key;
1364        let mut pawn_key = self.pawn_key;
1365        let mut non_pawn_key = self.non_pawn_key;
1366        let mut minor_key = self.minor_key;
1367        let mut major_key = self.major_key;
1368
1369        // remove a previous en passant square from the hash
1370        if let Some(ep_sq) = saved_state.ep_square {
1371            hash_ep(&mut key, ep_sq);
1372        }
1373
1374        // hash out the castling to insert it again after updating rights.
1375        hash_castling(&mut key, self.castle_perm);
1376
1377        // update castling rights
1378        let mut new_rights = self.castle_perm;
1379        if piece == Piece::WR {
1380            if Some(from) == self.castle_perm.wk {
1381                new_rights.wk = None;
1382            } else if Some(from) == self.castle_perm.wq {
1383                new_rights.wq = None;
1384            }
1385        } else if piece == Piece::BR {
1386            if Some(from) == self.castle_perm.bk {
1387                new_rights.bk = None;
1388            } else if Some(from) == self.castle_perm.bq {
1389                new_rights.bq = None;
1390            }
1391        } else if piece == Piece::WK {
1392            new_rights.wk = None;
1393            new_rights.wq = None;
1394        } else if piece == Piece::BK {
1395            new_rights.bk = None;
1396            new_rights.bq = None;
1397        }
1398        new_rights.remove(to);
1399        self.castle_perm = new_rights;
1400
1401        // apply all the updates to the zobrist hash
1402        if let Some(ep_sq) = self.ep_sq {
1403            hash_ep(&mut key, ep_sq);
1404        }
1405        hash_side(&mut key);
1406        for &FeatureUpdate { sq, piece } in update_buffer.subs() {
1407            self.piece_array[sq] = None;
1408            hash_piece(&mut key, piece, sq);
1409            if piece.piece_type() == PieceType::Pawn {
1410                hash_piece(&mut pawn_key, piece, sq);
1411            } else {
1412                hash_piece(&mut non_pawn_key[piece.colour()], piece, sq);
1413                if piece.piece_type() == PieceType::King {
1414                    hash_piece(&mut major_key, piece, sq);
1415                    hash_piece(&mut minor_key, piece, sq);
1416                } else if matches!(piece.piece_type(), PieceType::Queen | PieceType::Rook) {
1417                    hash_piece(&mut major_key, piece, sq);
1418                } else {
1419                    hash_piece(&mut minor_key, piece, sq);
1420                }
1421            }
1422        }
1423        for &FeatureUpdate { sq, piece } in update_buffer.adds() {
1424            self.piece_array[sq] = Some(piece);
1425            hash_piece(&mut key, piece, sq);
1426            if piece.piece_type() == PieceType::Pawn {
1427                hash_piece(&mut pawn_key, piece, sq);
1428            } else {
1429                hash_piece(&mut non_pawn_key[piece.colour()], piece, sq);
1430                if piece.piece_type() == PieceType::King {
1431                    hash_piece(&mut major_key, piece, sq);
1432                    hash_piece(&mut minor_key, piece, sq);
1433                } else if matches!(piece.piece_type(), PieceType::Queen | PieceType::Rook) {
1434                    hash_piece(&mut major_key, piece, sq);
1435                } else {
1436                    hash_piece(&mut minor_key, piece, sq);
1437                }
1438            }
1439        }
1440        // reinsert the castling rights
1441        hash_castling(&mut key, self.castle_perm);
1442        self.key = key;
1443        self.pawn_key = pawn_key;
1444        self.non_pawn_key = non_pawn_key;
1445        self.minor_key = minor_key;
1446        self.major_key = major_key;
1447
1448        self.ply += 1;
1449        self.height += 1;
1450
1451        self.threats = self.generate_threats(self.side.flip());
1452
1453        self.history.push(saved_state);
1454
1455        #[cfg(debug_assertions)]
1456        self.check_validity().unwrap();
1457
1458        true
1459    }
1460
1461    pub fn unmake_move_base(&mut self) {
1462        // we remove this check because the board actually *can*
1463        // be in an inconsistent state when we call this, as we
1464        // may be unmaking a move that was determined to be
1465        // illegal, and as such the full make_move hasn't been
1466        // run yet.
1467        // #[cfg(debug_assertions)]
1468        // self.check_validity().unwrap();
1469
1470        let undo = self.history.last().expect("No move to unmake!");
1471
1472        let Undo {
1473            castle_perm,
1474            ep_square,
1475            fifty_move_counter,
1476            threats,
1477            piece_layout,
1478            piece_array,
1479            key,
1480            pawn_key,
1481            non_pawn_key,
1482            minor_key,
1483            major_key,
1484            ..
1485        } = undo;
1486
1487        self.height -= 1;
1488        self.ply -= 1;
1489        self.side = self.side.flip();
1490        self.key = *key;
1491        self.pawn_key = *pawn_key;
1492        self.non_pawn_key = *non_pawn_key;
1493        self.minor_key = *minor_key;
1494        self.major_key = *major_key;
1495        self.castle_perm = *castle_perm;
1496        self.ep_sq = *ep_square;
1497        self.fifty_move_counter = *fifty_move_counter;
1498        self.threats = *threats;
1499        self.pieces = *piece_layout;
1500        self.piece_array = *piece_array;
1501
1502        self.history.pop();
1503
1504        #[cfg(debug_assertions)]
1505        self.check_validity().unwrap();
1506    }
1507
1508    pub fn make_nullmove(&mut self) {
1509        #[cfg(debug_assertions)]
1510        self.check_validity().unwrap();
1511        debug_assert!(!self.in_check());
1512
1513        self.history.push(Undo {
1514            ep_square: self.ep_sq,
1515            threats: self.threats,
1516            key: self.key,
1517            ..Default::default()
1518        });
1519
1520        let mut key = self.key;
1521        if let Some(ep_sq) = self.ep_sq {
1522            hash_ep(&mut key, ep_sq);
1523        }
1524        hash_side(&mut key);
1525        self.key = key;
1526
1527        self.ep_sq = None;
1528        self.side = self.side.flip();
1529        self.ply += 1;
1530        self.height += 1;
1531
1532        self.threats = self.generate_threats(self.side.flip());
1533
1534        #[cfg(debug_assertions)]
1535        self.check_validity().unwrap();
1536    }
1537
1538    pub fn unmake_nullmove(&mut self) {
1539        #[cfg(debug_assertions)]
1540        self.check_validity().unwrap();
1541
1542        self.height -= 1;
1543        self.ply -= 1;
1544        self.side = self.side.flip();
1545
1546        let Undo {
1547            ep_square,
1548            threats,
1549            key,
1550            ..
1551        } = self.history.last().expect("No move to unmake!");
1552
1553        self.ep_sq = *ep_square;
1554        self.threats = *threats;
1555        self.key = *key;
1556
1557        self.history.pop();
1558
1559        #[cfg(debug_assertions)]
1560        self.check_validity().unwrap();
1561    }
1562
1563    pub fn last_move_was_nullmove(&self) -> bool {
1564        if let Some(Undo { piece_layout, .. }) = self.history.last() {
1565            piece_layout.all_kings().is_empty()
1566        } else {
1567            false
1568        }
1569    }
1570
1571    /// Makes a guess about the new position key after a move.
1572    /// This is a cheap estimate, and will fail for special moves such as promotions and castling.
1573    pub fn key_after(&self, m: Move) -> u64 {
1574        let src = m.from();
1575        let tgt = m.to();
1576        let piece = self.moved_piece(m).unwrap();
1577        let captured = self.piece_at(tgt);
1578
1579        let mut new_key = self.key;
1580        hash_side(&mut new_key);
1581        hash_piece(&mut new_key, piece, src);
1582        hash_piece(&mut new_key, piece, tgt);
1583
1584        if let Some(captured) = captured {
1585            hash_piece(&mut new_key, captured, tgt);
1586        }
1587
1588        new_key
1589    }
1590
1591    pub fn key_after_null_move(&self) -> u64 {
1592        let mut new_key = self.key;
1593        hash_side(&mut new_key);
1594        new_key
1595    }
1596
1597    /// Parses a move in the UCI format and returns a move or a reason why it couldn't be parsed.
1598    pub fn parse_uci(&self, uci: &str) -> anyhow::Result<Move> {
1599        let san_bytes = uci.as_bytes();
1600        if !(4..=5).contains(&san_bytes.len()) {
1601            bail!("invalid length: {}", san_bytes.len());
1602        }
1603        if !(b'a'..=b'h').contains(&san_bytes[0]) {
1604            bail!("invalid from_square file: {}", san_bytes[0] as char);
1605        }
1606        if !(b'1'..=b'8').contains(&san_bytes[1]) {
1607            bail!("invalid from_square rank: {}", san_bytes[1] as char);
1608        }
1609        if !(b'a'..=b'h').contains(&san_bytes[2]) {
1610            bail!("invalid to_square file: {}", san_bytes[2] as char);
1611        }
1612        if !(b'1'..=b'8').contains(&san_bytes[3]) {
1613            bail!("invalid to_square rank: {}", san_bytes[3] as char);
1614        }
1615        if san_bytes.len() == 5 && ![b'n', b'b', b'r', b'q', b'k'].contains(&san_bytes[4]) {
1616            bail!("invalid promotion piece: {}", san_bytes[4] as char);
1617        }
1618
1619        let from = Square::from_rank_file(
1620            Rank::from_index(san_bytes[1] - b'1').context("unknown")?,
1621            File::from_index(san_bytes[0] - b'a').context("unknown")?,
1622        );
1623        let to = Square::from_rank_file(
1624            Rank::from_index(san_bytes[3] - b'1').context("unknown")?,
1625            File::from_index(san_bytes[2] - b'a').context("unknown")?,
1626        );
1627
1628        let mut list = MoveList::new();
1629        self.generate_moves(&mut list);
1630
1631        let frc_cleanup = !self.chess960;
1632
1633        list.iter_moves()
1634            .copied()
1635            .find(|&m| {
1636                let m_to = if frc_cleanup && m.is_castle() {
1637                    // if we're in normal UCI mode, we'll rework our castling moves into the
1638                    // standard format.
1639                    match m.to() {
1640                        Square::A1 => Square::C1,
1641                        Square::H1 => Square::G1,
1642                        Square::A8 => Square::C8,
1643                        Square::H8 => Square::G8,
1644                        _ => m.to(),
1645                    }
1646                } else {
1647                    m.to()
1648                };
1649                m.from() == from
1650                    && m_to == to
1651                    && (san_bytes.len() == 4
1652                        || m.promotion_type().and_then(PieceType::promo_char).unwrap()
1653                            == san_bytes[4] as char)
1654            })
1655            .with_context(|| format!("illegal move: {}", uci))
1656    }
1657
1658    pub fn san(&mut self, m: Move) -> Option<String> {
1659        let check_char = match self.gives(m) {
1660            CheckState::None => "",
1661            CheckState::Check => "+",
1662            CheckState::Checkmate => "#",
1663        };
1664        if m.is_castle() {
1665            match () {
1666                () if m.to() > m.from() => return Some(format!("O-O{check_char}")),
1667                () if m.to() < m.from() => return Some(format!("O-O-O{check_char}")),
1668                () => unreachable!(),
1669            }
1670        }
1671        let to_sq = m.to();
1672        let moved_piece = self.piece_at(m.from())?;
1673        let is_capture = self.is_capture(m)
1674            || (moved_piece.piece_type() == PieceType::Pawn && Some(to_sq) == self.ep_sq);
1675        let piece_prefix = match moved_piece.piece_type() {
1676            PieceType::Pawn if !is_capture => "",
1677            PieceType::Pawn => &"abcdefgh"[m.from().file() as usize..=m.from().file() as usize],
1678            PieceType::Knight => "N",
1679            PieceType::Bishop => "B",
1680            PieceType::Rook => "R",
1681            PieceType::Queen => "Q",
1682            PieceType::King => "K",
1683        };
1684        let possible_ambiguous_attackers = if moved_piece.piece_type() == PieceType::Pawn {
1685            SquareSet::EMPTY
1686        } else {
1687            movegen::attacks_by_type(moved_piece.piece_type(), to_sq, self.pieces.occupied())
1688                & self.pieces.piece_bb(moved_piece)
1689        };
1690        let needs_disambiguation =
1691            possible_ambiguous_attackers.count() > 1 && moved_piece.piece_type() != PieceType::Pawn;
1692        let from_file = SquareSet::FILES[m.from().file()];
1693        let from_rank = SquareSet::RANKS[m.from().rank()];
1694        let can_be_disambiguated_by_file = (possible_ambiguous_attackers & from_file).count() == 1;
1695        let can_be_disambiguated_by_rank = (possible_ambiguous_attackers & from_rank).count() == 1;
1696        let needs_both = !can_be_disambiguated_by_file && !can_be_disambiguated_by_rank;
1697        let must_be_disambiguated_by_file = needs_both || can_be_disambiguated_by_file;
1698        let must_be_disambiguated_by_rank =
1699            needs_both || (can_be_disambiguated_by_rank && !can_be_disambiguated_by_file);
1700        let disambiguator1 = if needs_disambiguation && must_be_disambiguated_by_file {
1701            &"abcdefgh"[m.from().file() as usize..=m.from().file() as usize]
1702        } else {
1703            ""
1704        };
1705        let disambiguator2 = if needs_disambiguation && must_be_disambiguated_by_rank {
1706            &"12345678"[m.from().rank() as usize..=m.from().rank() as usize]
1707        } else {
1708            ""
1709        };
1710        let capture_sigil = if is_capture { "x" } else { "" };
1711        let promo_str = match m.promotion_type() {
1712            Some(PieceType::Knight) => "=N",
1713            Some(PieceType::Bishop) => "=B",
1714            Some(PieceType::Rook) => "=R",
1715            Some(PieceType::Queen) => "=Q",
1716            None => "",
1717            _ => unreachable!(),
1718        };
1719        let san = format!(
1720            "{piece_prefix}{disambiguator1}{disambiguator2}{capture_sigil}{to_sq}{promo_str}{check_char}"
1721        );
1722        Some(san)
1723    }
1724
1725    pub fn gives(&mut self, m: Move) -> CheckState {
1726        if !self.make_move_simple(m) {
1727            return CheckState::None;
1728        }
1729        let gives_check = self.in_check();
1730        if gives_check {
1731            let mut ml = MoveList::new();
1732            self.generate_moves(&mut ml);
1733            for &m in ml.iter_moves() {
1734                if !self.make_move_simple(m) {
1735                    continue;
1736                }
1737                // we found a legal move, so m does not give checkmate.
1738                self.unmake_move_base();
1739                self.unmake_move_base();
1740                return CheckState::Check;
1741            }
1742            // we didn't return, so there were no legal moves,
1743            // so m gives checkmate.
1744            self.unmake_move_base();
1745            return CheckState::Checkmate;
1746        }
1747        self.unmake_move_base();
1748        CheckState::None
1749    }
1750
1751    /// Has the current position occurred before in the current game?
1752    pub fn is_repetition(&self) -> bool {
1753        let mut counter = 0;
1754        // distance to the last irreversible move
1755        let moves_since_zeroing = self.fifty_move_counter() as usize;
1756        // a repetition is first possible at four ply back:
1757        for (dist_back, u) in self
1758            .history
1759            .iter()
1760            .rev()
1761            .enumerate()
1762            .take(moves_since_zeroing)
1763            .skip(3)
1764            .step_by(2)
1765        {
1766            if u.key == self.key {
1767                // in-tree, can twofold:
1768                if dist_back < self.height {
1769                    return true;
1770                }
1771                // partially materialised, proper threefold:
1772                counter += 1;
1773                if counter >= 2 {
1774                    return true;
1775                }
1776            }
1777        }
1778        false
1779    }
1780
1781    /// Should we consider the current position a draw?
1782    pub fn is_draw(&self) -> bool {
1783        (self.fifty_move_counter >= 100 || self.is_repetition()) && self.height != 0
1784    }
1785
1786    pub fn legal_moves(&mut self) -> Vec<Move> {
1787        let mut move_list = MoveList::new();
1788        self.generate_moves(&mut move_list);
1789        let mut legal_moves = Vec::new();
1790        for &m in move_list.iter_moves() {
1791            if self.make_move_simple(m) {
1792                self.unmake_move_base();
1793                legal_moves.push(m);
1794            }
1795        }
1796        legal_moves
1797    }
1798
1799    pub const fn fifty_move_counter(&self) -> u8 {
1800        self.fifty_move_counter
1801    }
1802
1803    pub fn has_insufficient_material<C: Col>(&self) -> bool {
1804        if (self.pieces.pawns::<C>() | self.pieces.rooks::<C>() | self.pieces.queens::<C>())
1805            .non_empty()
1806        {
1807            return false;
1808        }
1809
1810        if self.pieces.knights::<C>().non_empty() {
1811            // this approach renders KNNvK as *not* being insufficient material.
1812            // this is because the losing side can in theory help the winning side
1813            // into a checkmate, despite it being impossible to /force/ mate.
1814            let kings = self.pieces.all_kings();
1815            let queens = self.pieces.all_queens();
1816            return self.pieces.our_pieces::<C>().count() <= 2
1817                && (self.pieces.their_pieces::<C>() & !kings & !queens).is_empty();
1818        }
1819
1820        if self.pieces.bishops::<C>().non_empty() {
1821            let bishops = self.pieces.all_bishops();
1822            let same_color = (bishops & SquareSet::DARK_SQUARES).is_empty()
1823                || (bishops & SquareSet::LIGHT_SQUARES).is_empty();
1824            let pawns = self.pieces.all_pawns();
1825            let knights = self.pieces.all_knights();
1826            return same_color && pawns.is_empty() && knights.is_empty();
1827        }
1828
1829        true
1830    }
1831
1832    pub const fn full_move_number(&self) -> usize {
1833        self.ply / 2 + 1
1834    }
1835
1836    pub fn make_random_move(&mut self, rng: &mut ThreadRng) -> Option<Move> {
1837        let mut ml = MoveList::new();
1838        self.generate_moves(&mut ml);
1839        let self::movegen::MoveListEntry { mov, .. } = ml.choose(rng)?;
1840        self.make_move_simple(*mov);
1841        Some(*mov)
1842    }
1843
1844    pub fn is_insufficient_material(&self) -> bool {
1845        self.has_insufficient_material::<White>() && self.has_insufficient_material::<Black>()
1846    }
1847
1848    pub fn outcome(&mut self) -> GameOutcome {
1849        if self.fifty_move_counter >= 100 {
1850            return GameOutcome::Draw(DrawType::FiftyMoves);
1851        }
1852        let mut reps = 1;
1853        for undo in self.history.iter().rev().skip(1).step_by(2) {
1854            if undo.key == self.key {
1855                reps += 1;
1856                if reps == 3 {
1857                    return GameOutcome::Draw(DrawType::Repetition);
1858                }
1859            }
1860            // optimisation: if the fifty move counter was zeroed, then any prior positions will not be repetitions.
1861            if undo.fifty_move_counter == 0 {
1862                break;
1863            }
1864        }
1865        if self.is_insufficient_material() {
1866            return GameOutcome::Draw(DrawType::InsufficientMaterial);
1867        }
1868        let mut move_list = MoveList::new();
1869        self.generate_moves(&mut move_list);
1870        let mut legal_moves = false;
1871        for &m in move_list.iter_moves() {
1872            if self.make_move_simple(m) {
1873                self.unmake_move_base();
1874                legal_moves = true;
1875                break;
1876            }
1877        }
1878        if legal_moves {
1879            GameOutcome::Ongoing
1880        } else if self.in_check() {
1881            match self.side {
1882                Colour::White => GameOutcome::BlackWin(WinType::Mate),
1883                Colour::Black => GameOutcome::WhiteWin(WinType::Mate),
1884            }
1885        } else {
1886            GameOutcome::Draw(DrawType::Stalemate)
1887        }
1888    }
1889
1890    #[cfg(debug_assertions)]
1891    pub fn assert_mated(&mut self) {
1892        assert!(self.in_check());
1893        let mut move_list = MoveList::new();
1894        self.generate_moves(&mut move_list);
1895        for &mv in move_list.iter_moves() {
1896            assert!(!self.make_move_simple(mv));
1897        }
1898    }
1899
1900    #[allow(clippy::identity_op)]
1901    pub fn material_count(&self) -> u32 {
1902        let pawn_material = 1 * self.pieces.all_pawns().count();
1903        let knight_material = 3 * self.pieces.all_knights().count();
1904        let bishop_material = 3 * self.pieces.all_bishops().count();
1905        let rook_material = 5 * self.pieces.all_rooks().count();
1906        let queen_material = 9 * self.pieces.all_queens().count();
1907
1908        pawn_material + knight_material + bishop_material + rook_material + queen_material
1909    }
1910}
1911
1912#[allow(dead_code)]
1913#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1914pub enum GameOutcome {
1915    WhiteWin(WinType),
1916    BlackWin(WinType),
1917    Draw(DrawType),
1918    Ongoing,
1919}
1920
1921#[allow(dead_code)]
1922#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1923pub enum WinType {
1924    Mate,
1925    TB,
1926    Adjudication,
1927}
1928
1929#[allow(dead_code)]
1930#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1931pub enum DrawType {
1932    TB,
1933    FiftyMoves,
1934    Repetition,
1935    Stalemate,
1936    InsufficientMaterial,
1937    Adjudication,
1938}
1939
1940impl GameOutcome {
1941    pub const fn as_packed_u8(self) -> u8 {
1942        // 0 for black win, 1 for draw, 2 for white win
1943        match self {
1944            Self::WhiteWin(_) => 2,
1945            Self::BlackWin(_) => 0,
1946            Self::Draw(_) => 1,
1947            Self::Ongoing => panic!("Game is not over!"),
1948        }
1949    }
1950}
1951
1952impl Default for Board {
1953    fn default() -> Self {
1954        let mut out = Self::new();
1955        out.set_startpos(true);
1956        out
1957    }
1958}
1959
1960impl Display for Board {
1961    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1962        let mut counter = 0;
1963        for rank in Rank::all().rev() {
1964            for file in File::all() {
1965                let sq = Square::from_rank_file(rank, file);
1966                let piece = self.piece_at(sq);
1967                if let Some(piece) = piece {
1968                    if counter != 0 {
1969                        write!(f, "{counter}")?;
1970                    }
1971                    counter = 0;
1972                    write!(f, "{piece}")?;
1973                } else {
1974                    counter += 1;
1975                }
1976            }
1977            if counter != 0 {
1978                write!(f, "{counter}")?;
1979            }
1980            counter = 0;
1981            if rank != Rank::One {
1982                write!(f, "/")?;
1983            }
1984        }
1985
1986        if self.side == Colour::White {
1987            write!(f, " w")?;
1988        } else {
1989            write!(f, " b")?;
1990        }
1991        write!(f, " ")?;
1992        if self.castle_perm == CastlingRights::NONE {
1993            write!(f, "-")?;
1994        } else {
1995            for (_, ch) in [
1996                self.castle_perm.wk,
1997                self.castle_perm.wq,
1998                self.castle_perm.bk,
1999                self.castle_perm.bq,
2000            ]
2001            .into_iter()
2002            .zip("KQkq".chars())
2003            .filter(|(m, _)| m.is_some())
2004            {
2005                write!(f, "{ch}")?;
2006            }
2007        }
2008        if let Some(ep_sq) = self.ep_sq {
2009            write!(f, " {ep_sq}")?;
2010        } else {
2011            write!(f, " -")?;
2012        }
2013        write!(f, " {}", self.fifty_move_counter)?;
2014        write!(f, " {}", self.ply / 2 + 1)?;
2015
2016        Ok(())
2017    }
2018}
2019
2020impl std::fmt::UpperHex for Board {
2021    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
2022        for rank in Rank::all().rev() {
2023            write!(f, "{} ", rank as u8 + 1)?;
2024            for file in File::all() {
2025                let sq = Square::from_rank_file(rank, file);
2026                if let Some(piece) = self.piece_at(sq) {
2027                    write!(f, "{piece} ")?;
2028                } else {
2029                    write!(f, ". ")?;
2030                }
2031            }
2032            writeln!(f)?;
2033        }
2034
2035        writeln!(f, "  a b c d e f g h")?;
2036        writeln!(f, "FEN: {self}")?;
2037
2038        Ok(())
2039    }
2040}
2041
2042mod tests {
2043    #[test]
2044    fn read_fen_validity() {
2045        use super::Board;
2046
2047        let mut board_1 = Board::new();
2048        board_1
2049            .set_from_fen(Board::STARTING_FEN, true)
2050            .expect("setfen failed.");
2051        board_1.check_validity().unwrap();
2052
2053        let board_2 = Board::from_fen(Board::STARTING_FEN, true).expect("setfen failed.");
2054        board_2.check_validity().unwrap();
2055
2056        assert_eq!(board_1, board_2);
2057    }
2058
2059    #[test]
2060    fn game_end_states() {
2061        use super::Board;
2062        use super::{DrawType, GameOutcome};
2063        use crate::{chess::chessmove::Move, chess::types::Square};
2064
2065        let mut fiftymove_draw = Board::from_fen(
2066            "rnbqkb1r/pppppppp/5n2/8/3N4/8/PPPPPPPP/RNBQKB1R b KQkq - 100 2",
2067            false,
2068        )
2069        .unwrap();
2070        assert_eq!(
2071            fiftymove_draw.outcome(),
2072            GameOutcome::Draw(DrawType::FiftyMoves)
2073        );
2074        let mut draw_repetition = Board::default();
2075        assert_eq!(draw_repetition.outcome(), GameOutcome::Ongoing);
2076        draw_repetition.make_move_simple(Move::new(Square::G1, Square::F3));
2077        draw_repetition.make_move_simple(Move::new(Square::B8, Square::C6));
2078        assert_eq!(draw_repetition.outcome(), GameOutcome::Ongoing);
2079        draw_repetition.make_move_simple(Move::new(Square::F3, Square::G1));
2080        draw_repetition.make_move_simple(Move::new(Square::C6, Square::B8));
2081        assert_eq!(draw_repetition.outcome(), GameOutcome::Ongoing);
2082        draw_repetition.make_move_simple(Move::new(Square::G1, Square::F3));
2083        draw_repetition.make_move_simple(Move::new(Square::B8, Square::C6));
2084        assert_eq!(draw_repetition.outcome(), GameOutcome::Ongoing);
2085        draw_repetition.make_move_simple(Move::new(Square::F3, Square::G1));
2086        draw_repetition.make_move_simple(Move::new(Square::C6, Square::B8));
2087        assert_eq!(
2088            draw_repetition.outcome(),
2089            GameOutcome::Draw(DrawType::Repetition)
2090        );
2091        let mut stalemate = Board::from_fen("7k/8/6Q1/8/8/8/8/K7 b - - 0 1", true).unwrap();
2092        assert_eq!(stalemate.outcome(), GameOutcome::Draw(DrawType::Stalemate));
2093        let mut insufficient_material_bare_kings =
2094            Board::from_fen("8/8/5k2/8/8/2K5/8/8 b - - 0 1", true).unwrap();
2095        assert_eq!(
2096            insufficient_material_bare_kings.outcome(),
2097            GameOutcome::Draw(DrawType::InsufficientMaterial)
2098        );
2099        let mut insufficient_material_knights =
2100            Board::from_fen("8/8/5k2/8/2N5/2K2N2/8/8 b - - 0 1", true).unwrap();
2101        assert_eq!(
2102            insufficient_material_knights.outcome(),
2103            GameOutcome::Ongoing
2104        );
2105        // using FIDE rules.
2106    }
2107
2108    #[test]
2109    fn fen_round_trip() {
2110        use crate::chess::board::Board;
2111        use std::{
2112            fs::File,
2113            io::{BufRead, BufReader},
2114        };
2115
2116        let fens = BufReader::new(File::open("epds/perftsuite.epd").unwrap())
2117            .lines()
2118            .map(|l| l.unwrap().split_once(';').unwrap().0.trim().to_owned())
2119            .collect::<Vec<_>>();
2120        let mut board = Board::new();
2121        for fen in fens {
2122            board.set_from_fen(&fen, true).expect("setfen failed.");
2123            let fen_2 = board.to_string();
2124            assert_eq!(fen, fen_2);
2125        }
2126    }
2127
2128    #[test]
2129    fn scharnagl_backrank_works() {
2130        use super::Board;
2131        use crate::chess::piece::PieceType;
2132        let normal_chess_arrangement = Board::get_scharnagl_backrank(518);
2133        assert_eq!(
2134            normal_chess_arrangement,
2135            [
2136                PieceType::Rook,
2137                PieceType::Knight,
2138                PieceType::Bishop,
2139                PieceType::Queen,
2140                PieceType::King,
2141                PieceType::Bishop,
2142                PieceType::Knight,
2143                PieceType::Rook
2144            ]
2145        );
2146    }
2147
2148    #[test]
2149    fn scharnagl_full_works() {
2150        #![allow(clippy::similar_names)]
2151        use super::Board;
2152        let normal = Board::from_fen(Board::STARTING_FEN, true).unwrap();
2153        let frc = Board::from_frc_idx(518);
2154        let dfrc = Board::from_dfrc_idx(518 * 960 + 518);
2155        assert_eq!(normal, frc);
2156        assert_eq!(normal, dfrc);
2157    }
2158
2159    #[test]
2160    fn castling_pseudolegality() {
2161        use super::Board;
2162        use crate::chess::chessmove::{Move, MoveFlags};
2163        use crate::chess::types::Square;
2164        let board = Board::from_fen(
2165            "1r2k2r/2pb1pp1/2pp4/p1n5/2P4p/PP2P2P/1qB2PP1/R2QKN1R w KQk - 0 20",
2166            false,
2167        )
2168        .unwrap();
2169        let kingside_castle = Move::new_with_flags(Square::E1, Square::H1, MoveFlags::Castle);
2170        assert!(!board.is_pseudo_legal(kingside_castle));
2171    }
2172
2173    #[test]
2174    fn threat_generation_white() {
2175        use super::Board;
2176        use crate::chess::squareset::SquareSet;
2177
2178        let board = Board::from_fen("3k4/8/8/5N2/8/1P6/8/K1Q1RB2 b - - 0 1", true).unwrap();
2179        assert_eq!(
2180            board.threats.all,
2181            SquareSet::from_inner(0x1454_9d56_bddd_5f3f)
2182        );
2183    }
2184
2185    #[test]
2186    fn threat_generation_black() {
2187        use super::Board;
2188        use crate::chess::squareset::SquareSet;
2189
2190        let board = Board::from_fen("2br1q1k/8/6p1/8/2n5/8/8/4K3 w - - 0 1", true).unwrap();
2191        assert_eq!(
2192            board.threats.all,
2193            SquareSet::from_inner(0xfcfa_bbbd_6ab9_2a28)
2194        );
2195    }
2196
2197    #[test]
2198    fn key_after_works_for_simple_moves() {
2199        use super::Board;
2200        use crate::chess::chessmove::Move;
2201        use crate::chess::types::Square;
2202        let mut board = Board::default();
2203        let mv = Move::new(Square::E2, Square::E3);
2204        let key = board.key_after(mv);
2205        board.make_move_simple(mv);
2206        assert_eq!(board.key, key);
2207    }
2208
2209    #[test]
2210    fn key_after_works_for_captures() {
2211        use super::Board;
2212        use crate::chess::chessmove::Move;
2213        use crate::chess::types::Square;
2214        let mut board = Board::from_fen(
2215            "r1bqkb1r/ppp2ppp/2n5/3np1N1/2B5/8/PPPP1PPP/RNBQK2R w KQkq - 0 6",
2216            false,
2217        )
2218        .unwrap();
2219        // Nxf7!!
2220        let mv = Move::new(Square::G5, Square::F7);
2221        let key = board.key_after(mv);
2222        board.make_move_simple(mv);
2223        assert_eq!(board.key, key);
2224    }
2225
2226    #[test]
2227    fn key_after_works_for_nullmove() {
2228        use super::Board;
2229        let mut board = Board::default();
2230        let key = board.key_after_null_move();
2231        board.make_nullmove();
2232        assert_eq!(board.key, key);
2233    }
2234
2235    #[test]
2236    fn ep_square_edge_case() {
2237        use super::Board;
2238        use crate::chess::chessmove::Move;
2239        use crate::chess::piece::Piece;
2240        use crate::chess::types::Square;
2241        use crate::makemove::{hash_ep, hash_piece, hash_side};
2242        let mut not_ep_capturable = Board::from_fen(
2243            "rnbqkbnr/ppppp1pp/8/5p2/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2",
2244            false,
2245        )
2246        .unwrap();
2247        let mut ep_capturable = Board::from_fen(
2248            "rnbqkbnr/ppppp1pp/8/4Pp2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 2",
2249            false,
2250        )
2251        .unwrap();
2252        let d5 = Move::new(Square::D7, Square::D5);
2253        let mut not_ep_capturable_key = not_ep_capturable.key;
2254        let mut ep_capturable_key = ep_capturable.key;
2255        hash_side(&mut not_ep_capturable_key);
2256        hash_side(&mut ep_capturable_key);
2257        hash_piece(&mut not_ep_capturable_key, Piece::BP, Square::D7);
2258        hash_piece(&mut ep_capturable_key, Piece::BP, Square::D7);
2259        hash_piece(&mut not_ep_capturable_key, Piece::BP, Square::D5);
2260        hash_piece(&mut ep_capturable_key, Piece::BP, Square::D5);
2261
2262        hash_ep(&mut ep_capturable_key, Square::D6);
2263
2264        assert!(not_ep_capturable.make_move_simple(d5));
2265        assert!(ep_capturable.make_move_simple(d5));
2266
2267        assert_eq!(not_ep_capturable.ep_sq, None);
2268        assert_eq!(ep_capturable.ep_sq, Some(Square::D6));
2269
2270        assert_eq!(not_ep_capturable.key, not_ep_capturable_key);
2271        assert_eq!(ep_capturable.key, ep_capturable_key);
2272    }
2273
2274    #[test]
2275    fn other_ep_edge_case() {
2276        use super::Board;
2277        use crate::chess::chessmove::Move;
2278        use crate::chess::types::Square;
2279        let mut board = Board::from_fen(
2280            "rnbqkbnr/1ppppppp/p7/P7/8/8/1PPPPPPP/RNBQKBNR b KQkq - 0 2",
2281            false,
2282        )
2283        .unwrap();
2284        assert!(board.make_move_simple(Move::new(Square::B7, Square::B5)));
2285        assert_eq!(board.ep_sq, Some(Square::B6));
2286    }
2287}