Skip to main content

hieu_chess/
chess.rs

1use crate::board::{Board, SIZE_0X88};
2use crate::constants::{ATTACKS, COLOR_MASK, KNIGHT_DELTAS, QUEEN_DELTAS};
3use crate::error::Error;
4use crate::move_gen::MoveGen;
5use crate::piece::{PType, Piece};
6use crate::square::Square;
7use crate::utils;
8use std::cmp;
9use std::collections::{HashMap, HashSet};
10
11#[derive(Clone, Copy, PartialEq, Debug)]
12#[repr(u8)]
13pub enum Color {
14    WHITE = 0,
15    BLACK = 128,
16}
17
18impl TryFrom<&str> for Color {
19    type Error = Error;
20    fn try_from(value: &str) -> Result<Self, Self::Error> {
21        match value {
22            "w" => Ok(Color::WHITE),
23            "b" => Ok(Color::BLACK),
24            _ => Err(Error::InvalidColor),
25        }
26    }
27}
28
29impl From<Color> for &'static str {
30    fn from(color: Color) -> &'static str {
31        match color {
32            Color::WHITE => "w",
33            Color::BLACK => "b",
34        }
35    }
36}
37
38#[derive(Clone, PartialEq, Debug)]
39pub struct Move {
40    pub from: Square,
41    pub to: Square,
42    pub promotion_piece: Option<Piece>,
43}
44
45// TODO: implement algebraic notation for moves
46impl Move {
47    pub fn new(from: Square, to: Square, promotion_piece: Option<Piece>) -> Self {
48        Self {
49            from,
50            to,
51            promotion_piece,
52        }
53    }
54
55    pub fn from_str(from: &str, to: &str, promotion_piece: Option<Piece>) -> Self {
56        Self {
57            from: from.try_into().unwrap(),
58            to: to.try_into().unwrap(),
59            promotion_piece,
60        }
61    }
62}
63
64#[derive(Default, Clone)]
65struct CastlingRights {
66    white_kingside: bool,
67    white_queenside: bool,
68    black_kingside: bool,
69    black_queenside: bool,
70}
71
72struct HistoryEntry {
73    m: Move,
74    piece: Piece,
75    castling_rights: CastlingRights,
76    en_passant_sq: Option<Square>,
77    half_moves: u8,
78    full_moves: u8,
79    is_queenside_castle: bool,
80    is_kingside_castle: bool,
81    has_moved: bool,
82    unique_positions: HashMap<String, u8>,
83    side_to_move: Color,
84    fen: String,
85    is_capture: bool,
86    check_rays: HashSet<Square>,
87}
88
89pub struct Capture {
90    sq: Square,
91    piece: Piece,
92}
93
94pub struct GameState {
95    pub board: Board,
96    pub en_passant_sq: Option<Square>,
97    pub side_to_move: Color,
98    pub is_in_check: bool,
99    pub white_king_square: Option<Square>,
100    pub black_king_square: Option<Square>,
101    check_rays: HashSet<Square>,
102    castling_rights: CastlingRights,
103
104    pub captures: Vec<Capture>,
105    unique_positions: HashMap<String, u8>,
106
107    half_moves: u8,
108    full_moves: u8,
109    debug: bool,
110
111    history: Vec<HistoryEntry>,
112
113    has_moved: bool,
114    captures_count: usize,
115    castle_count: usize,
116    check_count: usize,
117}
118
119impl GameState {
120    pub fn new() -> Self {
121        Self {
122            side_to_move: Color::WHITE,
123            board: Board::new(),
124            en_passant_sq: None,
125            is_in_check: false,
126            check_rays: HashSet::new(),
127            white_king_square: None,
128            black_king_square: None,
129            castling_rights: CastlingRights::default(),
130            captures: vec![],
131            debug: false,
132            full_moves: 0,
133            half_moves: 0,
134            unique_positions: HashMap::new(),
135            has_moved: false,
136            captures_count: 0,
137            castle_count: 0,
138            check_count: 0,
139
140            history: vec![],
141        }
142    }
143
144    pub fn play_move(&mut self, m: Move) -> Result<(), Error> {
145        if !self.is_occupied(&m.from) {
146            return Err(Error::UnknownMove);
147        }
148
149        if self
150            .board
151            .get(&m.from)
152            .expect("piece must be present after the is_occupied check")
153            .color
154            != self.side_to_move
155        {
156            return Err(Error::MustWaitForTurn);
157        }
158
159        // TODO remove this later
160        let legal_moves = MoveGen::moves_for_square(self, m.from);
161        if !legal_moves.contains(&m) || legal_moves.len() == 0 {
162            return Err(Error::IllegalMove);
163        }
164
165        self.make_move(m);
166        self.change_turn();
167        self.update_king_attacks();
168
169        Ok(())
170    }
171
172    // this method assumes all moves are valid, and there must be a piece being moved
173    pub fn make_move(&mut self, m: Move) {
174        let piece = self
175            .board
176            .get(&m.from)
177            .expect("a piece must be present in make_move")
178            .clone();
179
180        let mut is_capture = false;
181        if self.is_capture(&m) {
182            if self.is_enpassant_capture(&m) {
183                let delta: i8 = match piece.color {
184                    Color::WHITE => -16,
185                    Color::BLACK => 16,
186                };
187                let to =
188                    m.to.add(delta)
189                        .expect("en passant square must be valid here");
190
191                self.board.remove(&to);
192
193                self.captures.push(Capture {
194                    sq: to,
195                    piece: Piece::new(
196                        PType::PAWN,
197                        if piece.color == Color::WHITE {
198                            Color::BLACK
199                        } else {
200                            Color::WHITE
201                        },
202                    ),
203                });
204            } else {
205                self.captures.push(Capture {
206                    sq: m.to.clone(),
207                    piece: self
208                        .board
209                        .get(&m.to)
210                        .expect("capture must have a captured piece")
211                        .clone(),
212                });
213            }
214            is_capture = true;
215        }
216
217        // TODO: refactor this
218        let mut is_queenside_castle = false;
219        let mut is_kingside_castle = false;
220        if self.board.get(&m.from).unwrap().p_type == PType::KING {
221            if self.is_castling_kingside(&m) {
222                is_kingside_castle = true;
223                // remove the rook
224                self.board
225                    .remove(&m.to.add(1).expect("castling square must be valid"));
226                // put a new rook to the left
227                self.set(
228                    Piece::new(PType::ROOK, piece.color),
229                    &m.to.add(-1).expect("castling square must be valid"),
230                );
231                self.castle_count += 1;
232            } else if self.is_castling_queenside(&m) {
233                is_queenside_castle = true;
234                // remove the rook
235                self.board
236                    .remove(&m.to.add(-2).expect("castling square must be valid"));
237                // put a new rook to the right
238                self.set(
239                    Piece::new(PType::ROOK, piece.color),
240                    &m.to.add(1).expect("castling square must be valid"),
241                );
242                self.castle_count += 1;
243            }
244        }
245
246        // TODO: we can do better
247        self.history.push(HistoryEntry {
248            m: m.clone(),
249            piece: piece.clone(),
250            castling_rights: self.castling_rights.clone(),
251            full_moves: self.full_moves,
252            half_moves: self.half_moves,
253            en_passant_sq: self.en_passant_sq,
254            is_kingside_castle,
255            is_queenside_castle,
256            has_moved: self.has_moved,
257            unique_positions: self.unique_positions.clone(),
258            side_to_move: self.side_to_move,
259            fen: self.get_fen(),
260            is_capture,
261            check_rays: self.check_rays.clone(),
262        });
263
264        if self.is_enpassant_move(&m) {
265            let delta: i8 = match piece.color {
266                Color::WHITE => -16,
267                Color::BLACK => 16,
268            };
269
270            self.en_passant_sq = Some(m.to.add(delta).expect("en passant square must be valid"));
271        } else {
272            self.en_passant_sq = None;
273        }
274
275        self.has_moved = true;
276        self.full_moves += 1;
277
278        if piece.p_type == PType::PAWN || self.is_capture(&m) {
279            self.half_moves = 0;
280        } else {
281            self.half_moves += 1;
282        }
283
284        if let Some(ref promo_piece) = m.promotion_piece {
285            self.set(promo_piece.clone(), &m.to);
286        } else {
287            self.set(piece.clone(), &m.to);
288        }
289
290        self.board.remove(&m.from);
291        self.update_castling_rights(&piece, &m);
292        self.update_positions();
293    }
294
295    pub fn undo(&mut self) {
296        if let Some(entry) = self.history.pop() {
297            let piece = entry.piece;
298
299            self.set(piece.clone(), &entry.m.from);
300            self.board.remove(&entry.m.to);
301
302            if entry.is_kingside_castle {
303                // remove the rook
304                self.board.remove(&entry.m.from.add(1).unwrap());
305                self.set(
306                    Piece::new(PType::ROOK, piece.color),
307                    &entry.m.from.add(3).unwrap(),
308                );
309            } else if entry.is_queenside_castle {
310                self.board.remove(&entry.m.from.add(-1).unwrap());
311                self.set(
312                    Piece::new(PType::ROOK, piece.color),
313                    &entry.m.from.add(-4).unwrap(),
314                );
315            }
316
317            self.full_moves = entry.full_moves;
318            self.half_moves = entry.half_moves;
319            self.has_moved = entry.has_moved;
320            self.unique_positions = entry.unique_positions;
321            self.side_to_move = entry.side_to_move;
322            self.castling_rights = entry.castling_rights;
323            self.en_passant_sq = entry.en_passant_sq;
324            self.check_rays = entry.check_rays;
325            self.is_in_check = self.check_rays.len() > 0;
326
327            if entry.is_capture {
328                let c = self.captures.pop().expect("capture MUST be in history");
329                self.board.set(c.piece, &c.sq);
330            }
331        };
332    }
333
334    fn update_king_attacks(&mut self) {
335        let king_sq: Square = self.get_current_king_sq().expect("no king to check");
336
337        // *self.check_count.borrow_mut() = 0;
338        self.check_rays = self.get_attack_rays(king_sq);
339        self.is_in_check = self.check_rays.len() > 0;
340    }
341
342    fn get_attack_rays(&self, sq: Square) -> HashSet<Square> {
343        #[allow(non_snake_case)]
344        let SLIDING_ATTACK_DELTAS = QUEEN_DELTAS;
345        let mut temp: Square;
346        let mut attack_rays: HashSet<Square> = HashSet::new();
347
348        for delta in SLIDING_ATTACK_DELTAS {
349            let mut attacker_present = false;
350            let mut visting_squares: HashSet<Square> = HashSet::new();
351            temp = sq;
352
353            while let Ok(to_sq) = temp.add(*delta) {
354                let Some(piece) = self.board.get(&to_sq) else {
355                    temp = to_sq;
356                    visting_squares.insert(to_sq);
357                    continue;
358                };
359
360                if piece.color == self.side_to_move {
361                    break; // stop looking in this direction if a friendly piece is blocking
362                }
363
364                let diff = to_sq.0 as i16 - sq.0 as i16 + 119;
365                let attack_mask = ATTACKS[diff as usize];
366
367                if attack_mask == 0 {
368                    break; // this piece can't attack that direction, so we stop looking that direction
369                }
370
371                if piece.p_type == PType::PAWN {
372                    let piece_bits = (piece.p_type as u8) | (piece.color as u8);
373
374                    if (piece_bits & attack_mask) != 0
375                        && (piece.color as u8) == (attack_mask & COLOR_MASK)
376                    {
377                        attacker_present = true;
378                        visting_squares.insert(to_sq);
379                    }
380                } else {
381                    if ((piece.p_type as u8) & attack_mask) != 0 {
382                        attacker_present = true;
383                        visting_squares.insert(to_sq);
384                    }
385                }
386
387                break;
388            }
389
390            if attacker_present {
391                attack_rays.extend(&visting_squares);
392            }
393        }
394
395        // check for knight attacks
396        for delta in KNIGHT_DELTAS {
397            if let Ok(to_sq) = sq.add(*delta) {
398                let Some(piece) = self.board.get(&to_sq) else {
399                    continue;
400                };
401
402                if piece.p_type == PType::KNIGHT && piece.color != self.side_to_move {
403                    attack_rays.insert(to_sq);
404                }
405            }
406        }
407
408        return attack_rays;
409    }
410
411    pub fn is_attacked(&self, sq: Square) -> bool {
412        return self.get_attack_rays(sq).len() > 0;
413    }
414
415    pub fn get_current_king_sq(&self) -> Option<Square> {
416        if self.side_to_move == Color::WHITE {
417            return self.white_king_square;
418        } else {
419            return self.black_king_square;
420        }
421    }
422
423    pub fn get_castling_rights(&self) -> (bool, bool) {
424        if self.side_to_move == Color::WHITE {
425            return (
426                self.castling_rights.white_kingside,
427                self.castling_rights.white_queenside,
428            );
429        } else {
430            return (
431                self.castling_rights.black_kingside,
432                self.castling_rights.black_queenside,
433            );
434        }
435    }
436
437    fn update_castling_rights(&mut self, piece: &Piece, m: &Move) {
438        let a1: Square = "a1".try_into().unwrap();
439        let h1: Square = "h1".try_into().unwrap();
440        let a8: Square = "a8".try_into().unwrap();
441        let h8: Square = "h8".try_into().unwrap();
442
443        if self.board.get(&a1) != Some(&Piece::new(PType::ROOK, Color::WHITE)) {
444            self.castling_rights.white_queenside = false;
445        }
446        if self.board.get(&h1) != Some(&Piece::new(PType::ROOK, Color::WHITE)) {
447            self.castling_rights.white_kingside = false;
448        }
449        if self.board.get(&a8) != Some(&Piece::new(PType::ROOK, Color::BLACK)) {
450            self.castling_rights.black_queenside = false;
451        }
452        if self.board.get(&h8) != Some(&Piece::new(PType::ROOK, Color::BLACK)) {
453            self.castling_rights.black_kingside = false;
454        }
455
456        if piece.p_type == PType::KING {
457            if piece.color == Color::WHITE {
458                self.castling_rights.white_kingside = false;
459                self.castling_rights.white_queenside = false;
460            } else {
461                self.castling_rights.black_kingside = false;
462                self.castling_rights.black_queenside = false;
463            }
464
465            return;
466        }
467
468        if piece.p_type != PType::ROOK {
469            return;
470        }
471
472        let kingside_rook_sq = match piece.color {
473            Color::WHITE => "h1",
474            Color::BLACK => "h8",
475        };
476
477        let queenside_rook_sq = match piece.color {
478            Color::WHITE => "a1",
479            Color::BLACK => "a8",
480        };
481
482        if m.from.get_notation() == kingside_rook_sq {
483            if piece.color == Color::WHITE {
484                self.castling_rights.white_kingside = false;
485            } else {
486                self.castling_rights.black_kingside = false;
487            }
488        } else if m.from.get_notation() == queenside_rook_sq {
489            if piece.color == Color::WHITE {
490                self.castling_rights.white_queenside = false;
491            } else {
492                self.castling_rights.black_queenside = false;
493            }
494        }
495    }
496
497    fn update_king_sq(&mut self, sq: Square, color: Color) {
498        if color == Color::WHITE {
499            self.white_king_square = Some(sq);
500        } else {
501            self.black_king_square = Some(sq);
502        }
503    }
504
505    fn update_positions(&mut self) {
506        let fen = self.get_fen();
507        let fen_parts: Vec<&str> = fen.split(" ").collect();
508
509        *self
510            .unique_positions
511            .entry(fen_parts[0].to_string())
512            .or_insert(0) += 1;
513    }
514
515    fn change_turn(&mut self) {
516        if self.side_to_move == Color::WHITE {
517            self.side_to_move = Color::BLACK;
518        } else {
519            self.side_to_move = Color::WHITE;
520        }
521    }
522
523    pub fn reset(&mut self) {
524        self.side_to_move = Color::WHITE;
525        self.board = Board::new();
526        self.en_passant_sq = None;
527        self.is_in_check = false;
528        self.check_rays = HashSet::new();
529        self.white_king_square = None;
530        self.black_king_square = None;
531        self.castling_rights = CastlingRights::default();
532        self.captures = vec![];
533        self.debug = false;
534        self.full_moves = 0;
535        self.half_moves = 0;
536        self.unique_positions = HashMap::new();
537        self.has_moved = false;
538    }
539
540    fn is_draw(&mut self) -> bool {
541        self.is_stalemate()
542            || self.is_threefold_repetition()
543            || self.is_50_moves()
544            || self.is_insufficient_material()
545    }
546
547    fn is_stalemate(&mut self) -> bool {
548        !self.is_in_check && MoveGen::moves(self, self.side_to_move).len() == 0
549    }
550
551    // https://www.chess.com/article/view/how-chess-games-can-end-8-ways-explained#insufficient-material
552    // TODO: i think we can do better
553    fn is_insufficient_material(&self) -> bool {
554        let mut w_knights = 0;
555        let mut b_knights = 0;
556        let mut w_bishops = 0;
557        let mut b_bishops = 0;
558        let mut w_light_square_bishops = 0;
559        let mut w_dark_square_bishops = 0;
560        let mut b_light_square_bishops = 0;
561        let mut b_dark_square_bishops = 0;
562        let mut knights = 0;
563        let mut bishops = 0;
564
565        for idx in 0..SIZE_0X88 {
566            let idx = idx as u8;
567
568            if utils::is_valid_idx(idx).is_err() {
569                continue;
570            }
571
572            let Some(piece) = self.board.get(&idx) else {
573                continue;
574            };
575
576            if piece.p_type == PType::PAWN
577                || piece.p_type == PType::ROOK
578                || piece.p_type == PType::QUEEN
579            {
580                return false;
581            }
582
583            let square: Square = idx.try_into().unwrap();
584
585            if piece.color == Color::WHITE {
586                if piece.p_type == PType::KNIGHT {
587                    w_knights += 1;
588                    knights += 1;
589                }
590
591                if piece.p_type == PType::BISHOP {
592                    if square.color() == Color::WHITE {
593                        w_light_square_bishops += 1;
594                    } else {
595                        w_dark_square_bishops += 1;
596                    }
597
598                    w_bishops += 1;
599                    bishops += 1;
600                }
601            } else {
602                if piece.p_type == PType::KNIGHT {
603                    b_knights += 1;
604                    knights += 1;
605                }
606
607                if piece.p_type == PType::BISHOP {
608                    if square.color() == Color::WHITE {
609                        b_light_square_bishops += 1;
610                    } else {
611                        b_dark_square_bishops += 1;
612                    }
613
614                    b_bishops += 1;
615                    bishops += 1;
616                }
617            }
618        }
619
620        // king vs king
621        if knights == 0 && bishops == 0 {
622            return true;
623        }
624
625        // king + minor piece vs king
626        if (knights == 1 && bishops == 0) || (bishops == 1 && knights == 0) {
627            return true;
628        }
629
630        // king + two knights vs king
631        // if (w_knights == 2 && bishops == 0) || (b_knights == 2 && bishops == 0) {
632        //     return true;
633        // }
634
635        // king + minor piece vs king + minor piece (minor piece is different kind)
636        // if (w_knights == 1 && b_bishops == 1) || (w_bishops == 1 && b_knights == 1) {
637        //     return true;
638        // }
639
640        // king + bishop vs king + bishop (same color bishops)
641        if (w_dark_square_bishops == 1 && b_dark_square_bishops == 1)
642            || (w_light_square_bishops == 1 && b_light_square_bishops == 1)
643        {
644            return true;
645        }
646
647        if w_dark_square_bishops != 0 && b_dark_square_bishops != 0 && knights == 0 {
648            return true;
649        }
650
651        if w_light_square_bishops != 0 && b_light_square_bishops != 0 && knights == 0 {
652            return true;
653        }
654
655        false
656    }
657
658    fn is_50_moves(&self) -> bool {
659        self.half_moves >= 100
660    }
661
662    // this may not work as intended
663    fn is_threefold_repetition(&self) -> bool {
664        for (_, position_count) in &self.unique_positions {
665            if *position_count >= 3 {
666                return true;
667            }
668        }
669
670        false
671    }
672
673    fn is_checkmate(&mut self) -> bool {
674        self.is_in_check && MoveGen::moves(self, self.side_to_move).len() == 0
675    }
676
677    pub fn is_occupied(&self, sq: &Square) -> bool {
678        return self.board.get(sq).is_some();
679    }
680
681    fn is_friendly(&self, piece: &Piece) -> bool {
682        return piece.color == self.side_to_move;
683    }
684
685    fn is_enpassant_capture(&self, m: &Move) -> bool {
686        self.board.get(&m.from).unwrap().p_type == PType::PAWN && Some(m.to) == self.en_passant_sq
687    }
688
689    fn is_enpassant_move(&self, m: &Move) -> bool {
690        let p = self.board.get(&m.from).unwrap();
691
692        if p.p_type != PType::PAWN {
693            return false;
694        }
695
696        if (m.to.0 as i16 - m.from.0 as i16).abs() != 32 {
697            return false;
698        }
699
700        for d in &[1, -1] {
701            if let Ok(to) = m.to.add(*d) {
702                if let Some(piece) = self.board.get(&to) {
703                    if piece.p_type == PType::PAWN && piece.color != p.color {
704                        return true;
705                    }
706                }
707            }
708        }
709
710        return false;
711    }
712
713    fn is_capture(&self, m: &Move) -> bool {
714        let Some(p) = self.board.get(&m.to) else {
715            let a = self.is_enpassant_capture(m);
716            return a;
717        };
718        p.color != self.side_to_move
719    }
720
721    pub fn is_castling(&self, m: &Move) -> bool {
722        (m.to.0 as i16 - m.from.0 as i16).abs() == 2
723        // let Some(king) = self.board.get(&m.from) else {
724        //     return false;
725        // };
726
727        // if king.color == Color::WHITE {
728        //     return m.from == "e1".try_into().unwrap()
729        //         && (m.to == "g1".try_into().unwrap() || m.to == "c1".try_into().unwrap());
730        // } else {
731        //     return m.from == "e8".try_into().unwrap()
732        //         && (m.to == "g8".try_into().unwrap() || m.to == "c8".try_into().unwrap());
733        // }
734    }
735
736    pub fn is_castling_kingside(&self, m: &Move) -> bool {
737        let a = m.from.get_notation();
738        (a == "e1" || a == "e8") && (m.to.0 as i16 - m.from.0 as i16) == 2
739    }
740
741    pub fn is_castling_queenside(&self, m: &Move) -> bool {
742        let a = m.from.get_notation();
743        (a == "e1" || a == "e8") && (m.to.0 as i16 - m.from.0 as i16) == -2
744    }
745
746    fn set(&mut self, piece: Piece, sq: &Square) {
747        if piece.p_type == PType::KING {
748            self.update_king_sq(sq.clone(), piece.color);
749        }
750        self.board.set(piece, sq);
751    }
752
753    // TODO: handle error while parsing FEN, full moves
754    pub fn load_fen(&mut self, fen: &str) {
755        let fen_parts: Vec<&str> = fen.split(" ").collect();
756
757        let mut ranks: Vec<&str> = fen_parts[0].split("/").collect();
758        ranks.reverse();
759
760        for rank_idx in 0..ranks.len() {
761            let mut file_idx: u8 = 0;
762
763            for c in ranks[rank_idx].chars() {
764                let sq = Square::new((rank_idx as u8).into(), file_idx.into());
765
766                match c {
767                    '1'..='8' => {
768                        file_idx += (c.to_digit(10).unwrap() as usize - 1) as u8;
769                    }
770                    _ => {
771                        self.set(
772                            c.to_string()
773                                .as_str()
774                                .try_into()
775                                .expect("invalid FEN characters"),
776                            &sq,
777                        );
778                    }
779                };
780
781                file_idx += 1;
782            }
783        }
784
785        //set turn
786        match fen_parts[1] {
787            "w" => self.side_to_move = Color::WHITE,
788            "b" => self.side_to_move = Color::BLACK,
789            _ => panic!("invalid FEN turn"),
790        }
791
792        //castling rights
793        for castling_right in fen_parts[2].chars() {
794            match castling_right {
795                'K' => {
796                    self.castling_rights.white_kingside = true;
797                }
798                'Q' => {
799                    self.castling_rights.white_queenside = true;
800                }
801                'k' => {
802                    self.castling_rights.black_kingside = true;
803                }
804                'q' => {
805                    self.castling_rights.black_queenside = true;
806                }
807
808                '-' => {
809                    self.castling_rights.white_kingside = false;
810                    self.castling_rights.white_queenside = false;
811                    self.castling_rights.black_kingside = false;
812                    self.castling_rights.black_kingside = false;
813                }
814                _ => panic!("cant load fen castling rights"),
815            }
816        }
817
818        // en passant square
819        let en_passant_square = fen_parts[3];
820
821        match en_passant_square {
822            "-" => {
823                self.en_passant_sq = None;
824            }
825            _ => {
826                self.en_passant_sq = Some(
827                    en_passant_square
828                        .try_into()
829                        .expect("invalid en passant square in FEN"),
830                );
831            }
832        }
833
834        self.half_moves = fen_parts[4].parse().expect("can't parse FEN half moves");
835        self.full_moves = fen_parts[5].parse().expect("can't parse FEN full moves");
836
837        *self
838            .unique_positions
839            .entry(fen_parts[0].to_string())
840            .or_insert(0) += 1;
841
842        self.update_king_attacks();
843    }
844
845    // TODO refactor this
846    pub fn get_fen(&self) -> String {
847        let mut empty_count: u8 = 0;
848
849        let mut rank = String::new();
850        let mut ranks: Vec<String> = vec![];
851
852        for idx in 0..128 {
853            if utils::is_valid_idx(idx).is_err() {
854                continue;
855            }
856
857            if let Some(piece) = self.board.get(&idx) {
858                if empty_count != 0 {
859                    rank.push_str(empty_count.to_string().as_str());
860                    empty_count = 0;
861                }
862
863                let p: String = piece.clone().into();
864                rank.push_str(&p);
865            } else {
866                empty_count += 1;
867            }
868
869            if (idx + 1) % 8 == 0 {
870                if empty_count != 0 {
871                    rank.push_str(empty_count.to_string().as_str());
872                    empty_count = 0;
873                }
874
875                ranks.push(rank);
876                rank = String::new();
877            }
878        }
879
880        ranks.reverse();
881        let en_passant_sq = if let Some(sq) = self.en_passant_sq {
882            sq.get_notation()
883        } else {
884            "-".to_string()
885        };
886
887        let half_moves = self.half_moves;
888        let full_moves = if self.has_moved {
889            cmp::max(1, self.full_moves - 1)
890        } else {
891            self.full_moves
892        };
893
894        let mut castling_rights = String::new();
895        if self.castling_rights.white_kingside {
896            castling_rights.push_str("K");
897        }
898
899        if self.castling_rights.white_queenside {
900            castling_rights.push_str("Q");
901        }
902
903        if self.castling_rights.black_kingside {
904            castling_rights.push_str("k");
905        }
906
907        if self.castling_rights.black_queenside {
908            castling_rights.push_str("q")
909        }
910
911        if castling_rights.is_empty() {
912            castling_rights.push_str("-");
913        }
914
915        let turn: &str = self.side_to_move.into();
916
917        // TODO: use format!
918        vec![
919            ranks.join("/"),
920            turn.to_string(),
921            castling_rights,
922            en_passant_sq,
923            half_moves.to_string(),
924            full_moves.to_string(),
925        ]
926        .join(" ")
927    }
928
929    pub fn get_boar(&self) -> Board {
930        self.board.clone()
931    }
932
933    pub fn perft(&mut self, depth: u8, log: bool) -> usize {
934        let mut nodes = 0;
935        let mut count = 0;
936
937        if depth <= 0 {
938            return 1;
939        }
940
941        let moves = MoveGen::moves(self, self.side_to_move);
942
943        for _move in moves {
944            let from = _move.from.get_notation();
945            let to = _move.to.get_notation();
946
947            match self.play_move(_move.clone()) {
948                Ok(_) => {}
949                Err(e) => {
950                    panic!("{}", e)
951                }
952            }
953            // if self.is_capture(&_move) && depth == 1 {
954            //     self.captures_count += 1;
955            // }
956            // if self.is_in_check && depth == 1 {
957            //     self.check_count += 1;
958            // }
959            count = self.perft(depth - 1, false);
960
961            nodes += count;
962
963            if log {
964                if let Some(promotion_piece) = _move.promotion_piece {
965                    match promotion_piece.p_type {
966                        PType::BISHOP => {
967                            println!("{} {}", format!("{}{}b", from, to), count);
968                        }
969                        PType::KNIGHT => {
970                            println!("{} {}", format!("{}{}n", from, to), count);
971                        }
972                        PType::ROOK => {
973                            println!("{} {}", format!("{}{}r", from, to), count);
974                        }
975                        PType::QUEEN => {
976                            println!("{} {}", format!("{}{}q", from, to), count);
977                        }
978                        _ => panic!("invalid promotion piece"),
979                    }
980                } else {
981                    println!("{} {}", format!("{}{}", from, to), count);
982                }
983            }
984
985            self.undo();
986        }
987
988        return nodes;
989    }
990}
991
992pub struct Chess {
993    state: GameState,
994}
995
996impl Chess {
997    pub fn new() -> Self {
998        Self {
999            state: GameState::new(),
1000        }
1001    }
1002
1003    pub fn play_move(&mut self, m: Move) -> Result<(), Error> {
1004        Ok(self.state.play_move(m)?)
1005    }
1006
1007    pub fn load_fen(&mut self, fen: &str) -> Result<(), Error> {
1008        self.state.load_fen(fen);
1009
1010        Ok(())
1011    }
1012
1013    pub fn get_fen(&self) -> String {
1014        self.state.get_fen()
1015    }
1016
1017    pub fn get_turn(&self) -> Color {
1018        self.state.side_to_move
1019    }
1020
1021    pub fn set_turn(&mut self, color: Color) {
1022        self.state.side_to_move = color;
1023    }
1024
1025    pub fn get_board_ptr(&self) -> *const Option<Piece> {
1026        self.state.board.get_board_ptr()
1027    }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032    use super::*;
1033    use crate::piece::*;
1034    use crate::square::*;
1035
1036    #[test]
1037    fn in_check() {
1038        let mut state = GameState::new();
1039        let white_king_sq = Square::new(Rank::Five, File::C);
1040        let attacker_sq = Square::new(Rank::Three, File::E);
1041        let attacker2_sq = Square::new(Rank::Eight, File::F);
1042
1043        let king = Piece::new(PType::KING, Color::WHITE);
1044        let attacker = Piece::new(PType::QUEEN, Color::BLACK);
1045        let attacker2 = Piece::new(PType::BISHOP, Color::BLACK);
1046
1047        state.set(king, &white_king_sq);
1048        state.set(attacker, &attacker_sq);
1049        state.set(attacker2, &attacker2_sq);
1050        state.set(
1051            Piece::new(PType::PAWN, Color::BLACK),
1052            &Square::new(Rank::Six, File::B),
1053        );
1054
1055        state.update_king_attacks();
1056
1057        assert!(state.is_in_check);
1058
1059        let mut squares: Vec<String> = state.check_rays.iter().map(|m| m.get_notation()).collect();
1060
1061        squares.sort();
1062        let mut expected = ["d6", "e7", "f8", "b6", "d4", "e3"];
1063        expected.sort();
1064
1065        assert_eq!(squares, expected);
1066
1067        let blocker = Piece::new(PType::PAWN, Color::WHITE);
1068        let blocker_sq = Square::new(Rank::Six, File::D);
1069        state.board.set(blocker, &blocker_sq);
1070
1071        state.update_king_attacks();
1072
1073        assert!(state.is_in_check);
1074
1075        let mut squares: Vec<String> = state.check_rays.iter().map(|m| m.get_notation()).collect();
1076
1077        squares.sort();
1078        let mut expected = ["d4", "e3", "b6"];
1079        expected.sort();
1080
1081        assert_eq!(squares, expected);
1082    }
1083
1084    #[test]
1085    fn load_fen() {
1086        let mut state = GameState::new();
1087        state.load_fen("8/2b5/1N6/2K3qk/8/8/8/8 w - - 0 1");
1088
1089        assert_eq!(
1090            state
1091                .board
1092                .get(&Square::new(Rank::Five, File::C))
1093                .expect("piece must exist here")
1094                .p_type,
1095            PType::KING
1096        );
1097
1098        assert_eq!(
1099            state
1100                .board
1101                .get(&Square::new(Rank::Five, File::G))
1102                .expect("piece must exist here")
1103                .p_type,
1104            PType::QUEEN
1105        );
1106    }
1107
1108    #[test]
1109    fn get_fen() {
1110        let mut state = GameState::new();
1111        let fens = [
1112            "8/2b5/1N6/2K3qk/8/8/8/8 w - - 0 1",
1113            "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
1114            "rn1qkb1r/p1b1pppp/4n3/1p1p2B1/Q1N5/3B1N2/PPPPPPPP/R3K2R w KQkq - 0 1",
1115            "k7/8/8/8/8/8/8/7K w - - 0 1",
1116            "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
1117            "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
1118            "1nbqkbn1/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/1NBQKBN1 b - - 1 2",
1119        ];
1120
1121        for fen in fens {
1122            state.load_fen(fen);
1123
1124            assert_eq!(fen, state.get_fen());
1125
1126            state.reset();
1127        }
1128
1129        let mut state = GameState::new();
1130        state.load_fen("4k3/8/8/8/5p2/8/4P3/4K3 w - - 0 1");
1131        assert!(state.play_move(Move::from_str("e2", "e4", None)).is_ok());
1132        assert_eq!(state.get_fen(), "4k3/8/8/8/4Pp2/8/8/4K3 b - e3 0 1");
1133
1134        // TODO: handle this case
1135        // let mut state = GameState::new();
1136        // state.load_fen("5k2/8/8/8/5p2/8/4P3/4KR2 w - - 0 1");
1137        // assert!(state.play_move(Move::from_str("e2", "e4", None)).is_ok());
1138        // assert_eq!(state.get_fen(), "5k2/8/8/8/4Pp2/8/8/4KR2 b - - 0 1");
1139    }
1140
1141    struct TestCase {
1142        fen: String,
1143        moves: Vec<Move>,
1144        expected: String,
1145    }
1146
1147    impl TestCase {
1148        fn new(fen: &str, moves: Vec<Move>, expected: &str) -> Self {
1149            Self {
1150                fen: fen.to_string(),
1151                moves,
1152                expected: expected.to_string(),
1153            }
1154        }
1155    }
1156
1157    #[test]
1158    fn undo() {
1159        let tests = vec![
1160            TestCase::new(
1161                "r3k2r/8/8/8/6pP/8/8/R3K2R w KQkq - 0 1",
1162                vec![Move::from_str("e1", "g1", None)],
1163                "r3k2r/8/8/8/6pP/8/8/R3K2R w KQkq - 0 1",
1164            ),
1165            TestCase::new(
1166                "r3k2r/8/8/8/6pP/8/8/R3K2R w KQkq - 0 1",
1167                vec![Move::from_str("e1", "c1", None)],
1168                "r3k2r/8/8/8/6pP/8/8/R3K2R w KQkq - 0 1",
1169            ),
1170            TestCase::new(
1171                "r3k2r/8/8/8/6pP/8/8/R3K2R b KQkq - 0 1",
1172                vec![Move::from_str("e8", "g8", None)],
1173                "r3k2r/8/8/8/6pP/8/8/R3K2R b KQkq - 0 1",
1174            ),
1175            TestCase::new(
1176                "r3k2r/8/2q5/1P6/6p1/8/7P/R3K2R w KQkq - 0 1",
1177                vec![Move::from_str("h2", "h4", None)],
1178                "r3k2r/8/2q5/1P6/6p1/8/7P/R3K2R w KQkq - 0 1",
1179            ),
1180            TestCase::new(
1181                "r3k2r/8/2q5/1P6/6p1/8/7P/R3K2R w KQkq - 0 1",
1182                vec![Move::from_str("b5", "c6", None)],
1183                "r3k2r/8/2q5/1P6/6p1/8/7P/R3K2R w KQkq - 0 1",
1184            ),
1185            // en passant
1186            TestCase::new(
1187                "r3k2r/8/2q5/1P6/6pP/8/8/R3K2R b KQkq h3 0 1",
1188                vec![Move::from_str("g4", "h3", None)],
1189                "r3k2r/8/2q5/1P6/6pP/8/8/R3K2R b KQkq h3 0 1",
1190            ),
1191            // promotion
1192            TestCase::new(
1193                "r3k2r/5P2/2q5/1P6/6p1/7P/8/R3K2R w KQkq - 0 1",
1194                vec![Move::from_str(
1195                    "f7",
1196                    "f8",
1197                    Some(Piece::new(PType::QUEEN, Color::WHITE)),
1198                )],
1199                "r3k2r/5P2/2q5/1P6/6p1/7P/8/R3K2R w KQkq - 0 1",
1200            ),
1201        ];
1202
1203        let mut state = GameState::new();
1204
1205        for test in tests {
1206            state.reset();
1207            state.load_fen(&test.fen);
1208
1209            for m in test.moves {
1210                assert!(state.play_move(m).is_ok());
1211                state.undo();
1212            }
1213
1214            assert_eq!(state.get_fen(), test.expected);
1215            assert!(state.captures.is_empty());
1216        }
1217    }
1218
1219    #[test]
1220    fn play_move() {
1221        let tests = vec![
1222            TestCase::new(
1223                "rnb1kbnr/pppppppp/1q6/4Q2B/8/2N4B/P1PPP2P/R3K2R w KQkq - 0 1",
1224                vec![
1225                    Move::from_str("e1", "f1", None),
1226                    Move::from_str("e8", "d8", None),
1227                ],
1228                "rnbk1bnr/pppppppp/1q6/4Q2B/8/2N4B/P1PPP2P/R4K1R w - - 2 2",
1229            ),
1230            TestCase::new(
1231                "r1bk1b1r/1ppppp1p/1q1n3n/p3Q1pB/8/2NP3B/P1PKP2P/1R4R1 w - - 0 1",
1232                vec![
1233                    Move::from_str("e5", "e7", None),
1234                    Move::from_str("d8", "e7", None),
1235                ],
1236                "r1b2b1r/1pppkp1p/1q1n3n/p5pB/8/2NP3B/P1PKP2P/1R4R1 w - - 0 2",
1237            ),
1238        ];
1239
1240        let mut state = GameState::new();
1241
1242        for test in tests {
1243            state.reset();
1244            state.load_fen(&test.fen);
1245
1246            for m in test.moves {
1247                assert!(state.play_move(m).is_ok());
1248            }
1249
1250            assert_eq!(state.get_fen(), test.expected);
1251        }
1252    }
1253
1254    #[test]
1255    fn enpassant() {
1256        let tests = vec![
1257            TestCase::new(
1258                "1k6/8/8/8/5p2/8/4P3/4KR2 w - - 0 1",
1259                vec![Move::from_str("e2", "e4", None)],
1260                "1k6/8/8/8/4Pp2/8/8/4KR2 b - e3 0 1",
1261            ),
1262            TestCase::new(
1263                "1k6/8/2p5/8/8/8/4P3/4KR2 w - - 0 1",
1264                vec![Move::from_str("e2", "e4", None)],
1265                "1k6/8/2p5/8/4P3/8/8/4KR2 b - - 0 1",
1266            ),
1267            TestCase::new(
1268                "1k6/8/8/8/6p1/8/7P/4KR2 w - - 0 1",
1269                vec![Move::from_str("h2", "h4", None)],
1270                "1k6/8/8/8/6pP/8/8/4KR2 b - h3 0 1",
1271            ),
1272        ];
1273
1274        let mut state = GameState::new();
1275
1276        for test in tests {
1277            state.reset();
1278            state.load_fen(&test.fen);
1279
1280            for m in test.moves {
1281                assert!(state.play_move(m).is_ok());
1282            }
1283
1284            assert_eq!(state.get_fen(), test.expected);
1285        }
1286    }
1287
1288    #[test]
1289    fn checkmate() {
1290        // test cases by https://github.com/jhlywa/chess.js
1291        let fens = vec![
1292            "8/5r2/4K1q1/4p3/3k4/8/8/8 w - - 0 7",
1293            "4r2r/p6p/1pnN2p1/kQp5/3pPq2/3P4/PPP3PP/R5K1 b - - 0 2",
1294            "r3k2r/ppp2p1p/2n1p1p1/8/2B2P1q/2NPb1n1/PP4PP/R2Q3K w kq - 0 8",
1295            "8/6R1/pp1r3p/6p1/P3R1Pk/1P4P1/7K/8 b - - 0 4",
1296        ];
1297
1298        for fen in fens {
1299            let mut state = GameState::new();
1300            state.load_fen(fen);
1301
1302            assert!(state.is_checkmate());
1303        }
1304    }
1305
1306    #[test]
1307    fn insufficient_material() {
1308        // test cases by https://github.com/jhlywa/chess.js
1309        let fens = vec![
1310            "8/8/8/8/8/8/8/k6K w - - 0 1",
1311            "8/2N5/8/8/8/8/8/k6K w - - 0 1",
1312            "8/2b5/8/8/8/8/8/k6K w - - 0 1",
1313            "8/b7/3B4/8/8/8/8/k6K w - - 0 1",
1314            "8/b1B1b1B1/1b1B1b1B/8/8/8/8/1k5K w - - 0 1",
1315        ];
1316
1317        for fen in fens {
1318            let mut state = GameState::new();
1319            state.load_fen(fen);
1320
1321            assert!(state.is_insufficient_material());
1322        }
1323
1324        let fens = vec![
1325            "8/2p5/8/8/8/8/8/k6K w - - 0 1",
1326            "5k1K/7B/8/6b1/8/8/8/8 b - - 0 1",
1327            "7K/5k1N/8/6b1/8/8/8/8 b - - 0 1",
1328            "7K/5k1N/8/4n3/8/8/8/8 b - - 0 1",
1329        ];
1330
1331        for fen in fens {
1332            let mut state = GameState::new();
1333            state.load_fen(fen);
1334
1335            assert!(!state.is_insufficient_material());
1336        }
1337    }
1338
1339    #[test]
1340    fn stalemate() {
1341        // test cases by https://github.com/jhlywa/chess.js
1342        let fens = vec![
1343            "1R6/8/8/8/8/8/7R/k6K b - - 0 1",
1344            "8/8/5k2/p4p1p/P4K1P/1r6/8/8 w - - 0 2",
1345        ];
1346
1347        for fen in fens {
1348            let mut state = GameState::new();
1349            state.load_fen(fen);
1350
1351            assert!(state.is_stalemate());
1352        }
1353
1354        let mut state = GameState::new();
1355        state.load_fen("R3k3/8/4K3/8/8/8/8/8 b - - 0 1");
1356
1357        assert!(!state.is_stalemate());
1358    }
1359
1360    #[test]
1361    fn threefold_repetition() {
1362        let mut state = GameState::new();
1363        state.load_fen("8/pp3p1k/2p2q1p/3r1P2/5R2/7P/P1P1QP2/7K b - - 2 30");
1364        let moves = vec![
1365            Move::from_str("f6", "e5", None),
1366            Move::from_str("e2", "h5", None),
1367            Move::from_str("e5", "f6", None),
1368            Move::from_str("h5", "e2", None),
1369            Move::from_str("d5", "e5", None),
1370            Move::from_str("e2", "d3", None),
1371            Move::from_str("e5", "d5", None),
1372            Move::from_str("d3", "e2", None),
1373        ];
1374
1375        for m in moves {
1376            assert!(state.play_move(m).is_ok());
1377        }
1378
1379        assert!(state.is_threefold_repetition());
1380    }
1381
1382    // #[test]
1383    fn perft() {
1384        struct Perft {
1385            fen: &'static str,
1386            depth: u8,
1387            expected: usize,
1388        }
1389
1390        let tests = [
1391            Perft {
1392                fen: "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1",
1393                depth: 3,
1394                expected: 2812,
1395            },
1396            Perft {
1397                fen: "rnbqkbnr/p3pppp/2p5/1pPp4/3P4/8/PP2PPPP/RNBQKBNR w KQkq b6 0 4",
1398                depth: 3,
1399                expected: 23509,
1400            },
1401            Perft {
1402                fen: "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 101",
1403                depth: 3,
1404                expected: 89890,
1405            },
1406            Perft {
1407                fen: "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8",
1408                depth: 3,
1409                expected: 62379,
1410            },
1411            Perft {
1412                fen: "r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1",
1413                depth: 4,
1414                expected: 422333,
1415            },
1416            Perft {
1417                fen: "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1",
1418                depth: 3,
1419                expected: 97862,
1420            },
1421            Perft {
1422                fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
1423                depth: 4,
1424                expected: 197281,
1425            },
1426        ];
1427        let mut state = GameState::new();
1428
1429        for test in tests {
1430            state.reset();
1431            state.load_fen(&test.fen);
1432            assert_eq!(state.perft(test.depth, false), test.expected);
1433        }
1434    }
1435}