chess/
board_builder.rs

1use crate::board::Board;
2use crate::castle_rights::CastleRights;
3use crate::color::Color;
4use crate::error::Error;
5use crate::file::{File, ALL_FILES};
6use crate::piece::Piece;
7use crate::rank::{Rank, ALL_RANKS};
8use crate::square::{Square, ALL_SQUARES};
9
10use std::fmt;
11use std::ops::{Index, IndexMut};
12use std::str::FromStr;
13
14/// Represents a chess position that has *not* been validated for legality.
15///
16/// This structure is useful in the following cases:
17/// * You are trying to build a chess board manually in code.
18/// * The `Board` structure will try to keep the position fully legal, which will prevent you from
19///   placing pieces arbitrarily.  This structure will not.
20/// * You want to display the chess position in a UI.
21/// * You want to convert between formats like FEN.
22///
23/// ```
24/// use chess::{BoardBuilder, Board, Square, Color, Piece};
25/// use std::convert::TryFrom;
26/// let mut position = BoardBuilder::new();
27/// position.piece(Square::A1, Piece::King, Color::White);
28/// position.piece(Square::A8, Piece::Rook, Color::Black);
29/// position.piece(Square::D1, Piece::King, Color::Black);
30///
31/// // You can index the position by the square:
32/// assert_eq!(position[Square::A1], Some((Piece::King, Color::White)));
33///
34/// // White is in check, but that's ok, it's white's turn to move.
35/// assert!(Board::try_from(&position).is_ok());
36///
37/// // Now White is in check, but Black is ready to move.  This position is invalid.
38/// position.side_to_move(Color::Black);
39/// assert!(Board::try_from(position).is_err());
40///
41/// // One liners are possible with the builder pattern.
42/// use std::convert::TryInto;
43///
44/// let res: Result<Board, _> = BoardBuilder::new()
45///                        .piece(Square::A1, Piece::King, Color::White)
46///                        .piece(Square::A8, Piece::King, Color::Black)
47///                        .try_into();
48/// assert!(res.is_ok());
49/// ```
50#[derive(Copy, Clone)]
51pub struct BoardBuilder {
52    pieces: [Option<(Piece, Color)>; 64],
53    side_to_move: Color,
54    castle_rights: [CastleRights; 2],
55    en_passant: Option<File>,
56}
57
58impl BoardBuilder {
59    /// Construct a new, empty, BoardBuilder.
60    ///
61    /// * No pieces are on the board
62    /// * `CastleRights` are empty for both sides
63    /// * `en_passant` is not set
64    /// * `side_to_move` is Color::White
65    /// ```
66    /// use chess::{BoardBuilder, Board, Square, Color, Piece};
67    /// use std::convert::TryInto;
68    ///
69    /// # use chess::Error;
70    /// # fn main() -> Result<(), Error> {
71    /// let board: Board = BoardBuilder::new()
72    ///     .piece(Square::A1, Piece::King, Color::White)
73    ///     .piece(Square::A8, Piece::King, Color::Black)
74    ///     .try_into()?;
75    /// # Ok(())
76    /// # }
77    /// ```
78    pub fn new() -> BoardBuilder {
79        BoardBuilder {
80            pieces: [None; 64],
81            side_to_move: Color::White,
82            castle_rights: [CastleRights::NoRights, CastleRights::NoRights],
83            en_passant: None,
84        }
85    }
86
87    /// Set up a board with everything pre-loaded.
88    ///
89    /// ```
90    /// use chess::{BoardBuilder, Board, Square, Color, Piece, CastleRights};
91    /// use std::convert::TryInto;
92    ///
93    /// # use chess::Error;
94    /// # fn main() -> Result<(), Error> {
95    /// let board: Board = BoardBuilder::setup(
96    ///         &[
97    ///             (Square::A1, Piece::King, Color::White),
98    ///             (Square::H8, Piece::King, Color::Black)
99    ///         ],
100    ///         Color::Black,
101    ///         CastleRights::NoRights,
102    ///         CastleRights::NoRights,
103    ///         None)
104    ///     .try_into()?;
105    /// # Ok(())
106    /// # }
107    pub fn setup<'a>(
108        pieces: impl IntoIterator<Item = &'a (Square, Piece, Color)>,
109        side_to_move: Color,
110        white_castle_rights: CastleRights,
111        black_castle_rights: CastleRights,
112        en_passant: Option<File>,
113    ) -> BoardBuilder {
114        let mut result = BoardBuilder {
115            pieces: [None; 64],
116            side_to_move: side_to_move,
117            castle_rights: [white_castle_rights, black_castle_rights],
118            en_passant: en_passant,
119        };
120
121        for piece in pieces.into_iter() {
122            result.pieces[piece.0.to_index()] = Some((piece.1, piece.2));
123        }
124
125        result
126    }
127
128    /// Get the current player
129    ///
130    /// ```
131    /// use chess::{BoardBuilder, Board, Color};
132    ///
133    /// let bb: BoardBuilder = Board::default().into();
134    /// assert_eq!(bb.get_side_to_move(), Color::White);
135    /// ```
136    pub fn get_side_to_move(&self) -> Color {
137        self.side_to_move
138    }
139
140    /// Get the castle rights for a player
141    ///
142    /// ```
143    /// use chess::{BoardBuilder, Board, CastleRights, Color};
144    ///
145    /// let bb: BoardBuilder = Board::default().into();
146    /// assert_eq!(bb.get_castle_rights(Color::White), CastleRights::Both);
147    /// ```
148    pub fn get_castle_rights(&self, color: Color) -> CastleRights {
149        self.castle_rights[color.to_index()]
150    }
151
152    /// Get the current en_passant square
153    ///
154    /// ```
155    /// use chess::{BoardBuilder, Board, Square, ChessMove};
156    ///
157    /// let board = Board::default()
158    ///     .make_move_new(ChessMove::new(Square::E2, Square::E4, None))
159    ///     .make_move_new(ChessMove::new(Square::H7, Square::H6, None))
160    ///     .make_move_new(ChessMove::new(Square::E4, Square::E5, None))
161    ///     .make_move_new(ChessMove::new(Square::D7, Square::D5, None));
162    /// let bb: BoardBuilder = board.into();
163    /// assert_eq!(bb.get_en_passant(), Some(Square::D5));
164    /// ```
165    pub fn get_en_passant(&self) -> Option<Square> {
166        self.en_passant
167            .map(|f| Square::make_square((!self.get_side_to_move()).to_fourth_rank(), f))
168    }
169
170    /// Set the side to move on the position
171    ///
172    /// This function can be used on self directly or in a builder pattern.
173    ///
174    /// ```
175    /// use chess::{BoardBuilder, Color};
176    /// BoardBuilder::new()
177    ///              .side_to_move(Color::Black);      
178    ///
179    /// let mut bb = BoardBuilder::new();
180    /// bb.side_to_move(Color::Black);
181    /// ```
182    pub fn side_to_move<'a>(&'a mut self, color: Color) -> &'a mut Self {
183        self.side_to_move = color;
184        self
185    }
186
187    /// Set the castle rights for a particular color on the position
188    ///
189    /// This function can be used on self directly or in a builder pattern.
190    ///
191    /// ```
192    /// use chess::{BoardBuilder, Color, CastleRights};
193    /// BoardBuilder::new()
194    ///              .castle_rights(Color::White, CastleRights::NoRights);
195    ///
196    /// let mut bb = BoardBuilder::new();
197    /// bb.castle_rights(Color::Black, CastleRights::Both);
198    /// ```
199    pub fn castle_rights<'a>(
200        &'a mut self,
201        color: Color,
202        castle_rights: CastleRights,
203    ) -> &'a mut Self {
204        self.castle_rights[color.to_index()] = castle_rights;
205        self
206    }
207
208    /// Set a piece on a square.
209    ///
210    /// Note that this can and will overwrite another piece on the square if need.
211    ///
212    /// Note also that this will not update your castle rights.
213    ///
214    /// This function can be used on self directly or in a builder pattern.
215    ///
216    /// ```
217    /// use chess::{BoardBuilder, Color, Square, Piece};
218    ///
219    /// BoardBuilder::new()
220    ///              .piece(Square::A1, Piece::Rook, Color::White);
221    ///
222    /// let mut bb = BoardBuilder::new();
223    /// bb.piece(Square::A8, Piece::Rook, Color::Black);
224    /// ```
225    pub fn piece<'a>(&'a mut self, square: Square, piece: Piece, color: Color) -> &'a mut Self {
226        self[square] = Some((piece, color));
227        self
228    }
229
230    /// Clear a square on the board.
231    ///
232    /// Note that this will not update your castle rights.
233    ///
234    /// This function can be used on self directly or in a builder pattern.
235    ///
236    /// ```
237    /// use chess::{BoardBuilder, Square, Board};
238    ///
239    /// let mut bb: BoardBuilder = Board::default().into();
240    /// bb.clear_square(Square::A1);
241    /// ```
242    pub fn clear_square<'a>(&'a mut self, square: Square) -> &'a mut Self {
243        self[square] = None;
244        self
245    }
246
247    /// Set or clear the en_passant `File`.
248    ///
249    /// This function can be used directly or in a builder pattern.
250    ///
251    /// ```
252    /// use chess::{BoardBuilder, Square, Board, File, Color, Piece};
253    ///
254    /// BoardBuilder::new()
255    ///              .piece(Square::E4, Piece::Pawn, Color::White)
256    ///              .en_passant(Some(File::E));
257    /// ```
258    pub fn en_passant<'a>(&'a mut self, file: Option<File>) -> &'a mut Self {
259        self.en_passant = file;
260        self
261    }
262}
263
264impl Index<Square> for BoardBuilder {
265    type Output = Option<(Piece, Color)>;
266
267    fn index<'a>(&'a self, index: Square) -> &'a Self::Output {
268        &self.pieces[index.to_index()]
269    }
270}
271
272impl IndexMut<Square> for BoardBuilder {
273    fn index_mut<'a>(&'a mut self, index: Square) -> &'a mut Self::Output {
274        &mut self.pieces[index.to_index()]
275    }
276}
277
278impl fmt::Display for BoardBuilder {
279    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
280        let mut count = 0;
281        for rank in ALL_RANKS.iter().rev() {
282            for file in ALL_FILES.iter() {
283                let square = Square::make_square(*rank, *file).to_index();
284
285                if self.pieces[square].is_some() && count != 0 {
286                    write!(f, "{}", count)?;
287                    count = 0;
288                }
289
290                if let Some((piece, color)) = self.pieces[square] {
291                    write!(f, "{}", piece.to_string(color))?;
292                } else {
293                    count += 1;
294                }
295            }
296
297            if count != 0 {
298                write!(f, "{}", count)?;
299            }
300
301            if *rank != Rank::First {
302                write!(f, "/")?;
303            }
304            count = 0;
305        }
306
307        write!(f, " ")?;
308
309        if self.side_to_move == Color::White {
310            write!(f, "w ")?;
311        } else {
312            write!(f, "b ")?;
313        }
314
315        write!(
316            f,
317            "{}",
318            self.castle_rights[Color::White.to_index()].to_string(Color::White)
319        )?;
320        write!(
321            f,
322            "{}",
323            self.castle_rights[Color::Black.to_index()].to_string(Color::Black)
324        )?;
325        if self.castle_rights[0] == CastleRights::NoRights
326            && self.castle_rights[1] == CastleRights::NoRights
327        {
328            write!(f, "-")?;
329        }
330
331        write!(f, " ")?;
332        if let Some(sq) = self.get_en_passant() {
333            write!(f, "{}", sq)?;
334        } else {
335            write!(f, "-")?;
336        }
337
338        write!(f, " 0 1")
339    }
340}
341
342impl Default for BoardBuilder {
343    fn default() -> BoardBuilder {
344        BoardBuilder::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()
345    }
346}
347
348impl FromStr for BoardBuilder {
349    type Err = Error;
350
351    fn from_str(value: &str) -> Result<Self, Self::Err> {
352        let mut cur_rank = Rank::Eighth;
353        let mut cur_file = File::A;
354        let mut fen = &mut BoardBuilder::new();
355
356        let tokens: Vec<&str> = value.split(' ').collect();
357        if tokens.len() < 4 {
358            return Err(Error::InvalidFen {
359                fen: value.to_string(),
360            });
361        }
362
363        let pieces = tokens[0];
364        let side = tokens[1];
365        let castles = tokens[2];
366        let ep = tokens[3];
367
368        for x in pieces.chars() {
369            match x {
370                '/' => {
371                    cur_rank = cur_rank.down();
372                    cur_file = File::A;
373                }
374                '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {
375                    cur_file =
376                        File::from_index(cur_file.to_index() + (x as usize) - ('0' as usize));
377                }
378                'r' => {
379                    fen[Square::make_square(cur_rank, cur_file)] =
380                        Some((Piece::Rook, Color::Black));
381                    cur_file = cur_file.right();
382                }
383                'R' => {
384                    fen[Square::make_square(cur_rank, cur_file)] =
385                        Some((Piece::Rook, Color::White));
386                    cur_file = cur_file.right();
387                }
388                'n' => {
389                    fen[Square::make_square(cur_rank, cur_file)] =
390                        Some((Piece::Knight, Color::Black));
391                    cur_file = cur_file.right();
392                }
393                'N' => {
394                    fen[Square::make_square(cur_rank, cur_file)] =
395                        Some((Piece::Knight, Color::White));
396                    cur_file = cur_file.right();
397                }
398                'b' => {
399                    fen[Square::make_square(cur_rank, cur_file)] =
400                        Some((Piece::Bishop, Color::Black));
401                    cur_file = cur_file.right();
402                }
403                'B' => {
404                    fen[Square::make_square(cur_rank, cur_file)] =
405                        Some((Piece::Bishop, Color::White));
406                    cur_file = cur_file.right();
407                }
408                'p' => {
409                    fen[Square::make_square(cur_rank, cur_file)] =
410                        Some((Piece::Pawn, Color::Black));
411                    cur_file = cur_file.right();
412                }
413                'P' => {
414                    fen[Square::make_square(cur_rank, cur_file)] =
415                        Some((Piece::Pawn, Color::White));
416                    cur_file = cur_file.right();
417                }
418                'q' => {
419                    fen[Square::make_square(cur_rank, cur_file)] =
420                        Some((Piece::Queen, Color::Black));
421                    cur_file = cur_file.right();
422                }
423                'Q' => {
424                    fen[Square::make_square(cur_rank, cur_file)] =
425                        Some((Piece::Queen, Color::White));
426                    cur_file = cur_file.right();
427                }
428                'k' => {
429                    fen[Square::make_square(cur_rank, cur_file)] =
430                        Some((Piece::King, Color::Black));
431                    cur_file = cur_file.right();
432                }
433                'K' => {
434                    fen[Square::make_square(cur_rank, cur_file)] =
435                        Some((Piece::King, Color::White));
436                    cur_file = cur_file.right();
437                }
438                _ => {
439                    return Err(Error::InvalidFen {
440                        fen: value.to_string(),
441                    });
442                }
443            }
444        }
445        match side {
446            "w" | "W" => fen = fen.side_to_move(Color::White),
447            "b" | "B" => fen = fen.side_to_move(Color::Black),
448            _ => {
449                return Err(Error::InvalidFen {
450                    fen: value.to_string(),
451                })
452            }
453        }
454
455        if castles.contains("K") && castles.contains("Q") {
456            fen.castle_rights[Color::White.to_index()] = CastleRights::Both;
457        } else if castles.contains("K") {
458            fen.castle_rights[Color::White.to_index()] = CastleRights::KingSide;
459        } else if castles.contains("Q") {
460            fen.castle_rights[Color::White.to_index()] = CastleRights::QueenSide;
461        } else {
462            fen.castle_rights[Color::White.to_index()] = CastleRights::NoRights;
463        }
464
465        if castles.contains("k") && castles.contains("q") {
466            fen.castle_rights[Color::Black.to_index()] = CastleRights::Both;
467        } else if castles.contains("k") {
468            fen.castle_rights[Color::Black.to_index()] = CastleRights::KingSide;
469        } else if castles.contains("q") {
470            fen.castle_rights[Color::Black.to_index()] = CastleRights::QueenSide;
471        } else {
472            fen.castle_rights[Color::Black.to_index()] = CastleRights::NoRights;
473        }
474
475        if let Ok(sq) = Square::from_str(&ep) {
476            fen = fen.en_passant(Some(sq.get_file()));
477        }
478
479        Ok(*fen)
480    }
481}
482
483impl From<&Board> for BoardBuilder {
484    fn from(board: &Board) -> Self {
485        let mut pieces = vec![];
486        for sq in ALL_SQUARES.iter() {
487            if let Some(piece) = board.piece_on(*sq) {
488                let color = board.color_on(*sq).unwrap();
489                pieces.push((*sq, piece, color));
490            }
491        }
492
493        BoardBuilder::setup(
494            &pieces,
495            board.side_to_move(),
496            board.castle_rights(Color::White),
497            board.castle_rights(Color::Black),
498            board.en_passant().map(|sq| sq.get_file()),
499        )
500    }
501}
502
503impl From<Board> for BoardBuilder {
504    fn from(board: Board) -> Self {
505        (&board).into()
506    }
507}
508
509#[cfg(test)]
510use crate::bitboard::BitBoard;
511#[cfg(test)]
512use std::convert::TryInto;
513
514#[test]
515fn check_initial_position() {
516    let initial_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
517    let fen: BoardBuilder = Board::default().into();
518    let computed_initial_fen = format!("{}", fen);
519    assert_eq!(computed_initial_fen, initial_fen);
520
521    let pass_through = format!("{}", BoardBuilder::default());
522    assert_eq!(pass_through, initial_fen);
523}
524
525#[test]
526fn invalid_castle_rights() {
527    let res: Result<Board, _> = BoardBuilder::new()
528        .piece(Square::A1, Piece::King, Color::White)
529        .piece(Square::A8, Piece::King, Color::Black)
530        .castle_rights(Color::White, CastleRights::Both)
531        .try_into();
532    assert!(res.is_err());
533}
534
535#[test]
536fn test_kissing_kings() {
537    let res: Result<Board, _> = BoardBuilder::new()
538        .piece(Square::A1, Piece::King, Color::White)
539        .piece(Square::A2, Piece::King, Color::Black)
540        .try_into();
541    assert!(res.is_err());
542}
543
544#[test]
545fn test_in_check() {
546    let mut bb: BoardBuilder = BoardBuilder::new();
547    bb.piece(Square::A1, Piece::King, Color::White)
548        .piece(Square::A8, Piece::King, Color::Black)
549        .piece(Square::H1, Piece::Rook, Color::Black);
550
551    let board: Board = (&bb).try_into().unwrap();
552    assert_eq!(*board.checkers(), BitBoard::from_square(Square::H1));
553
554    bb.side_to_move(Color::Black);
555    let res: Result<Board, _> = bb.try_into();
556    assert!(res.is_err()); // My opponent cannot be in check when it's my move.
557}