Skip to main content

chess/
board_builder.rs

1use crate::board::Board;
2use crate::castle_rights::CastleRights;
3use crate::color::Color;
4use crate::error::Error;
5use crate::file::{ALL_FILES, File};
6use crate::piece::Piece;
7use crate::rank::{ALL_RANKS, Rank};
8use crate::square::{ALL_SQUARES, Square};
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)>, side_to_move: Color, white_castle_rights: CastleRights,
109		black_castle_rights: CastleRights, en_passant: Option<File>,
110	) -> BoardBuilder {
111		let mut result = BoardBuilder { pieces: [None; 64], side_to_move, castle_rights: [white_castle_rights, black_castle_rights], en_passant };
112
113		for piece in pieces.into_iter() {
114			result.pieces[piece.0.to_index()] = Some((piece.1, piece.2));
115		}
116
117		result
118	}
119
120	/// Get the current player
121	///
122	/// ```
123	/// use chess::{BoardBuilder, Board, Color};
124	///
125	/// let bb: BoardBuilder = Board::default().into();
126	/// assert_eq!(bb.get_side_to_move(), Color::White);
127	/// ```
128	pub fn get_side_to_move(&self) -> Color {
129		self.side_to_move
130	}
131
132	/// Get the castle rights for a player
133	///
134	/// ```
135	/// use chess::{BoardBuilder, Board, CastleRights, Color};
136	///
137	/// let bb: BoardBuilder = Board::default().into();
138	/// assert_eq!(bb.get_castle_rights(Color::White), CastleRights::Both);
139	/// ```
140	pub fn get_castle_rights(&self, color: Color) -> CastleRights {
141		self.castle_rights[color.to_index()]
142	}
143
144	/// Get the current en_passant square
145	///
146	/// ```
147	/// use chess::{BoardBuilder, Board, Square, ChessMove};
148	///
149	/// let board = Board::default()
150	///     .make_move_new(ChessMove::new(Square::E2, Square::E4, None))
151	///     .make_move_new(ChessMove::new(Square::H7, Square::H6, None))
152	///     .make_move_new(ChessMove::new(Square::E4, Square::E5, None))
153	///     .make_move_new(ChessMove::new(Square::D7, Square::D5, None));
154	/// let bb: BoardBuilder = board.into();
155	/// assert_eq!(bb.get_en_passant(), Some(Square::D5));
156	/// ```
157	pub fn get_en_passant(&self) -> Option<Square> {
158		self.en_passant.map(|f| Square::make_square((!self.get_side_to_move()).to_fourth_rank(), f))
159	}
160
161	/// Set the side to move on the position
162	///
163	/// This function can be used on self directly or in a builder pattern.
164	///
165	/// ```
166	/// use chess::{BoardBuilder, Color};
167	/// BoardBuilder::new()
168	///              .side_to_move(Color::Black);      
169	///
170	/// let mut bb = BoardBuilder::new();
171	/// bb.side_to_move(Color::Black);
172	/// ```
173	pub fn side_to_move(&mut self, color: Color) -> &mut Self {
174		self.side_to_move = color;
175		self
176	}
177
178	/// Set the castle rights for a particular color on the position
179	///
180	/// This function can be used on self directly or in a builder pattern.
181	///
182	/// ```
183	/// use chess::{BoardBuilder, Color, CastleRights};
184	/// BoardBuilder::new()
185	///              .castle_rights(Color::White, CastleRights::NoRights);
186	///
187	/// let mut bb = BoardBuilder::new();
188	/// bb.castle_rights(Color::Black, CastleRights::Both);
189	/// ```
190	pub fn castle_rights(&mut self, color: Color, castle_rights: CastleRights) -> &mut Self {
191		self.castle_rights[color.to_index()] = castle_rights;
192		self
193	}
194
195	/// Set a piece on a square.
196	///
197	/// Note that this can and will overwrite another piece on the square if need.
198	///
199	/// Note also that this will not update your castle rights.
200	///
201	/// This function can be used on self directly or in a builder pattern.
202	///
203	/// ```
204	/// use chess::{BoardBuilder, Color, Square, Piece};
205	///
206	/// BoardBuilder::new()
207	///              .piece(Square::A1, Piece::Rook, Color::White);
208	///
209	/// let mut bb = BoardBuilder::new();
210	/// bb.piece(Square::A8, Piece::Rook, Color::Black);
211	/// ```
212	pub fn piece(&mut self, square: Square, piece: Piece, color: Color) -> &mut Self {
213		self[square] = Some((piece, color));
214		self
215	}
216
217	/// Clear a square on the board.
218	///
219	/// Note that this will not update your castle rights.
220	///
221	/// This function can be used on self directly or in a builder pattern.
222	///
223	/// ```
224	/// use chess::{BoardBuilder, Square, Board};
225	///
226	/// let mut bb: BoardBuilder = Board::default().into();
227	/// bb.clear_square(Square::A1);
228	/// ```
229	pub fn clear_square(&mut self, square: Square) -> &mut Self {
230		self[square] = None;
231		self
232	}
233
234	/// Set or clear the en_passant `File`.
235	///
236	/// This function can be used directly or in a builder pattern.
237	///
238	/// ```
239	/// use chess::{BoardBuilder, Square, Board, File, Color, Piece};
240	///
241	/// BoardBuilder::new()
242	///              .piece(Square::E4, Piece::Pawn, Color::White)
243	///              .en_passant(Some(File::E));
244	/// ```
245	pub fn en_passant(&mut self, file: Option<File>) -> &mut Self {
246		self.en_passant = file;
247		self
248	}
249}
250
251impl Index<Square> for BoardBuilder {
252	type Output = Option<(Piece, Color)>;
253
254	fn index(&self, index: Square) -> &Self::Output {
255		&self.pieces[index.to_index()]
256	}
257}
258
259impl IndexMut<Square> for BoardBuilder {
260	fn index_mut(&mut self, index: Square) -> &mut Self::Output {
261		&mut self.pieces[index.to_index()]
262	}
263}
264
265impl fmt::Display for BoardBuilder {
266	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
267		let mut count = 0;
268		for rank in ALL_RANKS.iter().rev() {
269			for file in ALL_FILES.iter() {
270				let square = Square::make_square(*rank, *file).to_index();
271
272				if self.pieces[square].is_some() && count != 0 {
273					write!(f, "{}", count)?;
274					count = 0;
275				}
276
277				if let Some((piece, color)) = self.pieces[square] {
278					write!(f, "{}", piece.to_string(color))?;
279				} else {
280					count += 1;
281				}
282			}
283
284			if count != 0 {
285				write!(f, "{}", count)?;
286			}
287
288			if *rank != Rank::First {
289				write!(f, "/")?;
290			}
291			count = 0;
292		}
293
294		write!(f, " ")?;
295
296		if self.side_to_move == Color::White {
297			write!(f, "w ")?;
298		} else {
299			write!(f, "b ")?;
300		}
301
302		write!(f, "{}", self.castle_rights[Color::White.to_index()].to_string(Color::White))?;
303		write!(f, "{}", self.castle_rights[Color::Black.to_index()].to_string(Color::Black))?;
304		if self.castle_rights[0] == CastleRights::NoRights && self.castle_rights[1] == CastleRights::NoRights {
305			write!(f, "-")?;
306		}
307
308		write!(f, " ")?;
309		if let Some(sq) = self.get_en_passant() {
310			write!(f, "{}", sq)?;
311		} else {
312			write!(f, "-")?;
313		}
314
315		write!(f, " 0 1")
316	}
317}
318
319impl Default for BoardBuilder {
320	fn default() -> BoardBuilder {
321		BoardBuilder::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()
322	}
323}
324
325impl FromStr for BoardBuilder {
326	type Err = Error;
327
328	fn from_str(value: &str) -> Result<Self, Self::Err> {
329		let mut cur_rank = Rank::Eighth;
330		let mut cur_file = File::A;
331		let mut fen = &mut BoardBuilder::new();
332
333		let tokens: Vec<&str> = value.split(' ').collect();
334		if tokens.len() < 4 {
335			return Err(Error::InvalidFen { fen: value.to_string() });
336		}
337
338		let pieces = tokens[0];
339		let side = tokens[1];
340		let castles = tokens[2];
341		let ep = tokens[3];
342
343		for x in pieces.chars() {
344			match x {
345				'/' => {
346					cur_rank = cur_rank.down();
347					cur_file = File::A;
348				}
349				'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {
350					cur_file = File::from_index(cur_file.to_index() + (x as usize) - ('0' as usize));
351				}
352				'r' => {
353					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Rook, Color::Black));
354					cur_file = cur_file.right();
355				}
356				'R' => {
357					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Rook, Color::White));
358					cur_file = cur_file.right();
359				}
360				'n' => {
361					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Knight, Color::Black));
362					cur_file = cur_file.right();
363				}
364				'N' => {
365					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Knight, Color::White));
366					cur_file = cur_file.right();
367				}
368				'b' => {
369					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Bishop, Color::Black));
370					cur_file = cur_file.right();
371				}
372				'B' => {
373					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Bishop, Color::White));
374					cur_file = cur_file.right();
375				}
376				'p' => {
377					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Pawn, Color::Black));
378					cur_file = cur_file.right();
379				}
380				'P' => {
381					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Pawn, Color::White));
382					cur_file = cur_file.right();
383				}
384				'q' => {
385					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Queen, Color::Black));
386					cur_file = cur_file.right();
387				}
388				'Q' => {
389					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::Queen, Color::White));
390					cur_file = cur_file.right();
391				}
392				'k' => {
393					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::King, Color::Black));
394					cur_file = cur_file.right();
395				}
396				'K' => {
397					fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::King, Color::White));
398					cur_file = cur_file.right();
399				}
400				_ => {
401					return Err(Error::InvalidFen { fen: value.to_string() });
402				}
403			}
404		}
405		match side {
406			"w" | "W" => fen = fen.side_to_move(Color::White),
407			"b" | "B" => fen = fen.side_to_move(Color::Black),
408			_ => return Err(Error::InvalidFen { fen: value.to_string() }),
409		}
410
411		if castles.contains("K") && castles.contains("Q") {
412			fen.castle_rights[Color::White.to_index()] = CastleRights::Both;
413		} else if castles.contains("K") {
414			fen.castle_rights[Color::White.to_index()] = CastleRights::KingSide;
415		} else if castles.contains("Q") {
416			fen.castle_rights[Color::White.to_index()] = CastleRights::QueenSide;
417		} else {
418			fen.castle_rights[Color::White.to_index()] = CastleRights::NoRights;
419		}
420
421		if castles.contains("k") && castles.contains("q") {
422			fen.castle_rights[Color::Black.to_index()] = CastleRights::Both;
423		} else if castles.contains("k") {
424			fen.castle_rights[Color::Black.to_index()] = CastleRights::KingSide;
425		} else if castles.contains("q") {
426			fen.castle_rights[Color::Black.to_index()] = CastleRights::QueenSide;
427		} else {
428			fen.castle_rights[Color::Black.to_index()] = CastleRights::NoRights;
429		}
430
431		if let Ok(sq) = Square::from_str(ep) {
432			fen = fen.en_passant(Some(sq.get_file()));
433		}
434
435		Ok(*fen)
436	}
437}
438
439impl From<&Board> for BoardBuilder {
440	fn from(board: &Board) -> Self {
441		let mut pieces = vec![];
442		for sq in ALL_SQUARES.iter() {
443			if let Some(piece) = board.piece_on(*sq) {
444				let color = board.color_on(*sq).unwrap();
445				pieces.push((*sq, piece, color));
446			}
447		}
448
449		BoardBuilder::setup(
450			&pieces,
451			board.side_to_move(),
452			board.castle_rights(Color::White),
453			board.castle_rights(Color::Black),
454			board.en_passant().map(|sq| sq.get_file()),
455		)
456	}
457}
458
459impl From<Board> for BoardBuilder {
460	fn from(board: Board) -> Self {
461		(&board).into()
462	}
463}
464
465#[cfg(test)]
466use crate::bitboard::BitBoard;
467#[cfg(test)]
468use std::convert::TryInto;
469
470#[test]
471fn check_initial_position() {
472	let initial_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
473	let fen: BoardBuilder = Board::default().into();
474	let computed_initial_fen = format!("{}", fen);
475	assert_eq!(computed_initial_fen, initial_fen);
476
477	let pass_through = format!("{}", BoardBuilder::default());
478	assert_eq!(pass_through, initial_fen);
479}
480
481#[test]
482fn invalid_castle_rights() {
483	let res: Result<Board, _> = BoardBuilder::new()
484		.piece(Square::A1, Piece::King, Color::White)
485		.piece(Square::A8, Piece::King, Color::Black)
486		.castle_rights(Color::White, CastleRights::Both)
487		.try_into();
488	assert!(res.is_err());
489}
490
491#[test]
492fn test_kissing_kings() {
493	let res: Result<Board, _> = BoardBuilder::new().piece(Square::A1, Piece::King, Color::White).piece(Square::A2, Piece::King, Color::Black).try_into();
494	assert!(res.is_err());
495}
496
497#[test]
498fn test_in_check() {
499	let mut bb: BoardBuilder = BoardBuilder::new();
500	bb.piece(Square::A1, Piece::King, Color::White)
501		.piece(Square::A8, Piece::King, Color::Black)
502		.piece(Square::H1, Piece::Rook, Color::Black);
503
504	let board: Board = (&bb).try_into().unwrap();
505	assert_eq!(*board.checkers(), BitBoard::from_square(Square::H1));
506
507	bb.side_to_move(Color::Black);
508	let res: Result<Board, _> = bb.try_into();
509	assert!(res.is_err()); // My opponent cannot be in check when it's my move.
510}