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#[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 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 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 pub fn get_side_to_move(&self) -> Color {
129 self.side_to_move
130 }
131
132 pub fn get_castle_rights(&self, color: Color) -> CastleRights {
141 self.castle_rights[color.to_index()]
142 }
143
144 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 pub fn side_to_move(&mut self, color: Color) -> &mut Self {
174 self.side_to_move = color;
175 self
176 }
177
178 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 pub fn piece(&mut self, square: Square, piece: Piece, color: Color) -> &mut Self {
213 self[square] = Some((piece, color));
214 self
215 }
216
217 pub fn clear_square(&mut self, square: Square) -> &mut Self {
230 self[square] = None;
231 self
232 }
233
234 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()); }