friendly_chess/chess/
mod.rs

1mod board;
2mod castling;
3mod constants;
4mod errors;
5mod history;
6mod piece;
7mod play_move;
8mod square;
9mod utils;
10
11use board::*;
12use castling::*;
13use errors::*;
14use history::*;
15use piece::*;
16use play_move::*;
17use std::collections::HashMap;
18use std::ops::Range;
19use utils::*;
20
21pub use constants::{Color, BOARD_MAP, FILES};
22pub use piece::{Piece, PieceType};
23pub use play_move::Move;
24pub use square::{Square, SquareCoordinate, SquareCoordinateExt};
25pub use utils::{
26    convert_algebraic_notation_to_index, convert_index_to_algebraic_notation, is_valid,
27};
28
29use self::constants::{
30    BISHOP_DELTAS, BLACK_PAWN_DELTAS, BOARD_SIZE, COLOR_MASK, KING_DELTAS, KNIGHT_DELTAS,
31    QUEEN_DELTAS, ROOK_DELTAS, WHITE_PAWN_DELTAS,
32};
33
34#[derive(Clone, Debug)]
35pub struct Kings {
36    pub white: Option<SquareCoordinate>,
37    pub black: Option<SquareCoordinate>,
38}
39
40pub struct Chess {
41    pub board: Board,
42    pub turn: Color,
43
44    /// the kings' positions on the board
45    pub kings: Kings,
46    pub castling_rights: CastlingRights,
47    pub history: MoveHistory,
48    pub white_captures: Vec<Piece>,
49    pub black_captures: Vec<Piece>,
50
51    // /// Record each unique positions on the board.
52    // /// If any position occurs 3 times at any point in time, the game is declared draw
53    unique_positions: HashMap<String, u8>,
54    pub half_moves: u8,
55    pub full_moves: u8,
56    pub en_passant_sq: Option<SquareCoordinate>,
57}
58
59impl Chess {
60    pub fn new() -> Self {
61        Self {
62            board: Board::new(),
63            turn: Color::WHITE,
64            kings: Kings {
65                white: None,
66                black: None,
67            },
68            history: MoveHistory::new(),
69            castling_rights: CastlingRights::new(),
70            en_passant_sq: None,
71            white_captures: vec![],
72            black_captures: vec![],
73            full_moves: 0,
74            half_moves: 0,
75            unique_positions: HashMap::new(),
76        }
77    }
78
79    /// Make a move on the board
80    pub fn play_move(&mut self, m: Move) -> ChessResult<()> {
81        // TODO: check if it is a legal move 
82        self.make_move(m)?;
83        self.change_turn();
84
85        Ok(())
86    }
87
88    pub fn make_move(&mut self, m: Move) -> ChessResult<()> {
89        let m = self.convert_to_internal_move(m)?;
90
91        let history_entry = HistoryEntry {
92            player_move: m.clone(),
93            turn: self.turn,
94            kings: self.kings.clone(),
95            castling_rights: self.castling_rights.clone(),
96            en_passant_sq: self.en_passant_sq,
97        };
98
99        self.half_moves += 1;
100
101        self.set(m.to_sq, m.from_piece.piece_type, m.from_piece.color)?;
102        self.remove(m.from_sq)?;
103
104        if m.move_type == MoveType::Capture {
105            if let Some(to_piece) = m.to_piece {
106                if to_piece.color == Color::BLACK {
107                    self.white_captures.push(to_piece);
108                } else {
109                    self.black_captures.push(to_piece);
110                }
111            }
112        }
113
114        if m.move_type == MoveType::EnPassantCapture {
115            if let Some(en_passant_sq) = self.en_passant_sq {
116                if m.from_piece.color == Color::WHITE {
117                    self.white_captures.push(Piece {
118                        piece_type: PieceType::PAWN,
119                        color: Color::BLACK,
120                    });
121                    // remove the piece below
122                    self.remove(en_passant_sq.below()?)?;
123                } else {
124                    self.black_captures.push(Piece {
125                        piece_type: PieceType::PAWN,
126                        color: Color::WHITE,
127                    });
128                    // remove the piece above
129                    self.remove(en_passant_sq.above()?)?;
130                }
131            }
132        }
133
134        if m.move_type == MoveType::EnPassantMove {
135            if m.from_piece.color == Color::WHITE {
136                self.en_passant_sq = Some(m.to_sq.below()?);
137            } else {
138                self.en_passant_sq = Some(m.to_sq.above()?);
139            }
140        } else {
141            self.en_passant_sq = None;
142        }
143
144        if m.move_type == MoveType::CastleKingside {
145            self.remove(m.to_sq.right()?)?;
146            self.set(m.to_sq.left()?, PieceType::ROOK, m.from_piece.color)?;
147        }
148
149        if m.move_type == MoveType::CastleQueenside {
150            self.remove(m.to_sq.subtract(2)?)?;
151            self.set(m.to_sq.right()?, PieceType::ROOK, m.from_piece.color)?;
152        }
153
154        if m.move_type == MoveType::Promotion {
155            let promotion_piece = m
156                .promotion_piece
157                .ok_or(ChessError::UnknownError(format!("yah yeet")))?;
158            self.set(m.to_sq, promotion_piece.piece_type, promotion_piece.color)?;
159        }
160
161        self.history.push(history_entry);
162        self.castling_rights.update(&self.kings, &self.board)?;
163
164        // reset half moves if it is a pawn move or a piece is captured
165        if m.move_type == MoveType::Capture || m.from_piece.piece_type == PieceType::PAWN {
166            self.half_moves = 0;
167        }
168
169        Ok(())
170    }
171
172    pub fn undo_move(&mut self) -> ChessResult<()> {
173        if let Some(old) = self.history.pop() {
174            self.turn = old.turn;
175            self.en_passant_sq = old.en_passant_sq;
176            self.kings = old.kings;
177            self.castling_rights = old.castling_rights;
178
179            let m = old.player_move;
180
181            // put the piece back to its original square
182            self.set(m.from_sq, m.from_piece.piece_type, m.from_piece.color)?;
183            self.remove(m.to_sq)?;
184
185            self.half_moves = self.half_moves.saturating_sub(1);
186
187            if old.turn == Color::BLACK {
188                self.full_moves = self.full_moves.saturating_sub(1);
189            }
190
191            if m.move_type == MoveType::Capture || m.move_type == MoveType::EnPassantCapture {
192                if m.from_piece.color == Color::WHITE {
193                    self.white_captures.pop();
194                } else {
195                    self.black_captures.pop();
196                }
197            }
198
199            if m.move_type == MoveType::EnPassantCapture {
200                // put the captured piece back
201                if m.from_piece.color == Color::WHITE {
202                    self.set(m.to_sq.below()?, PieceType::PAWN, Color::BLACK)?;
203                } else {
204                    self.set(m.to_sq.above()?, PieceType::PAWN, Color::WHITE)?;
205                }
206            }
207
208            if m.move_type == MoveType::CastleKingside {
209                // put the rooks back
210                self.remove(m.to_sq.left()?)?;
211                self.set(m.to_sq.right()?, PieceType::ROOK, m.from_piece.color)?;
212            }
213
214            if m.move_type == MoveType::CastleQueenside {
215                // put the rooks back
216                self.remove(m.to_sq.right()?)?;
217                // put the rooks back two square to the left
218                self.set(m.to_sq.subtract(2)?, PieceType::ROOK, m.from_piece.color)?;
219            }
220
221            if m.move_type == MoveType::Capture || m.to_piece.is_some() {
222                if let Some(to_piece) = m.to_piece {
223                    // put the captured piece back
224                    self.set(m.to_sq, to_piece.piece_type, to_piece.color)?;
225                }
226            }
227
228            // reset half moves if it is a pawn move or a piece is captured
229            if m.move_type == MoveType::Capture || m.from_piece.piece_type == PieceType::PAWN {
230                self.half_moves = 0;
231            }
232        }
233
234        Ok(())
235    }
236
237    /// Get all legal moves for a specific piece type
238    pub fn moves_for_piece_type() {}
239
240    /// Get all legal moves for the piece on the specified square
241    pub fn moves_for_square(&mut self, sq: SquareCoordinate) -> ChessResult<Vec<Move>> {
242        let piece = self.get(sq)?.ok_or(ChessError::UnknownError(
243            "Can't generate moves for empty square".to_string(),
244        ))?;
245        
246        if piece.color != self.turn {
247            return Ok(vec![]);
248        }
249
250        let mut legal_moves = vec![];
251        let pseudo_legal_moves = match piece.piece_type {
252            PieceType::KING => self.king_moves(sq),
253            PieceType::QUEEN => self.sliding_moves(sq, QUEEN_DELTAS.to_vec()),
254            PieceType::ROOK => self.sliding_moves(sq, ROOK_DELTAS.to_vec()),
255            PieceType::BISHOP => self.sliding_moves(sq, BISHOP_DELTAS.to_vec()),
256            PieceType::KNIGHT => self.knight_moves(sq),
257            PieceType::PAWN => self.pawn_moves(sq),
258        }?;
259
260        for _move in pseudo_legal_moves {
261            self.make_move(_move)?;
262            if !self.in_check()? {
263                legal_moves.push(_move);
264            }
265
266            self.undo_move()?;
267        }
268
269        Ok(legal_moves)
270    }
271
272    /// Get all legal moves for the player to move
273    pub fn moves(&mut self) -> ChessResult<Vec<Move>> {
274        let mut moves = vec![];
275
276        for idx in 0..BOARD_SIZE {
277            if let Ok(idx) = self.board.is_valid(idx) {
278                if let Some(piece) = self.get((idx as u8).to_coordinate())? {
279                    if self.is_friendly(piece) {
280                        let mut a = self.moves_for_square((idx as u8).to_coordinate())?;
281                        moves.append(&mut a);
282                    }
283                }
284            }
285        }
286
287        Ok(moves)
288    }
289
290    /// Return true if the current player to move is in check
291    pub fn in_check(&self) -> ChessResult<bool> {
292        if self.turn == Color::WHITE {
293            if let Some(king) = self.kings.white {
294                return self.is_attacked(king);
295            }
296        } else {
297            if let Some(king) = self.kings.black {
298                return self.is_attacked(king);
299            }
300        }
301        Ok(false)
302    }
303
304    pub fn is_checkmate(&mut self) -> ChessResult<bool> {
305        let mut no_legal_moves = true;
306
307        for idx in 0..BOARD_SIZE {
308            let idx = match self.board.is_valid(idx) {
309                Ok(idx) => idx,
310                Err(_) => continue,
311            } as u8;
312
313            if let Some(piece) = self.get(idx.to_coordinate())? {
314                if self.is_friendly(piece) {
315                    let moves = self.moves_for_square(idx.to_coordinate())?;
316
317                    if moves.len() > 0 {
318                        no_legal_moves = false;
319                    }
320                }
321            }
322        }
323
324        Ok(self.in_check()? && no_legal_moves)
325    }
326
327    pub fn is_draw(&mut self) -> ChessResult<bool> {
328        Ok(self.is_stalemate()?
329            || self.is_threefold_repetition()?
330            || self.is_50_moves()?
331            || self.is_insufficient_material()?)
332    }
333
334    pub fn is_stalemate(&mut self) -> ChessResult<bool> {
335        let mut no_legal_moves = true;
336
337        for idx in 0..BOARD_SIZE {
338            let idx = match self.board.is_valid(idx) {
339                Ok(idx) => idx,
340                Err(_) => continue,
341            } as u8;
342
343            if let Some(piece) = self.get(idx.to_coordinate())? {
344                if self.is_friendly(piece) {
345                    let moves = self.moves()?;
346
347                    if moves.len() > 0 {
348                        no_legal_moves = false;
349                    }
350                }
351            }
352        }
353
354        Ok(!self.in_check()? && no_legal_moves)
355    }
356
357    pub fn is_insufficient_material(&self) -> ChessResult<bool> {
358        let mut friendly_knights = 0;
359        let mut friendly_bishops = 0;
360        let mut friendly_dark_bishops = 0;
361        let mut friendly_light_bishops = 0;
362        let mut enemy_dark_bishops = 0;
363        let mut enemy_light_bishops = 0;
364        let mut enemy_knights = 0;
365        let mut enemy_bishops = 0;
366
367        for idx in 0..BOARD_SIZE {
368            let idx = match self.board.is_valid(idx) {
369                Ok(idx) => idx,
370                Err(_) => continue,
371            } as u8;
372            let coord = idx.to_coordinate();
373
374            // if rank is odd and file is odd = dark
375            // if rank is even and file is even = dark
376
377            // if rank is even and file is odd = light
378            // if rank is odd and file is even = light
379
380            if let Some(piece) = self.get(coord)? {
381                let piece_type = piece.piece_type;
382
383                if piece_type == PieceType::PAWN
384                    || piece_type == PieceType::ROOK
385                    || piece_type == PieceType::QUEEN
386                {
387                    return Ok(false);
388                }
389
390                if self.is_friendly(piece) {
391                    if piece_type == PieceType::KNIGHT {
392                        friendly_knights += 1;
393                    }
394
395                    if piece_type == PieceType::BISHOP {
396                        // dark square bishop
397                        if coord.is_dark_sq() {
398                            friendly_dark_bishops += 1;
399
400                            if enemy_light_bishops >= 1 {
401                                return Ok(false);
402                            }
403                        }
404
405                        // light square bishop
406                        if coord.is_light_sq() {
407                            friendly_light_bishops += 1;
408
409                            if enemy_dark_bishops >= 1 {
410                                return Ok(false);
411                            }
412                        }
413
414                        friendly_bishops += 1;
415                    }
416                } else {
417                    if piece_type == PieceType::KNIGHT {
418                        enemy_knights += 1;
419                    }
420
421                    if piece_type == PieceType::BISHOP {
422                        // dark square bishop
423
424                        if coord.is_dark_sq() {
425                            enemy_dark_bishops += 1;
426
427                            if friendly_light_bishops >= 1 {
428                                return Ok(false);
429                            }
430                        }
431
432                        // light square bishop
433                        if coord.is_light_sq() {
434                            enemy_light_bishops += 1;
435
436                            if friendly_dark_bishops >= 1 {
437                                return Ok(false);
438                            }
439                        }
440
441                        enemy_bishops += 1;
442                    }
443                }
444            }
445        }
446
447        // king vs king
448        if friendly_knights == 0
449            && friendly_bishops == 0
450            && enemy_knights == 0
451            && enemy_bishops == 0
452        {
453            return Ok(true);
454        }
455
456        if friendly_knights == 0 && enemy_knights == 0 {
457            if friendly_bishops == 2 || enemy_bishops == 2 {
458                return Ok(false);
459            }
460
461            return Ok(true);
462        }
463
464        if friendly_bishops == 0 && enemy_bishops == 0 {
465            if friendly_knights >= 1 && enemy_knights >= 1 {
466                return Ok(false);
467            }
468
469            return Ok(true);
470        }
471
472        Ok(false)
473    }
474
475    pub fn is_50_moves(&self) -> ChessResult<bool> {
476        Ok(self.half_moves >= 100)
477    }
478
479    pub fn is_threefold_repetition(&self) -> ChessResult<bool> {
480        let fen = self.get_fen()?;
481        let position = fen.split(" ").collect::<Vec<&str>>()[0];
482
483        if let Some(count) = self.unique_positions.get(position) {
484            Ok(*count >= 3)
485        } else {
486            Ok(false)
487        }
488    }
489
490    /// Check if a square is attacked by opponent pieces
491    pub fn is_attacked(&self, from: SquareCoordinate) -> ChessResult<bool> {
492        let sliding_attack_deltas = vec![16, -16, 1, -1, 17, 15, -17, -15];
493        let knight_attack_deltas = vec![14, 31, 18, 33, -14, -31, -18, -33];
494
495        let from_idx = from.to_index();
496        let from_piece = self.get(from)?;
497
498        for delta in sliding_attack_deltas {
499            let mut to = from.to_index() as i16 + delta as i16;
500            loop {
501                if let Ok(_to) = utils::is_valid(to as usize) {
502                    let to_sq = (_to as u8).to_coordinate();
503
504                    if let Some(attacker) = self.get(to_sq)? {
505                        if attacker.color == self.turn {
506                            break;
507                        }
508
509                        if let Some(from_piece) = from_piece {
510                            if from_piece.color == attacker.color {
511                                break;
512                            }
513                        }
514
515                        let diff = from_idx as i16 - to + 119;
516                        let attack_bits_mask = constants::ATTACKS[diff as usize];
517
518                        if attack_bits_mask != 0 {
519                            if attacker.piece_type == PieceType::PAWN {
520                                let with_color =
521                                    attacker.piece_type.to_value() | attacker.color.to_value();
522
523                                if with_color & attack_bits_mask != 0
524                                    && attacker.color.to_value() == attack_bits_mask & COLOR_MASK
525                                {
526                                    return Ok(true);
527                                } else {
528                                    break;
529                                }
530                            } else {
531                                if (attacker.piece_type.to_value() & attack_bits_mask) != 0 {
532                                    return Ok(true);
533                                } else {
534                                    break;
535                                }
536                            }
537                        }
538                    }
539
540                    to += delta as i16;
541                } else {
542                    break;
543                }
544            }
545        }
546
547        for delta in knight_attack_deltas {
548            let to = from.to_index() as i16 + delta as i16;
549
550            if let Ok(_to) = utils::is_valid(to as usize) {
551                let to_sq = (_to as u8).to_coordinate();
552
553                if let Some(attacker) = self.get(to_sq)? {
554                    if !self.is_friendly(attacker) && attacker.piece_type == PieceType::KNIGHT {
555                        return Ok(true);
556                    }
557                }
558            }
559        }
560
561        Ok(false)
562    }
563
564    pub fn change_turn(&mut self) {
565        if self.turn == Color::WHITE {
566            self.turn = Color::BLACK
567        } else {
568            self.turn = Color::WHITE
569        }
570    }
571
572    /// Return the PieceType and its associated value on a square. Return an ChessError if the index is out of range.
573    ///
574    /// Some(t) means an occupied square, None means the square is empty.
575    pub fn get(&self, sq: SquareCoordinate) -> ChessResult<Option<Piece>> {
576        Ok(self.board.get(sq)?)
577    }
578
579    /// Put a piece at specific index on the board. Return the piece and index if succeed, or an ChessError if not.
580    ///
581    /// Note: this does not update castling rights
582    pub fn set(
583        &mut self,
584        sq: SquareCoordinate,
585        piece: PieceType,
586        color: Color,
587    ) -> ChessResult<(Piece, usize)> {
588        let s = self.board.set(sq, piece, color)?;
589
590        if piece == PieceType::KING {
591            self.update_kings_positions(sq, color);
592        }
593
594        Ok(s)
595    }
596
597    pub fn remove(&mut self, sq: SquareCoordinate) -> ChessResult<()> {
598        Ok(self.board.remove(sq)?)
599    }
600
601    pub fn get_fen(&self) -> ChessResult<String> {
602        let mut fen = String::from("");
603
604        let turn = self.turn;
605        let en_passant_sq = if let Some(sq) = self.en_passant_sq {
606            sq.to_san()
607        } else {
608            "-".to_string()
609        };
610
611        let half_moves = self.half_moves;
612        let full_moves = self.full_moves;
613
614        let mut castling_rights = String::new();
615        if self.castling_rights.white.kingside {
616            castling_rights.push_str("K");
617        }
618
619        if self.castling_rights.white.queenside {
620            castling_rights.push_str("Q");
621        }
622
623        if self.castling_rights.black.kingside {
624            castling_rights.push_str("k");
625        }
626
627        if self.castling_rights.black.queenside {
628            castling_rights.push_str("q")
629        }
630
631        if castling_rights.is_empty() {
632            castling_rights.push_str("-");
633        }
634
635        let mut empty_square: u8 = 0;
636        for idx in 0..BOARD_SIZE {
637            let idx = match self.board.is_valid(idx) {
638                Ok(idx) => idx,
639                Err(_) => continue,
640            } as u8;
641
642            if let Some(piece) = self.get(idx.to_coordinate())? {
643                if empty_square != 0 {
644                    fen.push_str(empty_square.to_string().as_str());
645                    empty_square = 0;
646                }
647
648                if piece.color == Color::WHITE {
649                    match piece.piece_type {
650                        PieceType::PAWN => fen.push_str("P"),
651                        PieceType::ROOK => fen.push_str("R"),
652                        PieceType::KNIGHT => fen.push_str("N"),
653                        PieceType::BISHOP => fen.push_str("B"),
654                        PieceType::QUEEN => fen.push_str("Q"),
655                        PieceType::KING => fen.push_str("K"),
656                        // _ => panic!("error generating FEN"),
657                    }
658                } else {
659                    match piece.piece_type {
660                        PieceType::PAWN => fen.push_str("p"),
661                        PieceType::ROOK => fen.push_str("r"),
662                        PieceType::KNIGHT => fen.push_str("n"),
663                        PieceType::BISHOP => fen.push_str("b"),
664                        PieceType::QUEEN => fen.push_str("q"),
665                        PieceType::KING => fen.push_str("k"),
666                        // _ => panic!("error generating FEN"),
667                    }
668                }
669            } else {
670                empty_square += 1;
671            }
672
673            if (idx + 1) % 8 == 0 {
674                if empty_square != 0 {
675                    fen.push_str(empty_square.to_string().as_str());
676                    empty_square = 0;
677                }
678
679                // if it is the last rank on board, no need to separate with /
680                if idx != 119 {
681                    fen.push('/');
682                }
683            }
684        }
685
686        Ok(vec![
687            fen,
688            turn.to_string(),
689            castling_rights,
690            en_passant_sq,
691            half_moves.to_string(),
692            full_moves.to_string(),
693        ]
694        .join(" "))
695    }
696
697    pub fn load_fen(&mut self, fen: String) -> ChessResult<()> {
698        let fen_parts: Vec<&str> = fen.split(" ").collect();
699
700        let ranks: Vec<&str> = fen_parts[0].split("/").collect();
701
702        let mut idx: usize = 0;
703        for rank in ranks {
704            for piece in rank.chars() {
705                let coord = (BOARD_MAP[idx] as u8).to_coordinate();
706                match piece {
707                    'p' => {
708                        self.set(coord, PieceType::PAWN, Color::BLACK)?;
709                    }
710                    'r' => {
711                        self.set(coord, PieceType::ROOK, Color::BLACK)?;
712                    }
713                    'n' => {
714                        self.set(coord, PieceType::KNIGHT, Color::BLACK)?;
715                    }
716                    'b' => {
717                        self.set(coord, PieceType::BISHOP, Color::BLACK)?;
718                    }
719                    'q' => {
720                        self.set(coord, PieceType::QUEEN, Color::BLACK)?;
721                    }
722                    'k' => {
723                        self.set(coord, PieceType::KING, Color::BLACK)?;
724                    }
725                    'P' => {
726                        self.set(coord, PieceType::PAWN, Color::WHITE)?;
727                    }
728                    'R' => {
729                        self.set(coord, PieceType::ROOK, Color::WHITE)?;
730                    }
731                    'N' => {
732                        self.set(coord, PieceType::KNIGHT, Color::WHITE)?;
733                    }
734                    'B' => {
735                        self.set(coord, PieceType::BISHOP, Color::WHITE)?;
736                    }
737                    'Q' => {
738                        self.set(coord, PieceType::QUEEN, Color::WHITE)?;
739                    }
740                    'K' => {
741                        self.set(coord, PieceType::KING, Color::WHITE)?;
742                    }
743                    '1'..='8' => idx += piece.to_digit(10).unwrap() as usize - 1,
744                    _ => panic!("can't load fen pieces"),
745                }
746
747                idx += 1;
748            }
749        }
750
751        // set turn
752        match fen_parts[1] {
753            "w" => self.set_turn(Color::WHITE),
754            "b" => self.set_turn(Color::BLACK),
755            _ => panic!("can't load fen turn"),
756        }
757
758        // castling rights
759        for castling_right in fen_parts[2].chars() {
760            match castling_right {
761                'K' => {
762                    self.castling_rights.white.kingside = true;
763                }
764                'Q' => {
765                    self.castling_rights.white.queenside = true;
766                }
767                'k' => {
768                    self.castling_rights.black.kingside = true;
769                }
770                'q' => {
771                    self.castling_rights.black.queenside = true;
772                }
773
774                '-' => {
775                    self.castling_rights.white.kingside = false;
776                    self.castling_rights.white.queenside = false;
777                    self.castling_rights.black.kingside = false;
778                    self.castling_rights.black.kingside = false;
779                }
780                _ => panic!("cant load fen castling rights"),
781            }
782        }
783
784        // en passant square
785        let square = fen_parts[3];
786
787        match square {
788            // no en passant square
789            "-" => {
790                self.en_passant_sq = None;
791            }
792            _ => {
793                let idx = BOARD_MAP[convert_algebraic_notation_to_index(square) as usize] as u8;
794                self.en_passant_sq = Some(idx.to_coordinate());
795            }
796        }
797
798        self.half_moves = fen_parts[4].parse().unwrap();
799        self.full_moves = fen_parts[5].parse().unwrap();
800
801        *self
802            .unique_positions
803            .entry(fen_parts[0].to_string())
804            .or_insert(0) += 1;
805
806        Ok(())
807    }
808
809    pub fn perft(&mut self, depth: u8, yah: bool) -> ChessResult<u64> {
810        let mut nodes: u64 = 0;
811        let mut cnt = Ok(0);
812
813        if depth <= 0 {
814            return Ok(1);
815        }
816
817        let moves = self.moves()?;
818
819        for _move in moves {
820            let from = convert_index_to_algebraic_notation(_move.from.to_index() as u8);
821            let to = convert_index_to_algebraic_notation(_move.to.to_index() as u8);
822
823            self.make_move(_move)?;
824            self.change_turn();
825
826            cnt = self.perft(depth - 1, false);
827
828            if let Ok(cnt) = cnt {
829                nodes += cnt;
830
831                if yah {
832                    if let Some(promotion_piece) = _move.promotion_piece {
833                        match promotion_piece.piece_type {
834                            PieceType::BISHOP => {
835                                println!("{} {}", format!("{}{}b", from, to), cnt);
836                            }
837                            PieceType::KNIGHT => {
838                                println!("{} {}", format!("{}{}n", from, to), cnt);
839                            }
840                            PieceType::ROOK => {
841                                println!("{} {}", format!("{}{}r", from, to), cnt);
842                            }
843                            PieceType::QUEEN => {
844                                println!("{} {}", format!("{}{}q", from, to), cnt);
845                            }
846                            _ => panic!("invalid promotion piece"),
847                        }
848                    } else {
849                        println!("{} {}", format!("{}{}", from, to), cnt);
850                    }
851                }
852            } else {
853                panic!(
854                    "depth {} mvoe {:?} piece {:?} error {:?}",
855                    depth,
856                    _move,
857                    self.get(_move.from),
858                    cnt
859                );
860            }
861            self.undo_move()?;
862        }
863
864
865        return Ok(nodes);
866    }
867
868    pub fn clear(&mut self) {
869        *self = Self::new();
870    }
871
872    fn set_turn(&mut self, color: Color) {
873        self.turn = color;
874    }
875
876    fn castling_rights_for_turn(&self) -> (bool, bool) {
877        if self.turn == Color::WHITE {
878            (
879                self.castling_rights.white.kingside,
880                self.castling_rights.white.queenside,
881            )
882        } else {
883            (
884                self.castling_rights.black.kingside,
885                self.castling_rights.black.queenside,
886            )
887        }
888    }
889
890    fn pawn_moves(&self, from: SquareCoordinate) -> ChessResult<Vec<Move>> {
891        let mut moves: Vec<Move> = vec![];
892
893        let from_piece = self.get(from)?.ok_or(ChessError::UnknownError(
894            "can't generate move for empty piece".to_string(),
895        ))?;
896
897        let deltas = match from_piece.color {
898            Color::BLACK => BLACK_PAWN_DELTAS,
899            Color::WHITE => WHITE_PAWN_DELTAS,
900        };
901
902        let mut can_move_forward = true;
903
904        for delta in deltas {
905            let to = from.to_index() as i16 + delta as i16;
906            if let Ok(_to) = utils::is_valid(to as usize) {
907                let to_sq = (_to as u8).to_coordinate();
908                let rank = to_sq.rank();
909
910                if delta % 2 != 0 {
911                    // normal capture
912                    if let Some(attacker) = self.get(to_sq)? {
913                        if !self.is_friendly(attacker) {
914                            // promotion
915                            if rank == 1 || rank == 8 {
916                                let color = from_piece.color;
917                                let promotion_pieces = vec![
918                                    Piece {
919                                        piece_type: PieceType::BISHOP,
920                                        color,
921                                    },
922                                    Piece {
923                                        piece_type: PieceType::KNIGHT,
924                                        color,
925                                    },
926                                    Piece {
927                                        piece_type: PieceType::ROOK,
928                                        color,
929                                    },
930                                    Piece {
931                                        piece_type: PieceType::QUEEN,
932                                        color,
933                                    },
934                                ];
935
936                                for piece in promotion_pieces {
937                                    moves.push(Move {
938                                        from,
939                                        to: to_sq,
940                                        promotion_piece: Some(piece),
941                                    })
942                                }
943                            } else {
944                                moves.push(Move {
945                                    from,
946                                    to: to_sq,
947                                    promotion_piece: None,
948                                });
949                            }
950                        }
951                    }
952
953                    // en passant capture
954                    if self.en_passant_sq == Some(to_sq) {
955                        moves.push(Move {
956                            from,
957                            to: to_sq,
958                            promotion_piece: None,
959                        });
960                    }
961                } else {
962                    if self.get(to_sq)?.is_some() {
963                        can_move_forward = false;
964                    }
965
966                    if !can_move_forward {
967                        continue;
968                    }
969
970                    let rank = from.rank();
971                    // can only do en passant move if rank is 2 for white or 7 for black
972                    if to.abs_diff(from.to_index() as i16) == 32 {
973                        if from_piece.color == Color::WHITE && rank == 2 {
974                            moves.push(Move {
975                                from,
976                                to: to_sq,
977                                promotion_piece: None,
978                            });
979                        }
980
981                        if from_piece.color == Color::BLACK && rank == 7 {
982                            moves.push(Move {
983                                from,
984                                to: to_sq,
985                                promotion_piece: None,
986                            });
987                        }
988                        continue;
989                    }
990
991                    let rank = to_sq.rank();
992
993                    if rank == 1 || rank == 8 {
994                        let color = from_piece.color;
995                        let promotion_pieces = vec![
996                            Piece {
997                                piece_type: PieceType::BISHOP,
998                                color,
999                            },
1000                            Piece {
1001                                piece_type: PieceType::KNIGHT,
1002                                color,
1003                            },
1004                            Piece {
1005                                piece_type: PieceType::ROOK,
1006                                color,
1007                            },
1008                            Piece {
1009                                piece_type: PieceType::QUEEN,
1010                                color,
1011                            },
1012                        ];
1013
1014                        for piece in promotion_pieces {
1015                            moves.push(Move {
1016                                from,
1017                                to: to_sq,
1018                                promotion_piece: Some(piece),
1019                            })
1020                        }
1021                    } else {
1022                        moves.push(Move {
1023                            from,
1024                            to: to_sq,
1025                            promotion_piece: None,
1026                        })
1027                    }
1028                }
1029            } else {
1030                continue;
1031            }
1032        }
1033
1034        Ok(moves)
1035    }
1036
1037    fn king_moves(&mut self, from: SquareCoordinate) -> ChessResult<Vec<Move>> {
1038        self.castling_rights.update(&self.kings, &self.board)?;
1039
1040        let mut moves: Vec<Move> = vec![];
1041
1042        let deltas = KING_DELTAS.to_vec();
1043        let (can_castle_kingside, can_castle_queenside) = self.castling_rights_for_turn();
1044        let from_idx = from.to_index() as i8;
1045
1046        let is_in_check = self.in_check()?;
1047
1048        for delta in deltas {
1049            let to = from.to_index() as i16 + delta as i16;
1050
1051            if let Ok(_to) = utils::is_valid(to as usize) {
1052                let to_sq = (_to as u8).to_coordinate();
1053
1054                let diff = to - from_idx as i16;
1055                let is_castling = diff == 2 || diff == -2;
1056
1057                // Kingside castle
1058                if diff == 2 && !can_castle_kingside {
1059                    continue;
1060                }
1061
1062                // Queenside castle
1063                if diff == -2 && !can_castle_queenside {
1064                    continue;
1065                }
1066
1067                if let Some(piece) = self.get(to_sq)? {
1068                    // if we encounter a friendly piece, we can't move there
1069                    if self.is_friendly(piece) {
1070                        continue;
1071                    }
1072                }
1073
1074                if is_castling {
1075                    let mut allow_castle = true;
1076                    let range: Range<i8> = match diff {
1077                        2 => Ok(0..2),    // kingside (2 squares to check)
1078                        -2 => Ok(-4..-1), // queenside (3 squares to check)
1079                        _ => Err(ChessError::UnknownError("Illegal castle".to_string())),
1080                    }?;
1081
1082                    for offset in range {
1083                        let offset = (offset + 1 + from_idx) as u8;
1084                        let coord = offset.to_coordinate();
1085
1086                        // if a piece is blocking the path, we can't castle
1087                        if self.get(coord)?.is_some() {
1088                            allow_castle = false;
1089                            break;
1090                        }
1091                        let a = 1;
1092                        let attacked = self.is_attacked(coord)?;
1093                        // if king is attacked on the path, we can't castle
1094                        if attacked
1095                            && (coord != SquareCoordinate::B1 && coord != SquareCoordinate::B8)
1096                        {
1097                            allow_castle = false;
1098                            break;
1099                        }
1100                    }
1101
1102                    if allow_castle && !is_in_check {
1103                        moves.push(Move {
1104                            from,
1105                            to: to_sq,
1106                            promotion_piece: None,
1107                        });
1108                    }
1109                } else {
1110                    moves.push(Move {
1111                        from,
1112                        to: to_sq,
1113                        promotion_piece: None,
1114                    });
1115                }
1116            } else {
1117                continue;
1118            }
1119        }
1120
1121        Ok(moves)
1122    }
1123
1124    fn knight_moves(&self, from: SquareCoordinate) -> ChessResult<Vec<Move>> {
1125        let mut moves: Vec<Move> = vec![];
1126
1127        let deltas = KNIGHT_DELTAS.to_vec();
1128
1129        for delta in deltas {
1130            let to = from.to_index() as i16 + delta as i16;
1131            if let Ok(_to) = utils::is_valid(to as usize) {
1132                let to_sq = (_to as u8).to_coordinate();
1133
1134                if let Some(piece) = self.get(to_sq)? {
1135                    // if we encounter a friendly piece, we can't move there
1136                    if self.is_friendly(piece) {
1137                        continue;
1138                    }
1139                }
1140
1141                moves.push(Move {
1142                    from,
1143                    to: to_sq,
1144                    promotion_piece: None,
1145                });
1146            } else {
1147                continue;
1148            }
1149        }
1150
1151        Ok(moves)
1152    }
1153
1154    fn sliding_moves(&self, from: SquareCoordinate, deltas: Vec<i8>) -> ChessResult<Vec<Move>> {
1155        let mut moves: Vec<Move> = vec![];
1156
1157        for delta in deltas {
1158            let mut to = from.to_index() as i16 + delta as i16;
1159
1160            loop {
1161                if let Ok(_to) = utils::is_valid(to as usize) {
1162                    let to_sq = (_to as u8).to_coordinate();
1163
1164                    if let Some(piece) = self.get(to_sq)? {
1165                        // if we encounter a friendly piece, we can't move there
1166                        if self.is_friendly(piece) {
1167                            break;
1168                        } else {
1169                            // if we encounter an enemy piece, we can capture it but cannot move further
1170                            moves.push(Move {
1171                                from,
1172                                to: to_sq,
1173                                promotion_piece: None,
1174                            });
1175                            break;
1176                        }
1177                    } else {
1178                        moves.push(Move {
1179                            from,
1180                            to: to_sq,
1181                            promotion_piece: None,
1182                        });
1183                    }
1184
1185                    // if the destination square is on the board, we keep searching in that direction until we go off the board
1186                    to += delta as i16;
1187                } else {
1188                    break;
1189                }
1190            }
1191        }
1192
1193        Ok(moves)
1194    }
1195
1196    fn convert_to_internal_move(&self, m: Move) -> ChessResult<InternalMove> {
1197        let from_piece = self
1198            .get(m.from)?
1199            .ok_or(ChessError::InvalidMove(m.from.to_index(), m.to.to_index()))?;
1200
1201        let to_piece = self.get(m.to)?;
1202
1203        // create an Internal Move with some defaults
1204        let mut internal_move = InternalMove {
1205            move_type: MoveType::Normal,
1206            from_sq: m.from,
1207            from_piece,
1208            to_sq: m.to,
1209            to_piece,
1210            promotion_piece: None,
1211        };
1212
1213        // capture
1214        if let Some(to_piece) = to_piece {
1215            if !self.is_friendly(to_piece) {
1216                // if the piece isn't friendly, then it is a capture
1217                internal_move.move_type = MoveType::Capture;
1218                internal_move.to_piece = Some(to_piece);
1219            }
1220        }
1221
1222        if from_piece.piece_type == PieceType::PAWN {
1223            // en passant move
1224            if m.to.to_index().abs_diff(m.from.to_index()) == 32 {
1225                internal_move.move_type = MoveType::EnPassantMove;
1226            }
1227
1228            // en passant capture
1229            if let Some(en_passant_sq) = self.en_passant_sq {
1230                if m.to == en_passant_sq {
1231                    internal_move.move_type = MoveType::EnPassantCapture;
1232                }
1233            }
1234
1235            let rank = m.to.rank();
1236            // promotion
1237            if (rank == 8 || rank == 1) {
1238                internal_move.move_type = MoveType::Promotion;
1239                internal_move.promotion_piece = m.promotion_piece;
1240            }
1241        }
1242
1243        // castling
1244        if from_piece.piece_type == PieceType::KING {
1245            let diff = m.to.to_index() as i8 - m.from.to_index() as i8;
1246            if diff == 2 {
1247                internal_move.move_type = MoveType::CastleKingside;
1248            }
1249
1250            if diff == -2 {
1251                internal_move.move_type = MoveType::CastleQueenside;
1252            }
1253        }
1254
1255        Ok(internal_move)
1256    }
1257
1258    fn is_friendly(&self, piece: Piece) -> bool {
1259        self.turn == piece.color
1260    }
1261
1262    fn update_kings_positions(&mut self, new_sq: SquareCoordinate, color: Color) {
1263        if color == Color::WHITE {
1264            self.kings.white = Some(new_sq)
1265        } else {
1266            self.kings.black = Some(new_sq)
1267        }
1268    }
1269}
1270
1271#[cfg(test)]
1272mod tests {
1273    use super::{PieceType, SquareCoordinate as Square, *};
1274
1275    #[test]
1276    fn board_set_and_get_pieces() {
1277        let mut chess = Chess::new();
1278
1279        assert_eq!(
1280            chess.set(Square::__BAD_COORD, PieceType::KING, Color::WHITE),
1281            Err(ChessError::InvalidIndex(Square::__BAD_COORD.to_index()))
1282        );
1283
1284        assert_eq!(chess.get(Square::E1), Ok(None));
1285
1286        // assigning to empty var to ignore warning
1287        let _ = chess.set(Square::E1, PieceType::KING, Color::WHITE);
1288
1289        assert_eq!(
1290            chess.get(Square::E1),
1291            Ok(Some(Piece {
1292                piece_type: PieceType::KING,
1293                color: Color::WHITE
1294            }))
1295        );
1296
1297        let _ = chess.remove(Square::E1);
1298
1299        assert_eq!(chess.get(Square::E1), Ok(None));
1300    }
1301
1302    #[test]
1303    fn make_move() {
1304        let mut chess = Chess::new();
1305
1306        // assigning to empty var to ignore warning
1307        let _ = chess.set(Square::E8, PieceType::KING, Color::BLACK);
1308
1309        assert_eq!(
1310            chess.get(Square::E8),
1311            Ok(Some(Piece {
1312                piece_type: PieceType::KING,
1313                color: Color::BLACK
1314            }))
1315        );
1316
1317        assert_eq!(
1318            chess.make_move(Move {
1319                from: Square::E8,
1320                to: Square::E7,
1321                promotion_piece: None
1322            }),
1323            Ok(())
1324        );
1325
1326        assert_eq!(
1327            chess.make_move(Move {
1328                from: Square::E3,
1329                to: Square::E7,
1330                promotion_piece: None
1331            }),
1332            Err(ChessError::InvalidMove(
1333                Square::E3.to_index(),
1334                Square::E7.to_index()
1335            ))
1336        );
1337    }
1338
1339    #[test]
1340    fn en_passant_move() {
1341        let mut chess = Chess::new();
1342
1343        // assigning to empty var to ignore warning
1344        let _ = chess.set(Square::E2, PieceType::PAWN, Color::WHITE);
1345
1346        assert_eq!(
1347            chess.make_move(Move {
1348                from: Square::E2,
1349                to: Square::E4,
1350                promotion_piece: None
1351            }),
1352            Ok(())
1353        );
1354
1355        assert_eq!(chess.en_passant_sq, Some(Square::E3));
1356
1357        assert_eq!(
1358            chess.make_move(Move {
1359                from: Square::E4,
1360                to: Square::E5,
1361                promotion_piece: None
1362            }),
1363            Ok(())
1364        );
1365
1366        assert_eq!(chess.en_passant_sq, None);
1367    }
1368
1369    #[test]
1370    fn en_passant_capture() {
1371        let mut chess = Chess::new();
1372
1373        let _ = chess.set(Square::E5, PieceType::PAWN, Color::WHITE);
1374        let _ = chess.set(Square::D5, PieceType::PAWN, Color::BLACK);
1375
1376        chess.en_passant_sq = Some(Square::D6);
1377
1378        assert_eq!(
1379            chess.make_move(Move {
1380                from: Square::E5,
1381                to: Square::D6,
1382                promotion_piece: None
1383            }),
1384            Ok(())
1385        );
1386
1387        assert_eq!(chess.get(Square::D5), Ok(None));
1388        assert_eq!(chess.en_passant_sq, None);
1389        assert_eq!(
1390            chess.white_captures,
1391            vec![Piece {
1392                piece_type: PieceType::PAWN,
1393                color: Color::BLACK
1394            }]
1395        )
1396    }
1397
1398    #[test]
1399    fn undo_normal_move() {
1400        let mut chess = Chess::new();
1401
1402        let _ = chess.set(Square::E1, PieceType::KING, Color::WHITE);
1403
1404        let _ = chess.make_move(Move {
1405            from: Square::E1,
1406            to: Square::E2,
1407            promotion_piece: None,
1408        });
1409
1410        assert_eq!(chess.undo_move(), Ok(()));
1411
1412        assert_eq!(chess.get(Square::E2), Ok(None));
1413
1414        assert_eq!(
1415            chess.get(Square::E1),
1416            Ok(Some(Piece {
1417                piece_type: PieceType::KING,
1418                color: Color::WHITE,
1419            }))
1420        );
1421    }
1422
1423    #[test]
1424    fn undo_en_passant_capture() {
1425        let mut chess = Chess::new();
1426
1427        let _ = chess.set(Square::E5, PieceType::PAWN, Color::WHITE);
1428        let _ = chess.set(Square::D5, PieceType::PAWN, Color::BLACK);
1429
1430        chess.en_passant_sq = Some(Square::D6);
1431
1432        assert_eq!(
1433            chess.make_move(Move {
1434                from: Square::E5,
1435                to: Square::D6,
1436                promotion_piece: None
1437            }),
1438            Ok(())
1439        );
1440
1441        assert_eq!(chess.undo_move(), Ok(()));
1442
1443        assert_eq!(chess.get(Square::D6), Ok(None));
1444        assert_eq!(
1445            chess.get(Square::D5),
1446            Ok(Some(Piece {
1447                piece_type: PieceType::PAWN,
1448                color: Color::BLACK
1449            }))
1450        );
1451        assert_eq!(
1452            chess.get(Square::E5),
1453            Ok(Some(Piece {
1454                piece_type: PieceType::PAWN,
1455                color: Color::WHITE
1456            }))
1457        );
1458    }
1459
1460    #[test]
1461    fn undo_en_passant_move() {
1462        let mut chess = Chess::new();
1463
1464        // assigning to empty var to ignore warning
1465        let _ = chess.set(Square::E2, PieceType::PAWN, Color::WHITE);
1466
1467        assert_eq!(
1468            chess.make_move(Move {
1469                from: Square::E2,
1470                to: Square::E4,
1471                promotion_piece: None
1472            }),
1473            Ok(())
1474        );
1475
1476        assert_eq!(chess.en_passant_sq, Some(Square::E3));
1477
1478        assert_eq!(chess.undo_move(), Ok(()));
1479
1480        assert_eq!(chess.get(Square::E4), Ok(None));
1481
1482        assert_eq!(
1483            chess.get(Square::E2),
1484            Ok(Some(Piece {
1485                piece_type: PieceType::PAWN,
1486                color: Color::WHITE
1487            }))
1488        );
1489
1490        assert_eq!(chess.en_passant_sq, None);
1491    }
1492
1493    #[test]
1494    fn undo_capture() {
1495        let mut chess = Chess::new();
1496
1497        // assigning to empty var to ignore warning
1498        let _ = chess.set(Square::F1, PieceType::BISHOP, Color::WHITE);
1499        let _ = chess.set(Square::C4, PieceType::QUEEN, Color::BLACK);
1500
1501        assert_eq!(
1502            chess.make_move(Move {
1503                from: Square::F1,
1504                to: Square::C4,
1505                promotion_piece: None
1506            }),
1507            Ok(())
1508        );
1509
1510        assert_eq!(
1511            chess.white_captures,
1512            vec![Piece {
1513                piece_type: PieceType::QUEEN,
1514                color: Color::BLACK
1515            }]
1516        );
1517
1518        assert_eq!(
1519            chess.get(Square::C4),
1520            Ok(Some(Piece {
1521                piece_type: PieceType::BISHOP,
1522                color: Color::WHITE
1523            }))
1524        );
1525
1526        assert_eq!(chess.undo_move(), Ok(()));
1527
1528        assert_eq!(chess.white_captures, vec![]);
1529
1530        assert_eq!(
1531            chess.get(Square::F1),
1532            Ok(Some(Piece {
1533                piece_type: PieceType::BISHOP,
1534                color: Color::WHITE
1535            }))
1536        );
1537
1538        assert_eq!(
1539            chess.get(Square::C4),
1540            Ok(Some(Piece {
1541                piece_type: PieceType::QUEEN,
1542                color: Color::BLACK
1543            }))
1544        );
1545    }
1546
1547    #[test]
1548    fn undo_kingside_castle() {
1549        let mut chess = Chess::new();
1550
1551        // assigning to empty var to ignore warning
1552        let _ = chess.set(Square::E1, PieceType::KING, Color::WHITE);
1553        let _ = chess.set(Square::H1, PieceType::ROOK, Color::WHITE);
1554
1555        assert_eq!(
1556            chess.make_move(Move {
1557                from: Square::E1,
1558                to: Square::G1,
1559                promotion_piece: None
1560            }),
1561            Ok(())
1562        );
1563
1564        assert_eq!(chess.undo_move(), Ok(()));
1565
1566        assert_eq!(
1567            chess.get(Square::E1),
1568            Ok(Some(Piece {
1569                piece_type: PieceType::KING,
1570                color: Color::WHITE
1571            }))
1572        );
1573
1574        assert_eq!(
1575            chess.get(Square::H1),
1576            Ok(Some(Piece {
1577                piece_type: PieceType::ROOK,
1578                color: Color::WHITE
1579            }))
1580        );
1581
1582        assert_eq!(chess.get(Square::G1), Ok(None));
1583        assert_eq!(chess.get(Square::F1), Ok(None));
1584        assert_eq!(chess.castling_rights.white.kingside, true);
1585        assert_eq!(chess.castling_rights.white.queenside, true);
1586    }
1587
1588    #[test]
1589    fn undo_queenside_castle() {
1590        let mut chess = Chess::new();
1591
1592        // assigning to empty var to ignore warning
1593        let _ = chess.set(Square::E8, PieceType::KING, Color::BLACK);
1594        let _ = chess.set(Square::A8, PieceType::ROOK, Color::BLACK);
1595
1596        assert_eq!(
1597            chess.make_move(Move {
1598                from: Square::E8,
1599                to: Square::C8,
1600                promotion_piece: None
1601            }),
1602            Ok(())
1603        );
1604
1605        assert_eq!(chess.undo_move(), Ok(()));
1606
1607        assert_eq!(
1608            chess.get(Square::E8),
1609            Ok(Some(Piece {
1610                piece_type: PieceType::KING,
1611                color: Color::BLACK
1612            }))
1613        );
1614
1615        assert_eq!(
1616            chess.get(Square::A8),
1617            Ok(Some(Piece {
1618                piece_type: PieceType::ROOK,
1619                color: Color::BLACK
1620            }))
1621        );
1622
1623        assert_eq!(chess.get(Square::C8), Ok(None));
1624        assert_eq!(chess.get(Square::D8), Ok(None));
1625        assert_eq!(chess.castling_rights.black.kingside, true);
1626        assert_eq!(chess.castling_rights.black.queenside, true);
1627    }
1628
1629    #[test]
1630    fn undo_kingside_promotion() {
1631        let mut chess = Chess::new();
1632
1633        // assigning to empty var to ignore warning
1634        let _ = chess.set(Square::E7, PieceType::PAWN, Color::WHITE);
1635
1636        assert_eq!(
1637            chess.make_move(Move {
1638                from: Square::E7,
1639                to: Square::E8,
1640                promotion_piece: Some(Piece {
1641                    piece_type: PieceType::QUEEN,
1642                    color: Color::WHITE
1643                })
1644            }),
1645            Ok(())
1646        );
1647
1648        assert_eq!(chess.undo_move(), Ok(()));
1649
1650        assert_eq!(
1651            chess.get(Square::E7),
1652            Ok(Some(Piece {
1653                piece_type: PieceType::PAWN,
1654                color: Color::WHITE
1655            }))
1656        );
1657
1658        assert_eq!(chess.get(Square::E8), Ok(None));
1659    }
1660
1661    #[test]
1662    fn castle_kingside_successfully() {
1663        let mut chess = Chess::new();
1664
1665        // assigning to empty var to ignore warning
1666        let _ = chess.set(Square::E1, PieceType::KING, Color::WHITE);
1667        let _ = chess.set(Square::H1, PieceType::ROOK, Color::WHITE);
1668
1669        assert_eq!(
1670            chess.make_move(Move {
1671                from: Square::E1,
1672                to: Square::G1,
1673                promotion_piece: None
1674            }),
1675            Ok(())
1676        );
1677
1678        assert_eq!(chess.get(Square::H1), Ok(None));
1679
1680        assert_eq!(
1681            chess.get(Square::G1),
1682            Ok(Some(Piece {
1683                piece_type: PieceType::KING,
1684                color: Color::WHITE
1685            }))
1686        );
1687
1688        assert_eq!(
1689            chess.get(Square::F1),
1690            Ok(Some(Piece {
1691                piece_type: PieceType::ROOK,
1692                color: Color::WHITE
1693            }))
1694        );
1695
1696        assert_eq!(chess.castling_rights.white.kingside, false);
1697        assert_eq!(chess.castling_rights.white.queenside, false);
1698    }
1699
1700    #[test]
1701    fn castle_queenside_successfully() {
1702        let mut chess = Chess::new();
1703
1704        // assigning to empty var to ignore warning
1705        let _ = chess.set(Square::E8, PieceType::KING, Color::BLACK);
1706        let _ = chess.set(Square::A8, PieceType::ROOK, Color::BLACK);
1707
1708        assert_eq!(
1709            chess.make_move(Move {
1710                from: Square::E8,
1711                to: Square::C8,
1712                promotion_piece: None
1713            }),
1714            Ok(())
1715        );
1716
1717        assert_eq!(chess.get(Square::A8), Ok(None));
1718
1719        assert_eq!(
1720            chess.get(Square::C8),
1721            Ok(Some(Piece {
1722                piece_type: PieceType::KING,
1723                color: Color::BLACK
1724            }))
1725        );
1726
1727        assert_eq!(
1728            chess.get(Square::D8),
1729            Ok(Some(Piece {
1730                piece_type: PieceType::ROOK,
1731                color: Color::BLACK
1732            }))
1733        );
1734
1735        assert_eq!(chess.castling_rights.black.queenside, false);
1736        assert_eq!(chess.castling_rights.black.kingside, false);
1737    }
1738
1739    #[test]
1740    fn cannot_castle_if_rook_not_in_correct_square() {
1741        let mut chess = Chess::new();
1742
1743        let _ = chess.set(Square::E1, PieceType::KING, Color::WHITE);
1744        let _ = chess.set(Square::H1, PieceType::ROOK, Color::WHITE);
1745        let _ = chess.set(Square::A1, PieceType::ROOK, Color::WHITE);
1746
1747        let _ = chess.make_move(Move {
1748            from: Square::H1,
1749            to: Square::H2,
1750            promotion_piece: None,
1751        });
1752
1753        assert_eq!(chess.castling_rights.white.queenside, true);
1754        assert_eq!(chess.castling_rights.white.kingside, false);
1755
1756        let _ = chess.make_move(Move {
1757            from: Square::A1,
1758            to: Square::D1,
1759            promotion_piece: None,
1760        });
1761
1762        assert_eq!(chess.castling_rights.white.queenside, false);
1763        assert_eq!(chess.castling_rights.white.kingside, false);
1764    }
1765
1766    #[test]
1767    fn promotion() {
1768        let mut chess = Chess::new();
1769
1770        // assigning to empty var to ignore warning
1771        let _ = chess.set(Square::E7, PieceType::PAWN, Color::WHITE);
1772
1773        assert_eq!(
1774            chess.make_move(Move {
1775                from: Square::E7,
1776                to: Square::E8,
1777                promotion_piece: Some(Piece {
1778                    piece_type: PieceType::QUEEN,
1779                    color: Color::WHITE
1780                })
1781            }),
1782            Ok(())
1783        );
1784
1785        assert_eq!(
1786            chess.get(Square::E8),
1787            Ok(Some(Piece {
1788                piece_type: PieceType::QUEEN,
1789                color: Color::WHITE
1790            }))
1791        );
1792
1793        assert_eq!(chess.get(Square::E7), Ok(None))
1794    }
1795}