chess/
board.rs

1//! Describe the board and interaction with it.
2
3use std::fmt;
4use std::ops::{Index, IndexMut};
5use std::str::FromStr;
6
7use crate::*;
8
9/// A representation of a chess board that implement FEN notation ([`Board::from_str`]).
10///
11/// # Examples
12///
13/// ```
14/// use chess::{Square, Board, Color, Piece, ChessMove};
15///
16/// let mut board = Board::default();
17/// // 8 | r n b q k b n r
18/// // 7 | p p p p p p p p
19/// // 6 | . . . . . . . .
20/// // 5 | . . . . . . . .
21/// // 4 | . . . . . . . .
22/// // 3 | . . . . . . . .
23/// // 2 | P P P P P P P P
24/// // 1 | R N B Q K B N R
25/// //   +----------------
26/// //     A B C D E F G H
27///
28/// assert_eq!(board.on(Square::E8), Some((Piece::King, Color::Black)));
29///
30/// // White move the pawn from E2 to E4
31/// let m = ChessMove::new(Square::E2, Square::E4);
32/// board.update(m);
33/// // 8 | r n b q k b n r
34/// // 7 | p p p p p p p p
35/// // 6 | . . . . . . . .
36/// // 5 | . . . . . . . .
37/// // 4 | . . . . P . . .
38/// // 3 | . . . . . . . .
39/// // 2 | P P P P . P P P
40/// // 1 | R N B Q K B N R
41/// //   +----------------
42/// //     A B C D E F G H
43///
44/// assert_eq!(board.on(Square::E4), Some((Piece::Pawn, Color::White)));
45/// assert_eq!(board.side_to_move(), Color::Black);
46/// ```
47#[derive(Copy, Clone, Eq, PartialEq, Debug)]
48pub struct Board {
49    squares: [Option<(Piece, Color)>; NUM_SQUARES],
50    side_to_move: Color,
51    castle_rights: [CastleRights; NUM_COLORS],
52    en_passant: Option<Square>,
53    halfmoves: u64,
54    fullmoves: u64,
55}
56
57impl Board {
58    /// Create a new empty board.
59    ///
60    /// Consider using the [`Default`] trait to initialize the board.
61    pub fn new() -> Self {
62        Board {
63            squares: [None; NUM_SQUARES],
64            side_to_move: Color::White,
65            castle_rights: [CastleRights::NoRights; NUM_COLORS],
66            en_passant: None,
67            halfmoves: 0,
68            fullmoves: 1,
69        }
70    }
71
72    /// Get the [`Color`] of the player who has to play.
73    pub fn side_to_move(&self) -> Color {
74        self.side_to_move
75    }
76
77    /// Get the [`CastleRights`] for a given side.
78    pub fn castle_rights(&self, color: Color) -> CastleRights {
79        self.castle_rights[color.to_index()]
80    }
81
82    /// Get the [`Square`] (if exist) of the En Passant.
83    pub fn en_passant(&self) -> Option<Square> {
84        self.en_passant
85    }
86
87    /// Get the halfmoves number.
88    pub fn halfmoves(&self) -> u64 {
89        self.halfmoves
90    }
91
92    /// Get the fullmoves number.
93    pub fn fullmoves(&self) -> u64 {
94        self.fullmoves
95    }
96
97    /// Get the [`GameState`] of the [`Board`].
98    pub fn state(&self) -> GameState {
99        let mut state = GameState::Ongoing;
100        if !self.has_any_move() {
101            state = GameState::Stalemate;
102            if self.is_check() {
103                state = GameState::Checkmates(self.side_to_move);
104            }
105        }
106        state
107    }
108
109    /// Check if the [`ChessMove`] is valid. Legality is not verified.
110    pub fn is_valid(&self, m: ChessMove) -> bool {
111        let mut is_valid = false;
112        if let Some(side) = self.color_on(m.from) {
113            if side == self.side_to_move {
114                if self.get_valid_moves(m.from).contains(&m.to) {
115                    is_valid = true;
116                }
117            }
118        }
119        is_valid
120    }
121
122    /// Check if the [`ChessMove`] is legal.
123    pub fn is_legal(&self, m: ChessMove) -> bool {
124        let mut is_legal = false;
125        if let Some(side) = self.color_on(m.from) {
126            if side == self.side_to_move {
127                if self.get_legal_moves(m.from).contains(&m.to) {
128                    is_legal = true;
129                }
130            }
131        }
132        is_legal
133    }
134
135    /// Update the chessboard according to the chess rules.
136    ///
137    /// Assume that the [`ChessMove`] is legal.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use chess::{Board, ChessMove, Square};
143    ///
144    /// let mut board = Board::default();
145    /// let m = ChessMove::new(Square::E2, Square::E4);
146    ///
147    /// board.update(m);
148    /// ```
149    pub fn update(&mut self, m: ChessMove) {
150        let piece_from = self.piece_on(m.from).unwrap();
151        let side = self.side_to_move;
152        let mut new_en_passant = false;
153        let reset_halfmove = self.piece_on_is(m.from, Piece::Pawn) || self.is_occupied(m.to);
154
155        match piece_from {
156            // Pawn: En Passant, promotion
157            Piece::Pawn => {
158                self[m.from] = None;
159                self[m.to] = Some((Piece::Pawn, side));
160                // if En Passant: capture the pawn
161                if self.en_passant == Some(m.to) {
162                    match side {
163                        Color::White => self[m.to.down()] = None,
164                        Color::Black => self[m.to.up()] = None,
165                    }
166                }
167                // Set self.en_passant
168                if m.distance() == 2 {
169                    if self.on_is(m.to.left(), (Piece::Pawn, !side))
170                        || self.on_is(m.to.right(), (Piece::Pawn, !side))
171                    {
172                        self.en_passant = Some(m.to.backward(side));
173                        new_en_passant = true;
174                    }
175                } else {
176                    self.en_passant = None;
177                }
178
179                // Promotion
180                if m.to.rank_for(side) == Rank::Eighth {
181                    self[m.to] = Some((Piece::Queen, side));
182                }
183            }
184            // King: Castle
185            Piece::King => {
186                if m.from.distance(m.to) == 2 {
187                    if self.castle_rights(side).has_kingside() && m.from.file() < m.to.file() {
188                        // if is a Castle - Kingside
189                        self[m.from] = None;
190                        self[m.to] = Some((Piece::King, side));
191                        self[m.to.right()] = None;
192                        self[m.to.left()] = Some((Piece::Rook, side));
193                    } else if self.castle_rights(side).has_queenside()
194                        && m.to.file() < m.from.file()
195                    {
196                        // if is a Castle - Queenside
197                        self[m.from] = None;
198                        self[m.to] = Some((Piece::King, side));
199                        self[m.to.left().left()] = None;
200                        self[m.to.right()] = Some((Piece::Rook, side));
201                    } else {
202                        panic!("Error::InvalidMove: Board: {}, invalid_move: {}", self, m);
203                    }
204                } else {
205                    // normal move
206                    self[m.from] = None;
207                    self[m.to] = Some((Piece::King, side));
208                }
209
210                // If the king move he lost both CastleRights
211                self.remove_castle_rights(side, CastleRights::Both);
212            }
213            // Rook: Castle
214            Piece::Rook => {
215                // remove CastleRights
216                match m.from {
217                    Square::A1 | Square::A8 => {
218                        self.remove_castle_rights(side, CastleRights::QueenSide)
219                    }
220                    Square::H1 | Square::H8 => {
221                        self.remove_castle_rights(side, CastleRights::KingSide)
222                    }
223                    _ => {}
224                }
225                self[m.from] = None;
226                self[m.to] = Some((Piece::Rook, side));
227            }
228            _ => {
229                self[m.from] = None;
230                self[m.to] = Some((piece_from, side));
231            }
232        }
233
234        self.side_to_move = !self.side_to_move;
235        if !new_en_passant {
236            self.en_passant = None;
237        }
238        self.halfmoves += 1;
239        if reset_halfmove {
240            self.halfmoves = 0;
241        }
242        if self.side_to_move == Color::White {
243            self.fullmoves += 1;
244        }
245    }
246
247    /// Remove [`CastleRights`] for a particular side.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use chess::{Board, CastleRights, Color};
253    /// let mut board = Board::default();
254    ///
255    /// assert_eq!(board.castle_rights(Color::White), CastleRights::Both);
256    /// assert_eq!(board.castle_rights(Color::Black), CastleRights::Both);
257    ///
258    /// board.remove_castle_rights(Color::White, CastleRights::QueenSide);
259    /// board.remove_castle_rights(Color::Black, CastleRights::Both);
260    ///
261    /// assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide);
262    /// assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights);
263    /// ```
264    pub fn remove_castle_rights(&mut self, color: Color, remove: CastleRights) {
265        let index = self.castle_rights(color).to_index() & !remove.to_index();
266        self.castle_rights[color.to_index()] = CastleRights::from_index(index);
267    }
268
269    /// Get the [`Piece`] at a given [`Square`].
270    pub fn piece_on(&self, square: Square) -> Option<Piece> {
271        self.squares[square.to_index()].map(|(piece, _)| piece)
272    }
273
274    /// Verify if the [`Square`] is occupied by the given [`Piece`].
275    pub fn piece_on_is(&self, square: Square, piece: Piece) -> bool {
276        matches!(self.piece_on(square), Some(real_piece) if real_piece == piece)
277    }
278
279    /// Get the [`Color`] at a given [`Square`].
280    pub fn color_on(&self, square: Square) -> Option<Color> {
281        self.squares[square.to_index()].map(|(_, color)| color)
282    }
283
284    /// Verify if the [`Square`] is occupied by the given [`Color`].
285    pub fn color_on_is(&self, square: Square, color: Color) -> bool {
286        matches!(self.color_on(square), Some(real_color) if real_color == color)
287    }
288
289    /// Get the [`Color`] at a given [`Square`].
290    pub fn on(&self, square: Square) -> Option<(Piece, Color)> {
291        self.squares[square.to_index()].map(|(piece, color)| (piece, color))
292    }
293
294    /// Verify if the [`Square`] is occupied by the given [`Piece`] and [`Color`].
295    pub fn on_is(&self, square: Square, (piece, color): (Piece, Color)) -> bool {
296        matches!(self.on(square), Some((real_piece, real_color)) if real_color == color && real_piece == piece)
297    }
298
299    /// Verify if the given [`Square`] is pinned for the current side.
300    ///
301    /// This implementation returns true only if the [`Piece`] has more than one valid move,
302    /// if not or if the [`Square`] is empty, then it returns false.
303    pub fn is_pinned(&self, square: Square) -> bool {
304        if self.color_on(square) == Some(self.side_to_move) {
305            !self.has_legal_move(square)
306        } else {
307            false
308        }
309    }
310
311    /// Get the piece pinned for the current side.
312    pub fn pinned(&self) -> Vec<Square> {
313        let mut pinned = Vec::new();
314        for square in ALL_SQUARES {
315            if self.is_pinned(square) {
316                pinned.push(square);
317            }
318        }
319        pinned
320    }
321
322    /// Get the [`Square`] of the [`Piece::King`] of the given [`Color`].
323    pub fn king_of(&self, color: Color) -> Square {
324        for square in ALL_SQUARES {
325            if self.on_is(square, (Piece::King, color)) {
326                return square;
327            }
328        }
329        panic!("King square of {color:?} not found")
330    }
331
332    /// Verify if the [`Square`] is empty (i.e. not occupied).
333    pub fn is_empty(&self, square: Square) -> bool {
334        self.squares[square.to_index()].is_none()
335    }
336
337    /// Verify if the [`Square`] is occupied.
338    pub fn is_occupied(&self, square: Square) -> bool {
339        self.squares[square.to_index()].is_some()
340    }
341
342    /// Verify if the [`Piece::King`] is in check.
343    pub fn is_check(&self) -> bool {
344        let king_square = self.king_of(self.side_to_move);
345        let enemy_color = !self.side_to_move;
346        self.is_targeted(king_square, enemy_color)
347    }
348
349    /// Verify if a move expose the king (used for legality).
350    ///
351    /// FIXME: A player should move if the only threat can be killed
352    fn is_exposing_move(&self, m: ChessMove) -> bool {
353        let mut next_board = *self;
354        let side = self.side_to_move;
355        let enemy_side = !side;
356        next_board.update(m);
357        if next_board.is_targeted(next_board.king_of(side), enemy_side) {
358            return true;
359        }
360        false
361    }
362
363    /// Verify if a [`Square`] can be taken by the given [`Color`] in the current [`Board`].
364    ///
365    /// > **Reciprocal**: see [`Board::is_not_targeted`].
366    pub fn is_targeted(&self, target: Square, attacker: Color) -> bool {
367        let mut is_targeted = false;
368        for from_square in ALL_SQUARES {
369            if self.color_on_is(from_square, attacker) {
370                if self.get_valid_moves(from_square).contains(&target) {
371                    is_targeted = true;
372                    break;
373                }
374            }
375        }
376        is_targeted
377    }
378
379    /// Verify if a [`Square`] cannot be taken by the given [`Color`] in the current [`Board`].
380    ///
381    /// > **Reciprocal**: see [`Board::is_targeted`].
382    pub fn is_not_targeted(&self, target: Square, attacker: Color) -> bool {
383        !self.is_targeted(target, attacker)
384    }
385
386    /// Verify if the [`Piece`] on the [`Square`] has one or more valid moves.
387    ///
388    /// If no [`Piece`] exist on the [`Square`], then return false.
389    ///
390    /// > **Note**: The legality is not verify, if you want to: use [`has_legal_move`][Board::has_legal_move].
391    pub fn has_valid_move(&self, square: Square) -> bool {
392        !self.get_valid_moves(square).is_empty()
393    }
394
395    /// Verify if the [`Piece`] on the [`Square`] has one or more legal moves.
396    ///
397    /// If no [`Piece`] exist on the [`Square`], then return false.
398    pub fn has_legal_move(&self, square: Square) -> bool {
399        !self.get_legal_moves(square).is_empty()
400    }
401
402    /// Verify if the player has one or more legal moves in all the [`Board`].
403    ///
404    /// If no [`Piece`] exist on the [`Square`], then return false.
405    pub fn has_any_move(&self) -> bool {
406        for from_square in ALL_SQUARES {
407            if self.color_on_is(from_square, self.side_to_move) {
408                if self.has_legal_move(from_square) {
409                    return true;
410                }
411            }
412        }
413        false
414    }
415
416    /// Compute and return all the valid moves for a [`Piece`] (if exist) at a given [`Square`].
417    ///
418    /// If no [`Piece`] exist on the [`Square`], then return an empty [`Vec`].
419    ///
420    /// > **Note**: The legality is not verify, if you want to: use [`get_legal_moves`][Board::get_legal_moves].
421    pub fn get_valid_moves(&self, from: Square) -> Vec<Square> {
422        let mut valid_moves = Vec::new();
423        if let Some((piece_from, side)) = self.on(from) {
424            let mut dest_square;
425            match piece_from {
426                Piece::Pawn => {
427                    // If square forward is is empty
428                    dest_square = from.forward(side);
429                    if self.is_empty(dest_square) {
430                        valid_moves.push(dest_square);
431
432                        // First move of the pawn
433                        dest_square = dest_square.forward(side);
434                        if from.rank_for(side) == Rank::Second && self.is_empty(dest_square) {
435                            valid_moves.push(dest_square);
436                        }
437                    }
438
439                    // If can capture (normal or en passant)
440                    if from.file_for(side) == File::A {
441                        dest_square = from.forward(side).right();
442                        if self.color_on_is(dest_square, !side)
443                            || Some(dest_square) == self.en_passant
444                        {
445                            valid_moves.push(dest_square);
446                        }
447                    } else if from.file_for(side) == File::H {
448                        dest_square = from.forward(side).left();
449                        if self.color_on_is(dest_square, !side)
450                            || Some(dest_square) == self.en_passant
451                        {
452                            valid_moves.push(dest_square);
453                        }
454                    } else {
455                        dest_square = from.forward(side).right();
456                        if self.color_on_is(dest_square, !side)
457                            || Some(dest_square) == self.en_passant
458                        {
459                            valid_moves.push(dest_square);
460                        }
461                        dest_square = from.forward(side).left();
462                        if self.color_on_is(dest_square, !side)
463                            || Some(dest_square) == self.en_passant
464                        {
465                            valid_moves.push(dest_square);
466                        }
467                    }
468                }
469                Piece::Knight => {
470                    let _knight_moves = vec![
471                        from.up().up().left(),
472                        from.up().up().right(),
473                        from.right().right().up(),
474                        from.right().right().down(),
475                        from.down().down().right(),
476                        from.down().down().left(),
477                        from.left().left().down(),
478                        from.left().left().up(),
479                    ];
480                    let mut knight_moves = Vec::new();
481                    // filter
482                    for dest_square in _knight_moves {
483                        if from.distance(dest_square) == 2 {
484                            knight_moves.push(dest_square);
485                        }
486                    }
487                    // Verify legality
488                    for dest_square in knight_moves {
489                        if !self.color_on_is(dest_square, side) {
490                            valid_moves.push(dest_square);
491                        }
492                    }
493                }
494                Piece::Bishop => {
495                    for direction in ALL_DIAGONAL {
496                        match from.rank() {
497                            Rank::First if direction.has(Direction::Down) => continue,
498                            Rank::Eighth if direction.has(Direction::Up) => continue,
499                            _ => {}
500                        }
501                        match from.file() {
502                            File::A if direction.has(Direction::Left) => continue,
503                            File::H if direction.has(Direction::Right) => continue,
504                            _ => {}
505                        }
506                        let mut old_square = from;
507                        dest_square = old_square.follow_direction(direction);
508                        while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
509                            valid_moves.push(dest_square);
510                            old_square = dest_square;
511                            dest_square = dest_square.follow_direction(direction);
512                        }
513                        if self.color_on_is(dest_square, !side)
514                            && old_square.distance(dest_square) == 1
515                        {
516                            valid_moves.push(dest_square);
517                        }
518                    }
519                }
520                Piece::Rook => {
521                    for direction in ALL_LINE {
522                        match from.rank() {
523                            Rank::First if direction.has(Direction::Down) => continue,
524                            Rank::Eighth if direction.has(Direction::Up) => continue,
525                            _ => {}
526                        }
527                        match from.file() {
528                            File::A if direction.has(Direction::Left) => continue,
529                            File::H if direction.has(Direction::Right) => continue,
530                            _ => {}
531                        }
532                        let mut old_square = from;
533                        dest_square = old_square.follow_direction(direction);
534                        while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
535                            valid_moves.push(dest_square);
536                            old_square = dest_square;
537                            dest_square = dest_square.follow_direction(direction);
538                        }
539                        if self.color_on_is(dest_square, !side)
540                            && old_square.distance(dest_square) == 1
541                        {
542                            valid_moves.push(dest_square);
543                        }
544                    }
545                }
546                Piece::Queen => {
547                    for direction in ALL_DIRECTION {
548                        match from.rank() {
549                            Rank::First if direction.has(Direction::Down) => continue,
550                            Rank::Eighth if direction.has(Direction::Up) => continue,
551                            _ => {}
552                        }
553                        match from.file() {
554                            File::A if direction.has(Direction::Left) => continue,
555                            File::H if direction.has(Direction::Right) => continue,
556                            _ => {}
557                        }
558                        let mut old_square = from;
559                        dest_square = old_square.follow_direction(direction);
560                        while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
561                            valid_moves.push(dest_square);
562                            old_square = dest_square;
563                            dest_square = dest_square.follow_direction(direction);
564                        }
565                        if self.color_on_is(dest_square, !side)
566                            && old_square.distance(dest_square) == 1
567                        {
568                            valid_moves.push(dest_square);
569                        }
570                    }
571                }
572                Piece::King => {
573                    for direction in ALL_DIRECTION {
574                        match from.rank() {
575                            Rank::First if direction.has(Direction::Down) => continue,
576                            Rank::Eighth if direction.has(Direction::Up) => continue,
577                            _ => {}
578                        }
579                        match from.file() {
580                            File::A if direction.has(Direction::Left) => continue,
581                            File::H if direction.has(Direction::Right) => continue,
582                            _ => {}
583                        }
584                        dest_square = from.follow_direction(direction);
585                        if !self.color_on_is(dest_square, side) {
586                            valid_moves.push(dest_square);
587                        }
588                    }
589                }
590            }
591        }
592        valid_moves
593    }
594
595    /// Compute and return all the legal moves for a [`Piece`] (if exist) at a given [`Square`].
596    ///
597    /// If no [`Piece`] exist on the [`Square`], then return an empty [`Vec`].
598    pub fn get_legal_moves(&self, from: Square) -> Vec<Square> {
599        let mut valid_moves = Vec::new();
600        if let Some((piece_from, side)) = self.on(from) {
601            let mut dest_square;
602            match piece_from {
603                Piece::Pawn => {
604                    // If square forward is is empty
605                    dest_square = from.forward(side);
606                    if self.is_empty(dest_square) {
607                        if !self.is_exposing_move(ChessMove::new(from, dest_square)) {
608                            valid_moves.push(dest_square);
609                        }
610
611                        // First move of the pawn
612                        dest_square = dest_square.forward(side);
613                        if from.rank_for(side) == Rank::Second
614                            && self.is_empty(dest_square)
615                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
616                        {
617                            valid_moves.push(dest_square);
618                        }
619                    }
620
621                    // If can capture (normal or en passant)
622                    if from.file_for(side) == File::A {
623                        dest_square = from.forward(side).right();
624                        if (self.color_on_is(dest_square, !side)
625                            || Some(dest_square) == self.en_passant)
626                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
627                        {
628                            valid_moves.push(dest_square);
629                        }
630                    } else if from.file_for(side) == File::H {
631                        dest_square = from.forward(side).left();
632                        if (self.color_on_is(dest_square, !side)
633                            || Some(dest_square) == self.en_passant)
634                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
635                        {
636                            valid_moves.push(dest_square);
637                        }
638                    } else {
639                        dest_square = from.forward(side).right();
640                        if (self.color_on_is(dest_square, !side)
641                            || Some(dest_square) == self.en_passant)
642                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
643                        {
644                            valid_moves.push(dest_square);
645                        }
646                        dest_square = from.forward(side).left();
647                        if (self.color_on_is(dest_square, !side)
648                            || Some(dest_square) == self.en_passant)
649                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
650                        {
651                            valid_moves.push(dest_square);
652                        }
653                    }
654                }
655                Piece::Knight => {
656                    let knight_moves_rule = vec![
657                        from.up().up().left(),
658                        from.up().up().right(),
659                        from.right().right().up(),
660                        from.right().right().down(),
661                        from.down().down().right(),
662                        from.down().down().left(),
663                        from.left().left().down(),
664                        from.left().left().up(),
665                    ];
666
667                    for square in knight_moves_rule {
668                        if from.distance(square) == 2 && !self.color_on_is(square, side) {
669                            if !self.is_exposing_move(ChessMove::new(from, square)) {
670                                valid_moves.push(square);
671                            }
672                        }
673                    }
674                }
675                Piece::Bishop => {
676                    for direction in ALL_DIAGONAL {
677                        match from.rank() {
678                            Rank::First if direction.has(Direction::Down) => continue,
679                            Rank::Eighth if direction.has(Direction::Up) => continue,
680                            _ => {}
681                        }
682                        match from.file() {
683                            File::A if direction.has(Direction::Left) => continue,
684                            File::H if direction.has(Direction::Right) => continue,
685                            _ => {}
686                        }
687                        let mut old_square = from;
688                        dest_square = old_square.follow_direction(direction);
689                        while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
690                            if !self.is_exposing_move(ChessMove::new(from, dest_square)) {
691                                valid_moves.push(dest_square);
692                            }
693                            old_square = dest_square;
694                            dest_square = dest_square.follow_direction(direction);
695                        }
696                        if self.color_on_is(dest_square, !side)
697                            && old_square.distance(dest_square) == 1
698                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
699                        {
700                            valid_moves.push(dest_square);
701                        }
702                    }
703                }
704                Piece::Rook => {
705                    for direction in ALL_LINE {
706                        match from.rank() {
707                            Rank::First if direction.has(Direction::Down) => continue,
708                            Rank::Eighth if direction.has(Direction::Up) => continue,
709                            _ => {}
710                        }
711                        match from.file() {
712                            File::A if direction.has(Direction::Left) => continue,
713                            File::H if direction.has(Direction::Right) => continue,
714                            _ => {}
715                        }
716                        let mut old_square = from;
717                        dest_square = old_square.follow_direction(direction);
718                        while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
719                            if !self.is_exposing_move(ChessMove::new(from, dest_square)) {
720                                valid_moves.push(dest_square);
721                            }
722                            old_square = dest_square;
723                            dest_square = dest_square.follow_direction(direction);
724                        }
725                        if self.color_on_is(dest_square, !side)
726                            && old_square.distance(dest_square) == 1
727                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
728                        {
729                            valid_moves.push(dest_square);
730                        }
731                    }
732                }
733                Piece::Queen => {
734                    for direction in ALL_DIRECTION {
735                        match from.rank() {
736                            Rank::First if direction.has(Direction::Down) => continue,
737                            Rank::Eighth if direction.has(Direction::Up) => continue,
738                            _ => {}
739                        }
740                        match from.file() {
741                            File::A if direction.has(Direction::Left) => continue,
742                            File::H if direction.has(Direction::Right) => continue,
743                            _ => {}
744                        }
745                        let mut old_square = from;
746                        dest_square = old_square.follow_direction(direction);
747                        while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
748                            if !self.is_exposing_move(ChessMove::new(from, dest_square)) {
749                                valid_moves.push(dest_square);
750                            }
751                            old_square = dest_square;
752                            dest_square = dest_square.follow_direction(direction);
753                        }
754                        if self.color_on_is(dest_square, !side)
755                            && old_square.distance(dest_square) == 1
756                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
757                        {
758                            valid_moves.push(dest_square);
759                        }
760                    }
761                }
762                Piece::King => {
763                    for direction in ALL_DIRECTION {
764                        match from.rank() {
765                            Rank::First if direction.has(Direction::Down) => continue,
766                            Rank::Eighth if direction.has(Direction::Up) => continue,
767                            _ => {}
768                        }
769                        match from.file() {
770                            File::A if direction.has(Direction::Left) => continue,
771                            File::H if direction.has(Direction::Right) => continue,
772                            _ => {}
773                        }
774                        dest_square = from.follow_direction(direction);
775                        if !self.color_on_is(dest_square, side)
776                            && !self.is_exposing_move(ChessMove::new(from, dest_square))
777                        {
778                            valid_moves.push(dest_square);
779                        }
780                    }
781                    if self.castle_rights(side).has_kingside() {
782                        if !self.color_on_is(from.right().right(), side)
783                            && !self.color_on_is(from.right(), side)
784                        {
785                            if !self.is_targeted(from, !side)
786                                && !self.is_targeted(from.right(), !side)
787                                && !self.is_targeted(from.right().right(), !side)
788                            {
789                                valid_moves.push(from.right().right());
790                            }
791                        }
792                    }
793                    if self.castle_rights(side).has_queenside() {
794                        if !self.color_on_is(from.left().left(), side)
795                            && !self.color_on_is(from.left(), side)
796                        {
797                            if !self.is_targeted(from, !side)
798                                && !self.is_targeted(from.left(), !side)
799                                && !self.is_targeted(from.left().left(), !side)
800                                && !self.is_targeted(from.left().left().left(), !side)
801                            {
802                                valid_moves.push(from.left().left());
803                            }
804                        }
805                    }
806                }
807            }
808        }
809        valid_moves
810    }
811
812    /// Construct a [`Vec`] of [`Square`] from a [`Square`] (exclusive) to the first [`Piece`]
813    /// (inclusive) with a given direction.
814    ///
815    /// # Examples
816    ///
817    /// ```
818    /// use chess::{Board, ChessMove, Direction, Square};
819    ///
820    /// let mut board = Board::default();
821    /// board.update(ChessMove::new(Square::D2, Square::D3));
822    /// board.update(ChessMove::new(Square::G7, Square::G5));
823    /// // 8 | r n b q k b n r
824    /// // 7 | p p p p p p . p
825    /// // 6 | . . . . . . . .
826    /// // 5 | . . . . . . p .
827    /// // 4 | . . . . . . . .
828    /// // 3 | . . . P . . . .
829    /// // 2 | P P P . P P P P
830    /// // 1 | R N B Q K B N R
831    /// //   +----------------
832    /// //     A B C D E F G H
833    ///
834    /// assert_eq!(
835    ///     board.get_line(Square::C1, Direction::UpRight),
836    ///     vec![Square::D2, Square::E3, Square::F4, Square::G5]
837    /// )
838    /// ```
839    pub fn get_line(&self, from: Square, direction: Direction) -> Vec<Square> {
840        let mut line = Vec::with_capacity(7);
841        let mut current_square = from.follow_direction(direction);
842        while self.is_empty(current_square) {
843            line.push(current_square);
844            current_square = current_square.follow_direction(direction);
845        }
846        line.push(current_square);
847        line
848    }
849}
850
851impl Default for Board {
852    /// Default board is his initial state at the beginning of a chess game.
853    ///
854    /// ```txt
855    /// 8 | r n b q k b n r
856    /// 7 | p p p p p p p p
857    /// 6 | . . . . . . . .
858    /// 5 | . . . . . . . .
859    /// 4 | . . . . . . . .
860    /// 3 | . . . . . . . .
861    /// 2 | P P P P P P P P
862    /// 1 | R N B Q K B N R
863    ///   +----------------
864    ///     A B C D E F G H
865    /// ```
866    fn default() -> Self {
867        Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()
868    }
869}
870
871impl FromStr for Board {
872    type Err = Error;
873
874    /// From Forsyth-Edwards Notation (FEN).
875    ///
876    /// <https://www.chess.com/terms/fen-chess>
877    fn from_str(value: &str) -> Result<Self, Self::Err> {
878        let mut cur_rank = Rank::Eighth;
879        let mut cur_file = File::A;
880        let mut board = Board::new();
881
882        let tokens: Vec<&str> = value.split(' ').collect();
883        if tokens.len() < 6 {
884            return Err(Error::InvalidFen {
885                fen: value.to_string(),
886            });
887        }
888
889        let pieces = tokens[0];
890        let side = tokens[1];
891        let castles = tokens[2];
892        let ep = tokens[3];
893        let halfmoves = tokens[4];
894        let fullmoves = tokens[5];
895
896        // Piece Placement
897        for x in pieces.chars() {
898            match x {
899                '/' => {
900                    cur_rank = cur_rank.down();
901                    cur_file = File::A;
902                }
903                '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {
904                    cur_file = File::new(cur_file.to_index() + (x as usize) - ('0' as usize));
905                }
906                'r' => {
907                    board[Square::make_square(cur_file, cur_rank)] =
908                        Some((Piece::Rook, Color::Black));
909                    cur_file = cur_file.right();
910                }
911                'R' => {
912                    board[Square::make_square(cur_file, cur_rank)] =
913                        Some((Piece::Rook, Color::White));
914                    cur_file = cur_file.right();
915                }
916                'n' => {
917                    board[Square::make_square(cur_file, cur_rank)] =
918                        Some((Piece::Knight, Color::Black));
919                    cur_file = cur_file.right();
920                }
921                'N' => {
922                    board[Square::make_square(cur_file, cur_rank)] =
923                        Some((Piece::Knight, Color::White));
924                    cur_file = cur_file.right();
925                }
926                'b' => {
927                    board[Square::make_square(cur_file, cur_rank)] =
928                        Some((Piece::Bishop, Color::Black));
929                    cur_file = cur_file.right();
930                }
931                'B' => {
932                    board[Square::make_square(cur_file, cur_rank)] =
933                        Some((Piece::Bishop, Color::White));
934                    cur_file = cur_file.right();
935                }
936                'p' => {
937                    board[Square::make_square(cur_file, cur_rank)] =
938                        Some((Piece::Pawn, Color::Black));
939                    cur_file = cur_file.right();
940                }
941                'P' => {
942                    board[Square::make_square(cur_file, cur_rank)] =
943                        Some((Piece::Pawn, Color::White));
944                    cur_file = cur_file.right();
945                }
946                'q' => {
947                    board[Square::make_square(cur_file, cur_rank)] =
948                        Some((Piece::Queen, Color::Black));
949                    cur_file = cur_file.right();
950                }
951                'Q' => {
952                    board[Square::make_square(cur_file, cur_rank)] =
953                        Some((Piece::Queen, Color::White));
954                    cur_file = cur_file.right();
955                }
956                'k' => {
957                    board[Square::make_square(cur_file, cur_rank)] =
958                        Some((Piece::King, Color::Black));
959                    cur_file = cur_file.right();
960                }
961                'K' => {
962                    board[Square::make_square(cur_file, cur_rank)] =
963                        Some((Piece::King, Color::White));
964                    cur_file = cur_file.right();
965                }
966                _ => {
967                    return Err(Error::InvalidFen {
968                        fen: value.to_string(),
969                    });
970                }
971            }
972        }
973
974        // Side to move
975        match side {
976            "w" | "W" => board.side_to_move = Color::White,
977            "b" | "B" => board.side_to_move = Color::Black,
978            _ => {
979                return Err(Error::InvalidFen {
980                    fen: value.to_string(),
981                })
982            }
983        }
984
985        // Castling Rights
986        if castles.contains('K') && castles.contains('Q') {
987            board.castle_rights[Color::White.to_index()] = CastleRights::Both;
988        } else if castles.contains('K') {
989            board.castle_rights[Color::White.to_index()] = CastleRights::KingSide;
990        } else if castles.contains('Q') {
991            board.castle_rights[Color::White.to_index()] = CastleRights::QueenSide;
992        } else {
993            board.castle_rights[Color::White.to_index()] = CastleRights::NoRights;
994        }
995
996        if castles.contains('k') && castles.contains('q') {
997            board.castle_rights[Color::Black.to_index()] = CastleRights::Both;
998        } else if castles.contains('k') {
999            board.castle_rights[Color::Black.to_index()] = CastleRights::KingSide;
1000        } else if castles.contains('q') {
1001            board.castle_rights[Color::Black.to_index()] = CastleRights::QueenSide;
1002        } else {
1003            board.castle_rights[Color::Black.to_index()] = CastleRights::NoRights;
1004        }
1005
1006        // Possible En Passant Targets
1007        if let Ok(square) = Square::from_str(ep) {
1008            board.en_passant = Some(square);
1009        }
1010
1011        // halfmoves and fullmoves
1012        board.halfmoves = halfmoves.parse().unwrap_or(0);
1013        board.fullmoves = fullmoves.parse().unwrap_or(1);
1014
1015        Ok(board)
1016    }
1017}
1018
1019impl fmt::Display for Board {
1020    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1021        // Piece Placement
1022        let mut count = 0;
1023        for rank in ALL_RANKS.iter().rev() {
1024            for file in ALL_FILES.iter() {
1025                let square_index = Square::make_square(*file, *rank).to_index();
1026
1027                if let Some((piece, color)) = self.squares[square_index] {
1028                    if count != 0 {
1029                        write!(f, "{}", count)?;
1030                        count = 0;
1031                    }
1032                    write!(f, "{}", piece.to_string(color))?;
1033                } else {
1034                    count += 1;
1035                }
1036            }
1037
1038            if count != 0 {
1039                write!(f, "{}", count)?;
1040            }
1041
1042            if *rank != Rank::First {
1043                write!(f, "/")?;
1044            }
1045            count = 0;
1046        }
1047
1048        write!(f, " ")?;
1049
1050        // Side to move
1051        if self.side_to_move == Color::White {
1052            write!(f, "w ")?;
1053        } else {
1054            write!(f, "b ")?;
1055        }
1056
1057        // Castling Rights
1058        write!(
1059            f,
1060            "{}",
1061            self.castle_rights(Color::White).to_string(Color::White)
1062        )?;
1063        write!(
1064            f,
1065            "{}",
1066            self.castle_rights(Color::Black).to_string(Color::Black)
1067        )?;
1068        if self.castle_rights[0] == CastleRights::NoRights
1069            && self.castle_rights[1] == CastleRights::NoRights
1070        {
1071            write!(f, "-")?;
1072        }
1073
1074        write!(f, " ")?;
1075
1076        // Possible En Passant Targets
1077        if let Some(sq) = self.en_passant() {
1078            write!(f, "{}", sq)?;
1079        } else {
1080            write!(f, "-")?;
1081        }
1082
1083        // halfmoves and fullmoves
1084        write!(f, " {} {}", self.halfmoves, self.fullmoves)
1085    }
1086}
1087
1088impl Index<Square> for Board {
1089    type Output = Option<(Piece, Color)>;
1090
1091    fn index(&self, index: Square) -> &Self::Output {
1092        &self.squares[index.to_index()]
1093    }
1094}
1095
1096impl IndexMut<Square> for Board {
1097    fn index_mut(&mut self, index: Square) -> &mut Self::Output {
1098        &mut self.squares[index.to_index()]
1099    }
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104    use super::*;
1105
1106    #[test]
1107    #[ignore]
1108    fn state() {
1109        todo!()
1110    }
1111
1112    #[test]
1113    #[ignore]
1114    fn is_valid() {
1115        todo!()
1116    }
1117
1118    #[test]
1119    #[ignore]
1120    fn is_legal() {
1121        todo!()
1122    }
1123
1124    #[test]
1125    #[ignore]
1126    fn update() {
1127        todo!()
1128    }
1129
1130    #[test]
1131    fn remove_castle_rights() {
1132        let mut board = Board::default();
1133
1134        assert_eq!(board.castle_rights(Color::White), CastleRights::Both);
1135        assert_eq!(board.castle_rights(Color::Black), CastleRights::Both);
1136
1137        board.remove_castle_rights(Color::White, CastleRights::QueenSide);
1138        board.remove_castle_rights(Color::Black, CastleRights::NoRights);
1139
1140        assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide);
1141        assert_eq!(board.castle_rights(Color::Black), CastleRights::Both);
1142
1143        board.remove_castle_rights(Color::White, CastleRights::KingSide);
1144        board.remove_castle_rights(Color::Black, CastleRights::Both);
1145
1146        assert_eq!(board.castle_rights(Color::White), CastleRights::NoRights);
1147        assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights);
1148    }
1149
1150    #[test]
1151    fn piece_on() {
1152        let board = Board::default();
1153        assert_eq!(board.piece_on(Square::A1), Some(Piece::Rook));
1154        assert_eq!(board.piece_on(Square::A2), Some(Piece::Pawn));
1155        assert_eq!(board.piece_on(Square::A3), None);
1156    }
1157
1158    #[test]
1159    fn piece_on_is() {
1160        let board = Board::default();
1161        assert!(board.piece_on_is(Square::A1, Piece::Rook));
1162        assert!(board.piece_on_is(Square::A7, Piece::Pawn));
1163        assert!(!board.piece_on_is(Square::A2, Piece::Queen));
1164        assert!(!board.piece_on_is(Square::A3, Piece::King));
1165    }
1166
1167    #[test]
1168    fn color_on() {
1169        let board = Board::default();
1170        assert_eq!(board.color_on(Square::A1), Some(Color::White));
1171        assert_eq!(board.color_on(Square::A8), Some(Color::Black));
1172        assert_eq!(board.color_on(Square::A3), None);
1173    }
1174
1175    #[test]
1176    fn color_on_is() {
1177        let board = Board::default();
1178        assert!(board.color_on_is(Square::A1, Color::White));
1179        assert!(board.color_on_is(Square::A8, Color::Black));
1180        assert!(!board.color_on_is(Square::A2, Color::Black));
1181        assert!(!board.color_on_is(Square::A3, Color::White));
1182    }
1183
1184    #[test]
1185    fn on() {
1186        let board = Board::default();
1187        assert_eq!(board.on(Square::A1), Some((Piece::Rook, Color::White)));
1188        assert_eq!(board.on(Square::A7), Some((Piece::Pawn, Color::Black)));
1189        assert_eq!(board.on(Square::A3), None);
1190    }
1191
1192    #[test]
1193    fn is_pinned() {
1194        let mut board = Board::default();
1195
1196        board.update(ChessMove::new(Square::E2, Square::E4));
1197        board.update(ChessMove::new(Square::F7, Square::F5));
1198
1199        board.update(ChessMove::new(Square::D1, Square::H5));
1200
1201        // the only piece that is not pinned
1202        assert!(!board.is_pinned(Square::G7));
1203
1204        // All other piece are pinned
1205        for square in ALL_SQUARES {
1206            if board.color_on_is(square, board.side_to_move()) {
1207                if square != Square::G7 {
1208                    assert!(board.is_pinned(square));
1209                }
1210            }
1211        }
1212    }
1213
1214    #[test]
1215    fn king_of() {
1216        let board = Board::default();
1217
1218        assert_eq!(board.king_of(Color::White), Square::E1);
1219        assert_eq!(board.king_of(Color::Black), Square::E8);
1220    }
1221
1222    #[test]
1223    fn is_empty() {
1224        let board = Board::default();
1225        for square in ALL_SQUARES {
1226            if square.rank().between(Rank::Third, Rank::Sixth) {
1227                assert!(board.is_empty(square));
1228            }
1229        }
1230    }
1231
1232    #[test]
1233    fn is_occupied() {
1234        let board = Board::default();
1235        for square in ALL_SQUARES {
1236            if square.rank().between(Rank::First, Rank::Second)
1237                || square.rank().between(Rank::Seventh, Rank::Eighth)
1238            {
1239                assert!(board.is_occupied(square));
1240            }
1241        }
1242    }
1243
1244    #[test]
1245    fn is_targeted() {
1246        let fen = "r1bqk2r/pppp1ppp/2n2n2/2b1p3/2B1P3/2N2N2/PPPP1PPP/R1BQK2R w KQkq - 6 5";
1247        let board = Board::from_str(fen).expect("valid fen");
1248        // 8 | r . b q k . . r
1249        // 7 | p p p p . p p p
1250        // 6 | . . n . . n . .
1251        // 5 | . . b . p . . .
1252        // 4 | . . B . P . . .
1253        // 3 | . . N . . N . .
1254        // 2 | P P P P . P P P
1255        // 1 | R . B Q K . . R
1256        //   +----------------
1257        //     A B C D E F G H
1258
1259        assert!(board.is_targeted(Square::E4, Color::Black)); // Knight
1260        assert!(board.is_targeted(Square::E5, Color::White)); // Knight
1261
1262        assert!(board.is_targeted(Square::F2, Color::Black)); // Bishop
1263        assert!(board.is_targeted(Square::F7, Color::White)); // Bishop
1264
1265        // Not targeted
1266        assert!(!board.is_targeted(Square::A8, Color::White));
1267        assert!(!board.is_targeted(Square::H1, Color::Black));
1268        assert!(!board.is_targeted(Square::E1, Color::Black));
1269        assert!(!board.is_targeted(Square::D8, Color::White));
1270    }
1271
1272    #[test]
1273    #[ignore]
1274    fn is_check() {
1275        todo!()
1276    }
1277
1278    #[test]
1279    #[ignore]
1280    fn is_exposing_move() {
1281        todo!()
1282    }
1283
1284    #[test]
1285    #[ignore]
1286    fn has_valid_moves() {
1287        todo!()
1288    }
1289
1290    #[test]
1291    #[ignore]
1292    fn has_legal_moves() {
1293        todo!()
1294    }
1295
1296    #[test]
1297    #[ignore]
1298    fn get_valid_move() {
1299        todo!()
1300    }
1301
1302    #[test]
1303    #[ignore]
1304    fn get_legal_move() {
1305        todo!()
1306    }
1307
1308    #[test]
1309    #[ignore]
1310    fn get_line() {
1311        todo!()
1312    }
1313
1314    #[test]
1315    fn from_fen() {
1316        let fen = "r1bqk2r/pppp1ppp/2n2n2/2b1p3/2B1P3/2N2N2/PPPP1PPP/R1BQK2R w KQkq - 6 5";
1317        let fen_board = Board::from_str(fen).expect("valid fen");
1318
1319        let mut board = Board::default();
1320
1321        board.update(ChessMove::new(Square::E2, Square::E4));
1322        board.update(ChessMove::new(Square::E7, Square::E5));
1323
1324        board.update(ChessMove::new(Square::B1, Square::C3));
1325        board.update(ChessMove::new(Square::B8, Square::C6));
1326
1327        board.update(ChessMove::new(Square::G1, Square::F3));
1328        board.update(ChessMove::new(Square::G8, Square::F6));
1329
1330        board.update(ChessMove::new(Square::F1, Square::C4));
1331        board.update(ChessMove::new(Square::F8, Square::C5));
1332
1333        assert_eq!(board, fen_board, "update don't work?");
1334
1335        // Broke CastleRights
1336        let fen = "r1bq1rk1/pppp1ppp/2n2n2/2b1p3/2B1P3/2N2N2/PPPP1PPP/R1BQK1R1 w Q - 8 6";
1337        let fen_board = Board::from_str(fen).expect("valid fen");
1338
1339        board.update(ChessMove::new(Square::H1, Square::G1)); // rook move so remove kingside
1340        board.update(ChessMove::new(Square::E8, Square::G8)); // king use castle kingside then loose both CastleRights
1341
1342        assert_eq!(board, fen_board, "Castle remove don't work?");
1343    }
1344
1345    #[test]
1346    fn to_fen() {
1347        let mut board = Board::default();
1348
1349        board.update(ChessMove::new(Square::E2, Square::E4));
1350        board.update(ChessMove::new(Square::E7, Square::E5));
1351
1352        board.update(ChessMove::new(Square::B1, Square::C3));
1353        board.update(ChessMove::new(Square::B8, Square::C6));
1354
1355        board.update(ChessMove::new(Square::G1, Square::F3));
1356        board.update(ChessMove::new(Square::G8, Square::F6));
1357
1358        board.update(ChessMove::new(Square::F1, Square::C4));
1359        board.update(ChessMove::new(Square::F8, Square::C5));
1360
1361        let fen = "r1bqk2r/pppp1ppp/2n2n2/2b1p3/2B1P3/2N2N2/PPPP1PPP/R1BQK2R w KQkq - 6 5";
1362        assert_eq!(board.to_string(), fen.to_string());
1363    }
1364}