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#[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)>,
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 pub fn get_side_to_move(&self) -> Color {
137 self.side_to_move
138 }
139
140 pub fn get_castle_rights(&self, color: Color) -> CastleRights {
149 self.castle_rights[color.to_index()]
150 }
151
152 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 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 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 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 pub fn clear_square<'a>(&'a mut self, square: Square) -> &'a mut Self {
243 self[square] = None;
244 self
245 }
246
247 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()); }