w_chess/
lib.rs

1mod chess_move;
2mod piece;
3mod square;
4
5pub use chess_move::ChessMove;
6use chess_move::{CastlingType, SanMove};
7pub use piece::Piece;
8use std::collections::HashMap;
9
10pub(crate) use square::*;
11
12#[derive(Debug)]
13pub struct Chessboard {
14    white: u64,
15    black: u64,
16    static_white_attack_mask: u64,
17    static_black_attack_mask: u64,
18
19    pieces: [u64; 6],
20    pseudo_legal_moves: HashMap<u64, u64>,
21    dynamic_piece_squares: Vec<usize>,
22    legal_moves: HashMap<u64, u64>,
23
24    turn: bool,
25    // 0: white king side, 1: white queen side, 2: black king side, 3: black queen side
26    castle_rights: [bool; 4],
27    en_passant_square: Option<u64>,
28    half_move: u32,
29    full_move: u32,
30
31    board_repetitions: HashMap<String, usize>,
32
33    pub history: Vec<ChessMove>,
34}
35
36impl Chessboard {
37    /// Returns a chessboard with the starting position.
38    /// # Examples
39    /// ```
40    /// use w_chess::Chessboard;
41    /// let mut board = Chessboard::new();
42    /// board.move_to("e4");
43    /// ```
44    pub fn new() -> Self {
45        let mut board = Self::load_fen(START_FEN);
46
47        board.generate_legal_moves();
48
49        board
50    }
51
52    /// Returns a chessboard with the position from the FEN string.
53    /// # Examples
54    /// ```
55    /// use w_chess::Chessboard;
56    /// let mut board = Chessboard::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
57    /// board.move_to("e4");
58    /// ```
59    pub fn from_fen(fen: &str) -> Self {
60        let mut board = Self::load_fen(fen);
61
62        board.generate_legal_moves();
63
64        board
65    }
66
67    fn get_color(&self, color: bool) -> u64 {
68        match color {
69            true => self.white,
70            false => self.black,
71        }
72    }
73
74    fn all(&self) -> u64 {
75        self.white | self.black
76    }
77
78    fn generate_pawn_moves(&self, square: u64) -> u64 {
79        let color = self.white & square != 0;
80
81        match color {
82            true => {
83                let mut mask = 0;
84                if square << 7 & self.black != 0 {
85                    mask |= square << 7;
86                }
87
88                if square << 9 & self.black != 0 {
89                    mask |= square << 9;
90                }
91
92                if square << 8 & self.all() == 0 {
93                    mask |= square << 8;
94                    if square & RANK_2 != 0 && square << 16 & self.all() == 0 {
95                        mask |= square << 16;
96                    }
97                }
98
99                if let Some(en_passant_square) = self.en_passant_square {
100                    if square << 7 == en_passant_square || square << 9 == en_passant_square {
101                        mask |= en_passant_square;
102                    }
103                }
104
105                mask
106            }
107            false => {
108                let mut mask = 0;
109
110                if square >> 7 & self.white != 0 {
111                    mask |= square >> 7;
112                }
113
114                if square >> 9 & self.white != 0 {
115                    mask |= square >> 9;
116                }
117                if square >> 8 & self.all() == 0 {
118                    mask |= square >> 8;
119                    if square & RANK_7 != 0 && square >> 16 & self.all() == 0 {
120                        mask |= square >> 16;
121                    }
122                }
123
124                if let Some(en_passant_square) = self.en_passant_square {
125                    if square >> 7 == en_passant_square || square >> 9 == en_passant_square {
126                        mask |= en_passant_square;
127                    }
128                }
129
130                mask
131            }
132        }
133    }
134
135    fn generate_knight_moves(&self, square: u64) -> u64 {
136        let mut mask = 0;
137
138        // 2 up, 1 left
139        if square & FILE_A == 0 && square & (RANK_8 | RANK_7) == 0 {
140            mask |= square << 15;
141        }
142
143        // 2 up, 1 right
144        if square & FILE_H == 0 && square & (RANK_8 | RANK_7) == 0 {
145            mask |= square << 17;
146        }
147
148        // 2 down, 1 left
149        if square & FILE_A == 0 && square & (RANK_1 | RANK_2) == 0 {
150            mask |= square >> 17;
151        }
152
153        // 2 down, 1 right
154        if square & FILE_H == 0 && square & (RANK_1 | RANK_2) == 0 {
155            mask |= square >> 15;
156        }
157
158        // 2 left, 1 up
159        if square & (FILE_A | FILE_B) == 0 && square & RANK_8 == 0 {
160            mask |= square << 6;
161        }
162
163        // 2 left, 1 down
164        if square & (FILE_A | FILE_B) == 0 && square & RANK_1 == 0 {
165            mask |= square >> 10;
166        }
167
168        // 2 right, 1 up
169        if square & (FILE_H | FILE_G) == 0 && square & RANK_8 == 0 {
170            mask |= square << 10;
171        }
172
173        // 2 right, 1 down
174        if square & (FILE_H | FILE_G) == 0 && square & RANK_1 == 0 {
175            mask |= square >> 6;
176        }
177
178        mask
179    }
180
181    fn generate_rook_moves(&self, square: u64, board: u64) -> u64 {
182        let mut mask = 0;
183
184        let mut up = square;
185        while up & RANK_8 == 0 {
186            up <<= 8;
187            mask |= up;
188            if up & board != 0 {
189                break;
190            }
191        }
192
193        let mut down = square;
194        while down & RANK_1 == 0 {
195            down >>= 8;
196            mask |= down;
197            if down & board != 0 {
198                break;
199            }
200        }
201
202        let mut left = square;
203        while left & FILE_A == 0 {
204            left >>= 1;
205            mask |= left;
206            if left & board != 0 {
207                break;
208            }
209        }
210
211        let mut right = square;
212        while right & FILE_H == 0 {
213            right <<= 1;
214            mask |= right;
215            if right & board != 0 {
216                break;
217            }
218        }
219
220        mask
221    }
222
223    fn generate_bishop_moves(&self, square: u64, board: u64) -> u64 {
224        let mut mask = 0;
225
226        let mut up_left = square;
227        while up_left & (RANK_8 | FILE_A) == 0 {
228            up_left <<= 7;
229            mask |= up_left;
230            if up_left & board != 0 {
231                break;
232            }
233        }
234
235        let mut up_right = square;
236        while up_right & (RANK_8 | FILE_H) == 0 {
237            up_right <<= 9;
238            mask |= up_right;
239            if up_right & board != 0 {
240                break;
241            }
242        }
243        let mut down_left = square;
244        while down_left & (RANK_1 | FILE_A) == 0 {
245            down_left >>= 9;
246            mask |= down_left;
247            if down_left & board != 0 {
248                break;
249            }
250        }
251        let mut down_right = square;
252        while down_right & (RANK_1 | FILE_H) == 0 {
253            down_right >>= 7;
254            mask |= down_right;
255            if down_right & board != 0 {
256                break;
257            }
258        }
259
260        mask
261    }
262
263    fn generate_king_moves(&self, square: u64) -> u64 {
264        let mut mask = 0;
265
266        if square & RANK_8 == 0 {
267            mask |= square << 8;
268        }
269
270        if square & RANK_1 == 0 {
271            mask |= square >> 8;
272        }
273
274        if square & FILE_A == 0 {
275            mask |= square << 1;
276        }
277
278        if square & FILE_H == 0 {
279            mask |= square >> 1;
280        }
281
282        if square & (RANK_8 | FILE_A) == 0 {
283            mask |= square << 9;
284        }
285
286        if square & (RANK_8 | FILE_H) == 0 {
287            mask |= square << 7;
288        }
289
290        if square & (RANK_1 | FILE_A) == 0 {
291            mask |= square >> 7;
292        }
293
294        if square & (RANK_1 | FILE_H) == 0 {
295            mask |= square >> 9;
296        }
297
298        mask
299    }
300
301    fn generate_queen_moves(&self, square: u64, board: u64) -> u64 {
302        self.generate_rook_moves(square, board) | self.generate_bishop_moves(square, board)
303    }
304
305    fn generate_pseudo_legal_moves(&mut self) {
306        self.static_white_attack_mask = 0;
307        self.static_black_attack_mask = 0;
308        self.dynamic_piece_squares.clear();
309
310        for i in 0..64 {
311            let square: u64 = 1 << i;
312
313            if self.all() & square != 0 {
314                let color = self.white & square != 0;
315                let piece = self.get_piece(square);
316                match piece {
317                    Piece::PAWN => {
318                        match color {
319                            true => {
320                                self.static_white_attack_mask |= square << 7;
321                                self.static_white_attack_mask |= square << 9;
322                            }
323                            false => {
324                                self.static_black_attack_mask |= square >> 7;
325                                self.static_black_attack_mask |= square >> 9;
326                            }
327                        }
328                        self.pseudo_legal_moves
329                            .insert(square, self.generate_pawn_moves(square));
330                    }
331
332                    Piece::BISHOP | Piece::ROOK | Piece::QUEEN => {
333                        let mask = match piece {
334                            Piece::BISHOP => self.generate_bishop_moves(square, self.all()),
335                            Piece::ROOK => self.generate_rook_moves(square, self.all()),
336                            Piece::QUEEN => self.generate_queen_moves(square, self.all()),
337                            _ => unreachable!(),
338                        };
339
340                        self.dynamic_piece_squares.push(i);
341                        self.pseudo_legal_moves
342                            .insert(square, mask & !self.get_color(color));
343                    }
344                    Piece::KNIGHT => {
345                        let mask = self.generate_knight_moves(square);
346
347                        match color {
348                            true => {
349                                self.static_white_attack_mask |= mask;
350                            }
351                            false => {
352                                self.static_black_attack_mask |= mask;
353                            }
354                        }
355                        self.pseudo_legal_moves
356                            .insert(square, mask & !self.get_color(color));
357                    }
358                    Piece::KING => {
359                        let mut mask = self.generate_king_moves(square);
360                        match color {
361                            true => {
362                                self.static_white_attack_mask |= mask;
363
364                                if self.turn {
365                                    if self.castle_rights[0]
366                                        && self.all() & WHITE_KING_SIDE_CASTLE == 0
367                                    {
368                                        mask |= 1 << Square::G1 as u64;
369                                    }
370
371                                    if self.castle_rights[1]
372                                        && self.all() & WHITE_QUEEN_SIDE_CASTLE == 0
373                                    {
374                                        mask |= 1 << Square::C1 as u64;
375                                    }
376                                }
377                            }
378                            false => {
379                                self.static_black_attack_mask |= mask;
380
381                                if !self.turn {
382                                    if self.castle_rights[2]
383                                        && self.all() & BLACK_KING_SIDE_CASTLE == 0
384                                    {
385                                        mask |= 1 << Square::G8 as u64;
386                                    }
387
388                                    if self.castle_rights[3]
389                                        && self.all() & BLACK_QUEEN_SIDE_CASTLE == 0
390                                    {
391                                        mask |= 1 << Square::C8 as u64;
392                                    }
393                                }
394                            }
395                        }
396
397                        self.pseudo_legal_moves
398                            .insert(square, mask & !self.get_color(color));
399                    }
400                    _ => {
401                        self.pseudo_legal_moves.insert(square, 0);
402                    }
403                }
404            } else {
405                self.pseudo_legal_moves.insert(square, 0);
406            }
407        }
408    }
409
410    fn generate_legal_moves(&mut self) {
411        self.generate_pseudo_legal_moves();
412
413        for (&square, &moves) in self.pseudo_legal_moves.iter() {
414            let current_square: u64 = square.into();
415            let color = self.white & current_square != 0;
416            let piece = self.get_piece(current_square);
417            if current_square & self.get_color(self.turn) == 0 {
418                self.legal_moves.insert(square, 0);
419                continue;
420            }
421
422            let mut legal_moves = 0;
423            let current_board_without_piece = self.all() & !current_square;
424
425            for potential_square in Self::get_squares(moves) {
426                let board = current_board_without_piece | potential_square;
427                let enemy_attack_mask = self.get_attack_mask(color, board);
428
429                match piece {
430                    Piece::KING => match color {
431                        true => {
432                            let g1: u64 = Square::G1.into();
433                            let c1: u64 = Square::C1.into();
434                            if potential_square & g1 != 0 {
435                                if enemy_attack_mask & WHITE_KING_SIDE_CASTLE == 0 {
436                                    legal_moves |= potential_square;
437                                }
438                            } else if potential_square & c1 != 0 {
439                                if enemy_attack_mask & WHITE_QUEEN_SIDE_CASTLE == 0 {
440                                    legal_moves |= potential_square;
441                                }
442                            } else {
443                                if enemy_attack_mask & potential_square == 0 {
444                                    legal_moves |= potential_square;
445                                }
446                            }
447                        }
448                        false => {
449                            let g8: u64 = Square::G8.into();
450                            let c8: u64 = Square::C8.into();
451                            if potential_square & g8 != 0 {
452                                if enemy_attack_mask & BLACK_KING_SIDE_CASTLE == 0 {
453                                    legal_moves |= potential_square;
454                                }
455                            } else if potential_square & c8 != 0 {
456                                if enemy_attack_mask & BLACK_QUEEN_SIDE_CASTLE == 0 {
457                                    legal_moves |= potential_square;
458                                }
459                            } else {
460                                if enemy_attack_mask & potential_square == 0 {
461                                    legal_moves |= potential_square;
462                                }
463                            }
464                        }
465                    },
466                    _ => {
467                        let king = self.pieces[Piece::KING as usize] & self.get_color(color);
468                        match color {
469                            true => {
470                                if enemy_attack_mask & king == 0 {
471                                    legal_moves |= potential_square;
472                                }
473                            }
474                            false => {
475                                if enemy_attack_mask & king == 0 {
476                                    legal_moves |= potential_square;
477                                }
478                            }
479                        }
480                    }
481                };
482            }
483            self.legal_moves.insert(square, legal_moves);
484        }
485    }
486
487    fn get_piece(&self, square: u64) -> Piece {
488        if self.pieces[Piece::PAWN as usize] & square != 0 {
489            return Piece::PAWN;
490        }
491
492        if self.pieces[Piece::KNIGHT as usize] & square != 0 {
493            return Piece::KNIGHT;
494        }
495
496        if self.pieces[Piece::BISHOP as usize] & square != 0 {
497            return Piece::BISHOP;
498        }
499
500        if self.pieces[Piece::ROOK as usize] & square != 0 {
501            return Piece::ROOK;
502        }
503
504        if self.pieces[Piece::QUEEN as usize] & square != 0 {
505            return Piece::QUEEN;
506        }
507
508        if self.pieces[Piece::KING as usize] & square != 0 {
509            return Piece::KING;
510        }
511
512        Piece::UNKNOWN
513    }
514
515    fn load_fen(fen: &str) -> Self {
516        let mut white = 0;
517        let mut black = 0;
518        let mut pieces = [0, 0, 0, 0, 0, 0];
519        let static_white_attack_mask = 0;
520        let static_black_attack_mask = 0;
521        let mut castle_rights = [false; 4];
522        let mut turn = true;
523        let mut en_passant_square = None;
524        let mut half_move = 0;
525        let mut full_move = 0;
526
527        for (index, rank) in fen.split('/').enumerate() {
528            let mut file: u64 = 0;
529            if index == 7 {
530                let parts: Vec<&str> = rank.split(' ').collect();
531                turn = parts[1] == "w";
532                for castle_right in parts[2].chars() {
533                    match castle_right {
534                        'K' => castle_rights[0] = true,
535                        'Q' => castle_rights[1] = true,
536                        'k' => castle_rights[2] = true,
537                        'q' => castle_rights[3] = true,
538                        _ => {}
539                    }
540                }
541                if parts[3] != "-" {
542                    en_passant_square = Some(Square::from(parts[3]) as u64);
543                }
544
545                half_move = parts[4].parse().unwrap();
546                full_move = parts[5].parse().unwrap();
547            }
548            for c in rank.chars() {
549                if file > 7 {
550                    break;
551                }
552                if c.is_digit(10) {
553                    file += c.to_digit(10).unwrap() as u64;
554                } else {
555                    let square = 1 << 56 - (index as u64) * 8 + file;
556                    let color = c.is_uppercase();
557                    match c.to_ascii_lowercase() {
558                        'p' => {
559                            pieces[Piece::PAWN as usize] |= square;
560                            match color {
561                                true => white |= square,
562                                false => black |= square,
563                            }
564                        }
565                        'n' => {
566                            pieces[Piece::KNIGHT as usize] |= square;
567                            match color {
568                                true => white |= square,
569                                false => black |= square,
570                            }
571                        }
572                        'b' => {
573                            pieces[Piece::BISHOP as usize] |= square;
574                            match color {
575                                true => white |= square,
576                                false => black |= square,
577                            }
578                        }
579                        'r' => {
580                            pieces[Piece::ROOK as usize] |= square;
581                            match color {
582                                true => white |= square,
583                                false => black |= square,
584                            }
585                        }
586                        'q' => {
587                            pieces[Piece::QUEEN as usize] |= square;
588                            match color {
589                                true => white |= square,
590                                false => black |= square,
591                            }
592                        }
593                        'k' => {
594                            pieces[Piece::KING as usize] |= square;
595                            match color {
596                                true => white |= square,
597                                false => black |= square,
598                            }
599                        }
600                        _ => {}
601                    }
602                    file += 1;
603                }
604            }
605        }
606
607        Self {
608            white,
609            static_white_attack_mask,
610            black,
611            static_black_attack_mask,
612            pieces,
613            legal_moves: HashMap::new(),
614            pseudo_legal_moves: HashMap::new(),
615            dynamic_piece_squares: Vec::new(),
616            castle_rights,
617            turn,
618            en_passant_square,
619            half_move,
620            full_move,
621            history: Vec::new(),
622            board_repetitions: HashMap::from([(
623                fen.split_whitespace().next().unwrap().to_string(),
624                1,
625            )]),
626        }
627    }
628
629    /// Returns the FEN string of the current position.
630    /// # Examples
631    /// ```
632    /// use w_chess::Chessboard;
633    /// let board = Chessboard::new();
634    /// assert_eq!(board.get_fen(), "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
635    /// ```
636    pub fn get_fen(&self) -> String {
637        let mut fen = String::new();
638        for rank in 0..8 {
639            let mut empty = 0;
640            for file in 0..8 {
641                let square = 1 << 56 - rank as u64 * 8 + file;
642                let color = self.white & square != 0;
643                match self.get_piece(square) {
644                    Piece::PAWN => {
645                        if empty > 0 {
646                            fen.push_str(&empty.to_string());
647                            empty = 0;
648                        }
649                        match color {
650                            true => fen.push('P'),
651                            false => fen.push('p'),
652                        }
653                    }
654                    Piece::KNIGHT => {
655                        if empty > 0 {
656                            fen.push_str(&empty.to_string());
657                            empty = 0;
658                        }
659                        match color {
660                            true => fen.push('N'),
661                            false => fen.push('n'),
662                        }
663                    }
664                    Piece::BISHOP => {
665                        if empty > 0 {
666                            fen.push_str(&empty.to_string());
667                            empty = 0;
668                        }
669                        match color {
670                            true => fen.push('B'),
671                            false => fen.push('b'),
672                        }
673                    }
674                    Piece::ROOK => {
675                        if empty > 0 {
676                            fen.push_str(&empty.to_string());
677                            empty = 0;
678                        }
679                        match color {
680                            true => fen.push('R'),
681                            false => fen.push('r'),
682                        }
683                    }
684                    Piece::QUEEN => {
685                        if empty > 0 {
686                            fen.push_str(&empty.to_string());
687                            empty = 0;
688                        }
689                        match color {
690                            true => fen.push('Q'),
691                            false => fen.push('q'),
692                        }
693                    }
694                    Piece::KING => {
695                        if empty > 0 {
696                            fen.push_str(&empty.to_string());
697                            empty = 0;
698                        }
699                        match color {
700                            true => fen.push('K'),
701                            false => fen.push('k'),
702                        }
703                    }
704                    _ => {
705                        empty += 1;
706                    }
707                }
708            }
709            if empty > 0 {
710                fen.push_str(&empty.to_string());
711            }
712            if rank < 7 {
713                fen.push('/');
714            }
715        }
716
717        let turn = if self.turn { "w" } else { "b" };
718
719        let mut castle_rights = String::new();
720
721        if self.castle_rights[0] {
722            castle_rights.push('K');
723        }
724        if self.castle_rights[1] {
725            castle_rights.push('Q');
726        }
727        if self.castle_rights[2] {
728            castle_rights.push('k');
729        }
730        if self.castle_rights[3] {
731            castle_rights.push('q');
732        }
733
734        if castle_rights.is_empty() {
735            castle_rights = "-".to_string();
736        }
737
738        let en_passant_square = match self.en_passant_square {
739            Some(square) => Square::from(square).to_string(),
740            None => "-".to_string(),
741        };
742
743        format!(
744            "{} {} {} {} {} {}",
745            fen, turn, castle_rights, en_passant_square, self.half_move, self.full_move
746        )
747    }
748
749    /// Returns if the current position is checked.
750    /// # Examples
751    /// ```
752    /// use w_chess::Chessboard;
753    /// let board = Chessboard::new();
754    /// assert_eq!(board.is_checked(true), false);
755    /// ```
756    pub fn is_checked(&self, color: bool) -> bool {
757        let king = self.pieces[Piece::KING as usize] & self.get_color(color);
758        let enemy_attack_mask = self.get_attack_mask(color, self.all());
759
760        enemy_attack_mask & king != 0
761    }
762
763    /// Returns if the current position is a checkmate.
764    /// # Examples
765    /// ```
766    /// use w_chess::Chessboard;
767    /// let board = Chessboard::new();
768    /// assert_eq!(board.is_mate(true), false);
769    /// ```
770    pub fn is_mate(&self, color: bool) -> bool {
771        self.is_checked(color) && !self.has_moves()
772    }
773
774    /// Returns if the current position is a stalemate.
775    /// # Examples
776    /// ```
777    /// use w_chess::Chessboard;
778    /// let board = Chessboard::new();
779    /// assert_eq!(board.is_stalemate(true), false);
780    /// ```
781    pub fn is_stalemate(&self, color: bool) -> bool {
782        !self.is_checked(color) && !self.has_moves()
783    }
784
785    /// Returns if the current position is a fifty moves rule.
786    /// # Examples
787    /// ```
788    /// use w_chess::Chessboard;
789    /// let board = Chessboard::new();
790    /// assert_eq!(board.is_fifty_moves(), false);
791    /// ```
792    pub fn is_fifty_moves(&self) -> bool {
793        self.half_move >= 100
794    }
795
796    /// Returns if the current position is a threefold repetition.
797    /// # Examples
798    /// ```
799    /// use w_chess::Chessboard;
800    /// let board = Chessboard::new();
801    /// assert_eq!(board.is_threefold_repetition(), false);
802    /// ``
803    pub fn is_threefold_repetition(&self) -> bool {
804        let mut max = 0;
805
806        for (_, &count) in self.board_repetitions.iter() {
807            if count > max {
808                max = count;
809            }
810        }
811
812        max >= 3
813    }
814
815    fn has_moves(&self) -> bool {
816        for &legal_moves in self.legal_moves.values() {
817            if legal_moves != 0 {
818                return true;
819            }
820        }
821        false
822    }
823
824    fn get_attack_mask(&self, color: bool, board: u64) -> u64 {
825        let mut enemy_attack_mask = match color {
826            true => self.static_black_attack_mask,
827            false => self.static_white_attack_mask,
828        };
829
830        for &dynamic_piece in self.dynamic_piece_squares.iter() {
831            let dynamic_piece_square = 1 << dynamic_piece;
832            let piece = self.get_piece(dynamic_piece_square);
833            let dynamic_piece_color = self.white & dynamic_piece_square != 0;
834
835            if dynamic_piece_color == color {
836                continue;
837            }
838
839            match piece {
840                Piece::BISHOP => {
841                    enemy_attack_mask |= self.generate_bishop_moves(dynamic_piece_square, board);
842                }
843                Piece::ROOK => {
844                    enemy_attack_mask |= self.generate_rook_moves(dynamic_piece_square, board);
845                }
846                Piece::QUEEN => {
847                    enemy_attack_mask |= self.generate_queen_moves(dynamic_piece_square, board);
848                }
849                _ => {}
850            }
851        }
852
853        enemy_attack_mask
854    }
855
856    /// Moves a piece to the given square in SAN format.
857    /// # Examples
858    /// ```
859    /// use w_chess::Chessboard;
860    /// let mut board = Chessboard::new();
861    /// board.move_to("e4");
862    /// ```
863    pub fn move_to(&mut self, san: &str) {
864        let mut san = SanMove::parse(san);
865
866        let mut has_moved = false;
867        if let Ok(ref mut valid_san) = san {
868            if let Some(castling) = valid_san.castling {
869                match castling {
870                    CastlingType::KingSide => match self.turn {
871                        true => {
872                            if let Some(legal_moves) = self.legal_moves.get(&Square::E1.into()) {
873                                let g1: u64 = Square::G1.into();
874
875                                if legal_moves & g1 != 0 {
876                                    self.half_move += 1;
877                                    let e1: u64 = Square::E1.into();
878
879                                    self.pieces[Piece::KING as usize] ^= e1;
880                                    self.pieces[Piece::KING as usize] |= g1;
881                                    self.white ^= e1;
882                                    self.white |= g1;
883
884                                    let f1: u64 = Square::F1.into();
885                                    let h1: u64 = Square::H1.into();
886
887                                    self.pieces[Piece::ROOK as usize] ^= h1;
888                                    self.pieces[Piece::ROOK as usize] |= f1;
889                                    self.white ^= h1;
890                                    self.white |= f1;
891
892                                    self.turn = !self.turn;
893                                    self.castle_rights[0] = false;
894                                    self.castle_rights[1] = false;
895                                    has_moved = true;
896                                }
897                            }
898                        }
899                        false => {
900                            if let Some(legal_moves) = self.legal_moves.get(&Square::E8.into()) {
901                                let g8: u64 = Square::G8.into();
902
903                                if legal_moves & g8 != 0 {
904                                    self.half_move += 1;
905                                    let e8: u64 = Square::E8.into();
906
907                                    self.pieces[Piece::KING as usize] ^= e8;
908                                    self.pieces[Piece::KING as usize] |= g8;
909                                    self.black ^= e8;
910                                    self.black |= g8;
911
912                                    let f8: u64 = Square::F8.into();
913                                    let h8: u64 = Square::H8.into();
914
915                                    self.pieces[Piece::ROOK as usize] ^= h8;
916                                    self.pieces[Piece::ROOK as usize] |= f8;
917                                    self.black ^= h8;
918                                    self.black |= f8;
919
920                                    self.turn = !self.turn;
921                                    self.full_move += 1;
922                                    self.castle_rights[2] = false;
923                                    self.castle_rights[3] = false;
924                                    has_moved = true;
925                                }
926                            }
927                        }
928                    },
929                    CastlingType::QueenSide => match self.turn {
930                        true => {
931                            if let Some(legal_moves) = self.legal_moves.get(&Square::E1.into()) {
932                                let c1: u64 = Square::C1.into();
933
934                                if legal_moves & c1 != 0 {
935                                    self.half_move += 1;
936                                    let e1: u64 = Square::E1.into();
937
938                                    self.pieces[Piece::KING as usize] ^= e1;
939                                    self.pieces[Piece::KING as usize] |= c1;
940                                    self.white ^= e1;
941                                    self.white |= c1;
942
943                                    let a1: u64 = Square::A1.into();
944                                    let d1: u64 = Square::D1.into();
945
946                                    self.pieces[Piece::ROOK as usize] ^= a1;
947                                    self.pieces[Piece::ROOK as usize] |= d1;
948                                    self.white ^= a1;
949                                    self.white |= d1;
950
951                                    self.turn = !self.turn;
952                                    self.castle_rights[0] = false;
953                                    self.castle_rights[1] = false;
954                                    has_moved = true;
955                                }
956                            }
957                        }
958                        false => {
959                            if let Some(legal_moves) = self.legal_moves.get(&Square::E8.into()) {
960                                let c8: u64 = Square::C8.into();
961
962                                if legal_moves & c8 != 0 {
963                                    self.half_move += 1;
964                                    let e8: u64 = Square::E8.into();
965
966                                    self.pieces[Piece::KING as usize] ^= e8;
967                                    self.pieces[Piece::KING as usize] |= c8;
968                                    self.black ^= e8;
969                                    self.black |= c8;
970
971                                    let d8: u64 = Square::D8.into();
972                                    let a8: u64 = Square::A8.into();
973
974                                    self.pieces[Piece::ROOK as usize] ^= a8;
975                                    self.pieces[Piece::ROOK as usize] |= d8;
976                                    self.black ^= a8;
977                                    self.black |= d8;
978
979                                    self.turn = !self.turn;
980                                    self.full_move += 1;
981                                    self.castle_rights[2] = false;
982                                    self.castle_rights[3] = false;
983                                    has_moved = true;
984                                }
985                            }
986                        }
987                    },
988                }
989            } else if let Some(promotion_piece) = valid_san.promotion {
990                'search: for (&from_square, &legal_moves) in self.legal_moves.iter() {
991                    let piece = self.get_piece(from_square);
992                    let color = self.white & from_square != 0;
993
994                    if self.turn != color {
995                        continue;
996                    }
997
998                    match piece {
999                        Piece::PAWN => {
1000                            for valid_square in Chessboard::get_squares(legal_moves) {
1001                                if valid_square & valid_san.to != 0 {
1002                                    self.half_move = 0;
1003
1004                                    let before = self.get_fen();
1005                                    let mut captured = None;
1006
1007                                    self.pieces[piece as usize] ^= from_square;
1008
1009                                    match color {
1010                                        true => {
1011                                            self.white ^= from_square;
1012                                            self.white |= valid_square;
1013
1014                                            if self.black & valid_square != 0 {
1015                                                let captured_piece = self.get_piece(valid_square);
1016                                                self.pieces[captured_piece as usize] ^=
1017                                                    valid_square;
1018                                                self.black ^= valid_square;
1019                                                captured = Some(captured_piece);
1020                                            }
1021                                        }
1022                                        false => {
1023                                            self.black ^= from_square;
1024                                            self.black |= valid_square;
1025
1026                                            if self.white & valid_square != 0 {
1027                                                let captured_piece = self.get_piece(valid_square);
1028                                                self.pieces[captured_piece as usize] ^=
1029                                                    valid_square;
1030                                                self.white ^= valid_square;
1031                                                captured = Some(captured_piece);
1032                                            }
1033                                        }
1034                                    }
1035
1036                                    self.pieces[promotion_piece as usize] |= valid_square;
1037
1038                                    let after = self.get_fen();
1039
1040                                    self.board_repetitions
1041                                        .entry(after.clone())
1042                                        .and_modify(|count| *count += 1)
1043                                        .or_insert(1);
1044
1045                                    self.history.push(ChessMove::new(
1046                                        valid_san,
1047                                        self.turn,
1048                                        before,
1049                                        after,
1050                                        from_square,
1051                                        captured,
1052                                    ));
1053
1054                                    self.turn = !self.turn;
1055                                    has_moved = true;
1056                                    break 'search;
1057                                }
1058                            }
1059                        }
1060                        _ => {
1061                            continue;
1062                        }
1063                    }
1064                }
1065            } else {
1066                'search: for (&from_square, &legal_moves) in self.legal_moves.iter() {
1067                    let piece = self.get_piece(from_square);
1068
1069                    if (piece != valid_san.piece)
1070                        || (valid_san.from > 0 && from_square & valid_san.from == 0)
1071                    {
1072                        continue;
1073                    }
1074
1075                    for valid_square in Chessboard::get_squares(legal_moves) {
1076                        if valid_square & valid_san.to != 0 {
1077                            let before = self.get_fen();
1078                            let mut captured = None;
1079
1080                            if let Some(en_passant_square) = self.en_passant_square.take() {
1081                                if valid_square == en_passant_square && piece == Piece::PAWN {
1082                                    self.pieces[Piece::PAWN as usize] ^= en_passant_square;
1083                                    match self.turn {
1084                                        true => {
1085                                            self.black ^= en_passant_square >> 8;
1086                                            self.pieces[Piece::PAWN as usize] ^=
1087                                                en_passant_square >> 8;
1088                                        }
1089                                        false => {
1090                                            self.white ^= en_passant_square << 8;
1091                                            self.pieces[Piece::PAWN as usize] ^=
1092                                                en_passant_square << 8;
1093                                        }
1094                                    }
1095                                }
1096                            }
1097
1098                            match piece {
1099                                Piece::KING => match self.turn {
1100                                    true => {
1101                                        self.castle_rights[0] = false;
1102                                        self.castle_rights[1] = false;
1103                                    }
1104                                    false => {
1105                                        self.castle_rights[2] = false;
1106                                        self.castle_rights[3] = false;
1107                                    }
1108                                },
1109                                Piece::ROOK => match self.turn {
1110                                    true => {
1111                                        let h1: u64 = Square::H1.into();
1112                                        let a1: u64 = Square::A1.into();
1113                                        if from_square & h1 != 0 {
1114                                            self.castle_rights[0] = false;
1115                                        } else if from_square & a1 != 0 {
1116                                            self.castle_rights[1] = false;
1117                                        }
1118                                    }
1119                                    false => {
1120                                        let h8: u64 = Square::H8.into();
1121                                        let a8: u64 = Square::A8.into();
1122                                        if from_square & h8 != 0 {
1123                                            self.castle_rights[2] = false;
1124                                        } else if from_square & a8 != 0 {
1125                                            self.castle_rights[3] = false;
1126                                        }
1127                                    }
1128                                },
1129                                Piece::PAWN => match self.turn {
1130                                    true => {
1131                                        if from_square & RANK_2 != 0 {
1132                                            if valid_square & RANK_4 != 0 {
1133                                                let black_pawns =
1134                                                    self.pieces[Piece::PAWN as usize] & self.black;
1135                                                if valid_square & FILE_A == 0
1136                                                    && black_pawns & (valid_square >> 1) != 0
1137                                                {
1138                                                    self.en_passant_square =
1139                                                        Some(valid_square >> 8);
1140                                                }
1141
1142                                                if valid_square & FILE_H == 0
1143                                                    && black_pawns & (valid_square << 1) != 0
1144                                                {
1145                                                    self.en_passant_square =
1146                                                        Some(valid_square >> 8);
1147                                                }
1148                                            }
1149                                        }
1150                                    }
1151                                    false => {
1152                                        if from_square & RANK_7 != 0 {
1153                                            if valid_square & RANK_5 != 0 {
1154                                                let white_pawns =
1155                                                    self.pieces[Piece::PAWN as usize] & self.white;
1156                                                if valid_square & FILE_A == 0
1157                                                    && white_pawns & valid_square >> 1 != 0
1158                                                {
1159                                                    self.en_passant_square =
1160                                                        Some(valid_square << 8);
1161                                                }
1162
1163                                                if valid_square & FILE_H == 0
1164                                                    && white_pawns & valid_square << 1 != 0
1165                                                {
1166                                                    self.en_passant_square =
1167                                                        Some(valid_square << 8);
1168                                                }
1169                                            }
1170                                        }
1171                                    }
1172                                },
1173                                _ => {}
1174                            }
1175
1176                            self.pieces[piece as usize] ^= from_square;
1177
1178                            match self.turn {
1179                                true => {
1180                                    self.white ^= from_square;
1181                                    self.white |= valid_square;
1182
1183                                    if self.black & valid_square != 0 {
1184                                        let captured_piece = self.get_piece(valid_square);
1185                                        self.pieces[captured_piece as usize] ^= valid_square;
1186                                        self.black ^= valid_square;
1187                                        self.half_move = 0;
1188                                        captured = Some(captured_piece);
1189                                    } else {
1190                                        match piece {
1191                                            Piece::PAWN => {
1192                                                self.half_move = 0;
1193                                            }
1194                                            _ => {
1195                                                self.half_move += 1;
1196                                            }
1197                                        }
1198                                    }
1199                                }
1200                                false => {
1201                                    self.black ^= from_square;
1202                                    self.black |= valid_square;
1203
1204                                    if self.white & valid_square != 0 {
1205                                        let captured_piece = self.get_piece(valid_square);
1206                                        self.pieces[captured_piece as usize] ^= valid_square;
1207                                        self.white ^= valid_square;
1208                                        self.half_move = 0;
1209                                        captured = Some(captured_piece);
1210                                    } else {
1211                                        match piece {
1212                                            Piece::PAWN => {
1213                                                self.half_move = 0;
1214                                            }
1215                                            _ => {
1216                                                self.half_move += 1;
1217                                            }
1218                                        }
1219                                    }
1220
1221                                    self.full_move += 1;
1222                                }
1223                            }
1224
1225                            self.pieces[piece as usize] |= valid_square;
1226                            let after = self.get_fen();
1227
1228                            self.board_repetitions
1229                                .entry(after.split_whitespace().next().unwrap().to_string())
1230                                .and_modify(|count| *count += 1)
1231                                .or_insert(1);
1232
1233                            self.history.push(ChessMove::new(
1234                                valid_san,
1235                                self.turn,
1236                                before,
1237                                after,
1238                                from_square,
1239                                captured,
1240                            ));
1241
1242                            self.turn = !self.turn;
1243                            has_moved = true;
1244                            break 'search;
1245                        }
1246                    }
1247                }
1248            }
1249            if has_moved {
1250                self.generate_legal_moves();
1251            } else {
1252                println!(
1253                    "Invalid move {} to {}",
1254                    valid_san.piece,
1255                    Square::from(valid_san.to)
1256                );
1257            }
1258        }
1259    }
1260
1261    fn get_squares(bitboard: u64) -> Vec<u64> {
1262        let mut squares = Vec::new();
1263        for i in 0..64 {
1264            let square = 1 << i;
1265            if bitboard & square != 0 {
1266                squares.push(square);
1267            }
1268        }
1269        squares
1270    }
1271
1272    /// Returns the ASCII representation of the current position.
1273    /// # Examples
1274    /// ```
1275    /// use w_chess::Chessboard;
1276    /// let board = Chessboard::new();
1277    /// println!("{}", board.ascii());
1278    /// ```
1279    pub fn ascii(&self) -> String {
1280        let mut board = String::new();
1281        for rank in 0..8 {
1282            for file in 0..8 {
1283                let square = 1 << 56 - rank as u64 * 8 + file;
1284                let piece = self.get_piece(square);
1285                let color = self.white & square != 0;
1286                match piece {
1287                    Piece::PAWN => match color {
1288                        true => {
1289                            board.push('P');
1290                        }
1291                        false => {
1292                            board.push('p');
1293                        }
1294                    },
1295                    Piece::KNIGHT => match color {
1296                        true => {
1297                            board.push('N');
1298                        }
1299                        false => {
1300                            board.push('n');
1301                        }
1302                    },
1303                    Piece::BISHOP => match color {
1304                        true => {
1305                            board.push('B');
1306                        }
1307                        false => {
1308                            board.push('b');
1309                        }
1310                    },
1311                    Piece::ROOK => match color {
1312                        true => {
1313                            board.push('R');
1314                        }
1315                        false => {
1316                            board.push('r');
1317                        }
1318                    },
1319                    Piece::QUEEN => match color {
1320                        true => {
1321                            board.push('Q');
1322                        }
1323                        false => {
1324                            board.push('q');
1325                        }
1326                    },
1327                    Piece::KING => match color {
1328                        true => {
1329                            board.push('K');
1330                        }
1331                        false => {
1332                            board.push('k');
1333                        }
1334                    },
1335                    _ => {
1336                        board.push('.');
1337                    }
1338                }
1339            }
1340            board.push('\n');
1341        }
1342        board
1343    }
1344}
1345
1346impl std::fmt::Display for Chessboard {
1347    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1348        write!(f, "{}", self.ascii())
1349    }
1350}
1351
1352#[cfg(test)]
1353mod tests {
1354    use super::*;
1355
1356    #[test]
1357    fn test_generate_moves() {
1358        let board = Chessboard::from_fen("8/4PnK1/4P3/2p3p1/1p2BPk1/P7/2pR2PB/5n2 w - - 0 1");
1359
1360        for (&square, &valid_move) in board.legal_moves.iter() {
1361            let board_square: u64 = square.into();
1362            let color: bool = board.white & board_square != 0;
1363            if color != board.turn {
1364                continue;
1365            }
1366            println!(
1367                "piece : {}, square:{}",
1368                board.get_piece(square.into()),
1369                square
1370            );
1371            for square in Chessboard::get_squares(valid_move) {
1372                println!("valid_move:{}", square);
1373            }
1374        }
1375    }
1376
1377    #[test]
1378    fn get_fen_works() {
1379        let fen = "8/PK4N1/P1p2rn1/7p/1P1B3P/2P5/p1NR4/5k2 w - - 0 1";
1380        let board = Chessboard::from_fen(fen);
1381
1382        assert_eq!(board.get_fen(), fen);
1383    }
1384
1385    #[test]
1386    fn test_mate() {
1387        let board =
1388            Chessboard::from_fen("r1bqkbnr/pppp1Qpp/8/4p3/1nB1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 4");
1389
1390        assert_eq!(board.is_mate(false), true);
1391    }
1392
1393    #[test]
1394    fn test_move_to() {
1395        let mut board = Chessboard::new();
1396
1397        board.move_to("f4");
1398        board.move_to("f5");
1399        board.move_to("Nf3");
1400        board.move_to("Nc6");
1401        board.move_to("e4");
1402        board.move_to("e5");
1403        board.move_to("Qe2");
1404        board.move_to("Bb4");
1405        board.move_to("d4");
1406        board.move_to("c3");
1407        println!("{}", board.get_fen());
1408    }
1409
1410    #[test]
1411    fn test_random_board() {
1412        let mut board =
1413            Chessboard::from_fen("3B4/R2p1P2/3p2p1/5NP1/2K1n3/1Q6/1p3R1p/2n1k3 w - - 0 1");
1414
1415        println!("{}", board.ascii());
1416
1417        board.move_to("Qe3");
1418        board.move_to("Kd1");
1419        board.move_to("Rf1");
1420        board.move_to("Kc2");
1421        board.move_to("Qe4");
1422        board.move_to("Kd2");
1423        board.move_to("Ba5");
1424
1425        println!("{}", board.get_fen());
1426        println!("{}", board.ascii());
1427
1428        assert!(board.is_mate(false));
1429    }
1430
1431    #[test]
1432    fn castle_white_queen_side() {
1433        let fen = "rnb1kbnr/pp2pppp/8/1q6/8/8/P3PPPP/R3K1NR w KQkq - 0 1";
1434        let mut board = Chessboard::from_fen(fen);
1435
1436        board.move_to("O-O-O");
1437
1438        assert_eq!(
1439            board.get_fen(),
1440            "rnb1kbnr/pp2pppp/8/1q6/8/8/P3PPPP/2KR2NR b kq - 1 1"
1441        );
1442    }
1443
1444    #[test]
1445    fn castle_white_king_side() {
1446        let fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPP2P/RNBQK2R w KQkq - 0 1";
1447        let mut board = Chessboard::from_fen(fen);
1448
1449        board.move_to("O-O");
1450
1451        assert_eq!(
1452            board.get_fen(),
1453            "rnbqkbnr/pppppppp/8/8/8/8/PPPPP2P/RNBQ1RK1 b kq - 1 1"
1454        );
1455    }
1456
1457    #[test]
1458    fn castle_black_queen_side() {
1459        let fen = "r3kbnr/p3pppp/8/8/1Q6/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1";
1460        let mut board = Chessboard::from_fen(fen);
1461
1462        board.move_to("O-O-O");
1463
1464        assert_eq!(
1465            board.get_fen(),
1466            "2kr1bnr/p3pppp/8/8/1Q6/8/PPPPPPPP/RNBQKBNR w KQ - 1 2"
1467        );
1468    }
1469
1470    #[test]
1471    fn castle_black_king_side() {
1472        let fen = "rnbqk2r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1";
1473        let mut board = Chessboard::from_fen(fen);
1474
1475        board.move_to("O-O");
1476
1477        assert_eq!(
1478            board.get_fen(),
1479            "rnbq1rk1/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 1 2"
1480        );
1481    }
1482
1483    #[test]
1484    fn test_en_passant() {
1485        let fen = "rnbqkbnr/pppp1ppp/8/8/4p3/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
1486
1487        let mut board = Chessboard::from_fen(fen);
1488
1489        board.move_to("d4");
1490        board.move_to("d3");
1491
1492        println!("{}", board.ascii());
1493        println!("{:?}", board.history);
1494
1495        assert_eq!(
1496            board.get_fen(),
1497            "rnbqkbnr/pppp1ppp/8/8/8/3p4/PPP1PPPP/RNBQKBNR w KQkq - 0 2"
1498        );
1499    }
1500
1501    #[test]
1502    fn test_three_fold() {
1503        let mut board = Chessboard::new();
1504
1505        board.move_to("Nf3");
1506        board.move_to("Nf6");
1507
1508        board.move_to("Ng1");
1509        board.move_to("Ng8");
1510
1511        board.move_to("Nf3");
1512        board.move_to("Nf6");
1513
1514        board.move_to("Ng1");
1515        board.move_to("Ng8");
1516
1517        assert!(board.is_threefold_repetition());
1518        assert_eq!(
1519            board.get_fen(),
1520            "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 8 5"
1521        );
1522    }
1523
1524    #[test]
1525    fn test_promotion() {
1526        let fen = "2b3k1/3PR3/8/8/8/8/8/6K1 w - - 0 1";
1527        let mut board = Chessboard::from_fen(fen);
1528
1529        println!("{}", board.ascii());
1530
1531        board.move_to("dxc8=Q#");
1532
1533        println!("{}", board.ascii());
1534
1535        assert_eq!(board.get_fen(), "2Q3k1/4R3/8/8/8/8/8/6K1 b - - 0 1");
1536
1537        assert!(board.is_mate(false));
1538    }
1539}