1use crate::bitboard::{BitBoard, EMPTY};
2use crate::board_builder::BoardBuilder;
3use crate::castle_rights::CastleRights;
4use crate::chess_move::ChessMove;
5use crate::color::{ALL_COLORS, Color, NUM_COLORS};
6use crate::error::Error;
7use crate::file::File;
8use crate::magic::{
9 between, get_adjacent_files, get_bishop_rays, get_castle_moves, get_file, get_king_moves, get_knight_moves, get_pawn_attacks, get_pawn_dest_double_moves,
10 get_pawn_source_double_moves, get_rank, get_rook_rays,
11};
12use crate::movegen::*;
13use crate::piece::{ALL_PIECES, NUM_PIECES, Piece};
14use crate::square::{ALL_SQUARES, Square};
15use crate::zobrist::Zobrist;
16use std::convert::{TryFrom, TryInto};
17use std::fmt;
18use std::hash::{Hash, Hasher};
19use std::str::FromStr;
20
21#[derive(Copy, Clone, PartialEq, Eq, Debug)]
23pub struct Board {
24 pieces: [BitBoard; NUM_PIECES],
25 color_combined: [BitBoard; NUM_COLORS],
26 combined: BitBoard,
27 side_to_move: Color,
28 castle_rights: [CastleRights; NUM_COLORS],
29 pinned: BitBoard,
30 checkers: BitBoard,
31 hash: u64,
32 en_passant: Option<Square>,
33}
34
35#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
37pub enum BoardStatus {
38 Ongoing,
39 Stalemate,
40 Checkmate,
41}
42
43impl Default for Board {
45 #[inline]
46 fn default() -> Board {
47 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").expect("Valid Position")
48 }
49}
50
51impl Hash for Board {
52 fn hash<H: Hasher>(&self, state: &mut H) {
53 self.hash.hash(state);
54 }
55}
56
57impl Board {
58 fn new() -> Board {
61 Board {
62 pieces: [EMPTY; NUM_PIECES],
63 color_combined: [EMPTY; NUM_COLORS],
64 combined: EMPTY,
65 side_to_move: Color::White,
66 castle_rights: [CastleRights::NoRights; NUM_COLORS],
67 pinned: EMPTY,
68 checkers: EMPTY,
69 hash: 0,
70 en_passant: None,
71 }
72 }
73
74 #[deprecated(since = "3.1.0", note = "please use `Board::from_str(fen)?` instead")]
94 #[inline]
95 pub fn from_fen(fen: String) -> Option<Board> {
96 Board::from_str(&fen).ok()
97 }
98
99 #[deprecated(since = "3.0.0", note = "please use the MoveGen structure instead. It is faster and more idiomatic.")]
100 #[inline]
101 pub fn enumerate_moves(&self, moves: &mut [ChessMove; 256]) -> usize {
102 let movegen = MoveGen::new_legal(self);
103 let mut size = 0;
104 for m in movegen {
105 moves[size] = m;
106 size += 1;
107 }
108 size
109 }
110
111 #[inline]
151 pub fn status(&self) -> BoardStatus {
152 let moves = MoveGen::new_legal(self).len();
153 match moves {
154 0 => {
155 if self.checkers == EMPTY {
156 BoardStatus::Stalemate
157 } else {
158 BoardStatus::Checkmate
159 }
160 }
161 _ => BoardStatus::Ongoing,
162 }
163 }
164
165 #[inline]
180 pub fn combined(&self) -> &BitBoard {
181 &self.combined
182 }
183
184 #[inline]
202 pub fn color_combined(&self, color: Color) -> &BitBoard {
203 unsafe { self.color_combined.get_unchecked(color.to_index()) }
204 }
205
206 #[inline]
217 pub fn king_square(&self, color: Color) -> Square {
218 (self.pieces(Piece::King) & self.color_combined(color)).to_square()
219 }
220
221 #[inline]
237 pub fn pieces(&self, piece: Piece) -> &BitBoard {
238 unsafe { self.pieces.get_unchecked(piece.to_index()) }
239 }
240
241 #[inline]
276 pub fn castle_rights(&self, color: Color) -> CastleRights {
277 unsafe { *self.castle_rights.get_unchecked(color.to_index()) }
278 }
279
280 #[deprecated(
282 since = "3.1.0",
283 note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position."
284 )]
285 #[inline]
286 pub fn add_castle_rights(&mut self, color: Color, add: CastleRights) {
287 unsafe {
288 *self.castle_rights.get_unchecked_mut(color.to_index()) = self.castle_rights(color).add(add);
289 }
290 }
291
292 #[deprecated(
304 since = "3.1.0",
305 note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position."
306 )]
307 #[inline]
308 pub fn remove_castle_rights(&mut self, color: Color, remove: CastleRights) {
309 unsafe {
310 *self.castle_rights.get_unchecked_mut(color.to_index()) = self.castle_rights(color).remove(remove);
311 }
312 }
313
314 #[inline]
323 pub fn side_to_move(&self) -> Color {
324 self.side_to_move
325 }
326
327 #[inline]
339 pub fn my_castle_rights(&self) -> CastleRights {
340 self.castle_rights(self.side_to_move())
341 }
342
343 #[deprecated(
345 since = "3.1.0",
346 note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position."
347 )]
348 #[inline]
349 pub fn add_my_castle_rights(&mut self, add: CastleRights) {
350 let color = self.side_to_move();
351 #[allow(deprecated)]
352 self.add_castle_rights(color, add);
353 }
354
355 #[deprecated(
367 since = "3.1.0",
368 note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position."
369 )]
370 #[inline]
371 pub fn remove_my_castle_rights(&mut self, remove: CastleRights) {
372 let color = self.side_to_move();
373 #[allow(deprecated)]
374 self.remove_castle_rights(color, remove);
375 }
376
377 #[inline]
389 pub fn their_castle_rights(&self) -> CastleRights {
390 self.castle_rights(!self.side_to_move())
391 }
392
393 #[deprecated(
395 since = "3.1.0",
396 note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position."
397 )]
398 #[inline]
399 pub fn add_their_castle_rights(&mut self, add: CastleRights) {
400 let color = !self.side_to_move();
401 #[allow(deprecated)]
402 self.add_castle_rights(color, add)
403 }
404
405 #[deprecated(
417 since = "3.1.0",
418 note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position."
419 )]
420 #[inline]
421 pub fn remove_their_castle_rights(&mut self, remove: CastleRights) {
422 let color = !self.side_to_move();
423 #[allow(deprecated)]
424 self.remove_castle_rights(color, remove);
425 }
426
427 fn xor(&mut self, piece: Piece, bb: BitBoard, color: Color) {
429 unsafe {
430 *self.pieces.get_unchecked_mut(piece.to_index()) ^= bb;
431 *self.color_combined.get_unchecked_mut(color.to_index()) ^= bb;
432 self.combined ^= bb;
433 self.hash ^= Zobrist::piece(piece, bb.to_square(), color);
434 }
435 }
436
437 #[deprecated(
452 since = "3.1.0",
453 note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position."
454 )]
455 #[inline]
456 pub fn set_piece(&self, piece: Piece, color: Color, square: Square) -> Option<Board> {
457 let mut result = *self;
458 let square_bb = BitBoard::from_square(square);
459 match self.piece_on(square) {
460 None => result.xor(piece, square_bb, color),
461 Some(x) => {
462 if self.color_combined(Color::White) & square_bb == square_bb {
464 result.xor(x, square_bb, Color::White);
465 } else {
466 result.xor(x, square_bb, Color::Black);
467 }
468 result.xor(piece, square_bb, color);
470 }
471 }
472
473 result.side_to_move = !result.side_to_move;
476 result.update_pin_info();
477 if result.checkers != EMPTY {
478 return None;
479 }
480
481 result.side_to_move = !result.side_to_move;
483 result.update_pin_info();
484
485 Some(result)
486 }
487
488 #[deprecated(
501 since = "3.1.0",
502 note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position."
503 )]
504 #[inline]
505 pub fn clear_square(&self, square: Square) -> Option<Board> {
506 let mut result = *self;
507 let square_bb = BitBoard::from_square(square);
508 match self.piece_on(square) {
509 None => {}
510 Some(x) => {
511 if self.color_combined(Color::White) & square_bb == square_bb {
513 result.xor(x, square_bb, Color::White);
514 } else {
515 result.xor(x, square_bb, Color::Black);
516 }
517 }
518 }
519
520 result.side_to_move = !result.side_to_move;
523 result.update_pin_info();
524 if result.checkers != EMPTY {
525 return None;
526 }
527
528 result.side_to_move = !result.side_to_move;
530 result.update_pin_info();
531
532 Some(result)
533 }
534
535 #[inline]
553 pub fn null_move(&self) -> Option<Board> {
554 if self.checkers != EMPTY {
555 None
556 } else {
557 let mut result = *self;
558 result.side_to_move = !result.side_to_move;
559 result.remove_ep();
560 result.update_pin_info();
561 Some(result)
562 }
563 }
564
565 pub fn is_sane(&self) -> bool {
581 for x in ALL_PIECES.iter() {
583 for y in ALL_PIECES.iter() {
584 if *x != *y && self.pieces(*x) & self.pieces(*y) != EMPTY {
585 return false;
586 }
587 }
588 }
589
590 if self.color_combined(Color::White) & self.color_combined(Color::Black) != EMPTY {
592 return false;
593 }
594
595 let combined = ALL_PIECES.iter().fold(EMPTY, |cur, next| cur | self.pieces(*next));
597
598 if combined != *self.combined() {
600 return false;
601 }
602
603 if (self.pieces(Piece::King) & self.color_combined(Color::White)).popcnt() != 1 {
605 return false;
606 }
607
608 if (self.pieces(Piece::King) & self.color_combined(Color::Black)).popcnt() != 1 {
610 return false;
611 }
612
613 if let Some(x) = self.en_passant
615 && self.pieces(Piece::Pawn) & self.color_combined(!self.side_to_move) & BitBoard::from_square(x) == EMPTY
616 {
617 return false;
618 }
619
620 let mut board_copy = *self;
622 board_copy.side_to_move = !board_copy.side_to_move;
623 board_copy.update_pin_info();
624 if board_copy.checkers != EMPTY {
625 return false;
626 }
627
628 for color in ALL_COLORS.iter() {
631 let castle_rights = self.castle_rights(*color);
633
634 if castle_rights.unmoved_rooks(*color) & self.pieces(Piece::Rook) & self.color_combined(*color) != castle_rights.unmoved_rooks(*color) {
637 return false;
638 }
639 if castle_rights != CastleRights::NoRights
642 && self.pieces(Piece::King) & self.color_combined(*color) != get_file(File::E) & get_rank(color.to_my_backrank())
643 {
644 return false;
645 }
646 }
647
648 if get_king_moves(self.king_square(Color::White)) & self.pieces(Piece::King) != EMPTY {
650 return false;
651 }
652
653 true
655 }
656
657 #[inline]
659 pub fn get_hash(&self) -> u64 {
660 self.hash
661 ^ if let Some(ep) = self.en_passant { Zobrist::en_passant(ep.get_file(), !self.side_to_move) } else { 0 }
662 ^ Zobrist::castles(self.castle_rights[self.side_to_move.to_index()], self.side_to_move)
663 ^ Zobrist::castles(self.castle_rights[(!self.side_to_move).to_index()], !self.side_to_move)
664 ^ if self.side_to_move == Color::Black { Zobrist::color() } else { 0 }
665 }
666
667 #[inline]
671 pub fn get_pawn_hash(&self) -> u64 {
672 0
673 }
674
675 #[inline]
686 pub fn piece_on(&self, square: Square) -> Option<Piece> {
687 let opp = BitBoard::from_square(square);
688 if self.combined() & opp == EMPTY {
689 None
690 } else {
691 if (self.pieces(Piece::Pawn) ^ self.pieces(Piece::Knight) ^ self.pieces(Piece::Bishop)) & opp != EMPTY {
699 if self.pieces(Piece::Pawn) & opp != EMPTY {
700 Some(Piece::Pawn)
701 } else if self.pieces(Piece::Knight) & opp != EMPTY {
702 Some(Piece::Knight)
703 } else {
704 Some(Piece::Bishop)
705 }
706 } else {
707 if self.pieces(Piece::Rook) & opp != EMPTY {
708 Some(Piece::Rook)
709 } else if self.pieces(Piece::Queen) & opp != EMPTY {
710 Some(Piece::Queen)
711 } else {
712 Some(Piece::King)
713 }
714 }
715 }
716 }
717
718 #[inline]
720 pub fn color_on(&self, square: Square) -> Option<Color> {
721 if (self.color_combined(Color::White) & BitBoard::from_square(square)) != EMPTY {
722 Some(Color::White)
723 } else if (self.color_combined(Color::Black) & BitBoard::from_square(square)) != EMPTY {
724 Some(Color::Black)
725 } else {
726 None
727 }
728 }
729
730 fn remove_ep(&mut self) {
732 self.en_passant = None;
733 }
734
735 #[inline]
764 pub fn en_passant(self) -> Option<Square> {
765 self.en_passant
766 }
767
768 fn set_ep(&mut self, sq: Square) {
771 if get_adjacent_files(sq.get_file()) & get_rank(sq.get_rank()) & self.pieces(Piece::Pawn) & self.color_combined(!self.side_to_move) != EMPTY {
773 self.en_passant = Some(sq);
774 }
775 }
776
777 #[inline]
797 pub fn legal(&self, m: ChessMove) -> bool {
798 MoveGen::new_legal(self).find(|x| *x == m).is_some()
799 }
800
801 #[inline]
816 pub fn make_move_new(&self, m: ChessMove) -> Board {
817 let mut result = *self;
818 result.remove_ep();
819 result.checkers = EMPTY;
820 result.pinned = EMPTY;
821 let source = m.get_source();
822 let dest = m.get_dest();
823
824 let source_bb = BitBoard::from_square(source);
825 let dest_bb = BitBoard::from_square(dest);
826 let move_bb = source_bb ^ dest_bb;
827 let moved = self.piece_on(source).unwrap();
828
829 result.xor(moved, source_bb, self.side_to_move);
830 result.xor(moved, dest_bb, self.side_to_move);
831 if let Some(captured) = self.piece_on(dest) {
832 result.xor(captured, dest_bb, !self.side_to_move);
833 }
834
835 #[allow(deprecated)]
836 result.remove_their_castle_rights(CastleRights::square_to_castle_rights(!self.side_to_move, dest));
837
838 #[allow(deprecated)]
839 result.remove_my_castle_rights(CastleRights::square_to_castle_rights(self.side_to_move, source));
840
841 let opp_king = result.pieces(Piece::King) & result.color_combined(!result.side_to_move);
842
843 let castles = moved == Piece::King && (move_bb & get_castle_moves()) == move_bb;
844
845 let ksq = opp_king.to_square();
846
847 const CASTLE_ROOK_START: [File; 8] = [File::A, File::A, File::A, File::A, File::H, File::H, File::H, File::H];
848 const CASTLE_ROOK_END: [File; 8] = [File::D, File::D, File::D, File::D, File::F, File::F, File::F, File::F];
849
850 if moved == Piece::Knight {
851 result.checkers ^= get_knight_moves(ksq) & dest_bb;
852 } else if moved == Piece::Pawn {
853 if let Some(Piece::Knight) = m.get_promotion() {
854 result.xor(Piece::Pawn, dest_bb, self.side_to_move);
855 result.xor(Piece::Knight, dest_bb, self.side_to_move);
856 result.checkers ^= get_knight_moves(ksq) & dest_bb;
857 } else if let Some(promotion) = m.get_promotion() {
858 result.xor(Piece::Pawn, dest_bb, self.side_to_move);
859 result.xor(promotion, dest_bb, self.side_to_move);
860 } else if (source_bb & get_pawn_source_double_moves()) != EMPTY && (dest_bb & get_pawn_dest_double_moves()) != EMPTY {
861 result.set_ep(dest);
862 result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb);
863 } else if Some(dest.ubackward(self.side_to_move)) == self.en_passant {
864 result.xor(Piece::Pawn, BitBoard::from_square(dest.ubackward(self.side_to_move)), !self.side_to_move);
865 result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb);
866 } else {
867 result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb);
868 }
869 } else if castles {
870 let my_backrank = self.side_to_move.to_my_backrank();
871 let index = dest.get_file().to_index();
872 let start = BitBoard::set(my_backrank, unsafe { *CASTLE_ROOK_START.get_unchecked(index) });
873 let end = BitBoard::set(my_backrank, unsafe { *CASTLE_ROOK_END.get_unchecked(index) });
874 result.xor(Piece::Rook, start, self.side_to_move);
875 result.xor(Piece::Rook, end, self.side_to_move);
876 }
877 let attackers = result.color_combined(result.side_to_move)
879 & ((get_bishop_rays(ksq) & (result.pieces(Piece::Bishop) | result.pieces(Piece::Queen)))
880 | (get_rook_rays(ksq) & (result.pieces(Piece::Rook) | result.pieces(Piece::Queen))));
881
882 for sq in attackers {
883 let between = between(sq, ksq) & result.combined();
884 if between == EMPTY {
885 result.checkers ^= BitBoard::from_square(sq);
886 } else if between.popcnt() == 1 {
887 result.pinned ^= between;
888 }
889 }
890
891 result.side_to_move = !result.side_to_move;
892 result
893 }
894
895 #[inline]
912 pub fn make_move(&self, m: ChessMove, result: &mut Board) {
913 *result = *self;
914 result.remove_ep();
915 result.checkers = EMPTY;
916 result.pinned = EMPTY;
917 let source = m.get_source();
918 let dest = m.get_dest();
919
920 let source_bb = BitBoard::from_square(source);
921 let dest_bb = BitBoard::from_square(dest);
922 let move_bb = source_bb ^ dest_bb;
923 let moved = self.piece_on(source).unwrap();
924
925 result.xor(moved, source_bb, self.side_to_move);
926 result.xor(moved, dest_bb, self.side_to_move);
927 if let Some(captured) = self.piece_on(dest) {
928 result.xor(captured, dest_bb, !self.side_to_move);
929 }
930
931 #[allow(deprecated)]
932 result.remove_their_castle_rights(CastleRights::square_to_castle_rights(!self.side_to_move, dest));
933
934 #[allow(deprecated)]
935 result.remove_my_castle_rights(CastleRights::square_to_castle_rights(self.side_to_move, source));
936
937 let opp_king = result.pieces(Piece::King) & result.color_combined(!result.side_to_move);
938
939 let castles = moved == Piece::King && (move_bb & get_castle_moves()) == move_bb;
940
941 let ksq = opp_king.to_square();
942
943 const CASTLE_ROOK_START: [File; 8] = [File::A, File::A, File::A, File::A, File::H, File::H, File::H, File::H];
944 const CASTLE_ROOK_END: [File; 8] = [File::D, File::D, File::D, File::D, File::F, File::F, File::F, File::F];
945
946 if moved == Piece::Knight {
947 result.checkers ^= get_knight_moves(ksq) & dest_bb;
948 } else if moved == Piece::Pawn {
949 if let Some(Piece::Knight) = m.get_promotion() {
950 result.xor(Piece::Pawn, dest_bb, self.side_to_move);
951 result.xor(Piece::Knight, dest_bb, self.side_to_move);
952 result.checkers ^= get_knight_moves(ksq) & dest_bb;
953 } else if let Some(promotion) = m.get_promotion() {
954 result.xor(Piece::Pawn, dest_bb, self.side_to_move);
955 result.xor(promotion, dest_bb, self.side_to_move);
956 } else if (source_bb & get_pawn_source_double_moves()) != EMPTY && (dest_bb & get_pawn_dest_double_moves()) != EMPTY {
957 result.set_ep(dest);
958 result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb);
959 } else if Some(dest.ubackward(self.side_to_move)) == self.en_passant {
960 result.xor(Piece::Pawn, BitBoard::from_square(dest.ubackward(self.side_to_move)), !self.side_to_move);
961 result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb);
962 } else {
963 result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb);
964 }
965 } else if castles {
966 let my_backrank = self.side_to_move.to_my_backrank();
967 let index = dest.get_file().to_index();
968 let start = BitBoard::set(my_backrank, unsafe { *CASTLE_ROOK_START.get_unchecked(index) });
969 let end = BitBoard::set(my_backrank, unsafe { *CASTLE_ROOK_END.get_unchecked(index) });
970 result.xor(Piece::Rook, start, self.side_to_move);
971 result.xor(Piece::Rook, end, self.side_to_move);
972 }
973 let attackers = result.color_combined(result.side_to_move)
975 & ((get_bishop_rays(ksq) & (result.pieces(Piece::Bishop) | result.pieces(Piece::Queen)))
976 | (get_rook_rays(ksq) & (result.pieces(Piece::Rook) | result.pieces(Piece::Queen))));
977
978 for sq in attackers {
979 let between = between(sq, ksq) & result.combined();
980 if between == EMPTY {
981 result.checkers ^= BitBoard::from_square(sq);
982 } else if between.popcnt() == 1 {
983 result.pinned ^= between;
984 }
985 }
986
987 result.side_to_move = !result.side_to_move;
988 }
989
990 fn update_pin_info(&mut self) {
992 self.pinned = EMPTY;
993 self.checkers = EMPTY;
994
995 let ksq = (self.pieces(Piece::King) & self.color_combined(self.side_to_move)).to_square();
996
997 let pinners = self.color_combined(!self.side_to_move)
998 & ((get_bishop_rays(ksq) & (self.pieces(Piece::Bishop) | self.pieces(Piece::Queen)))
999 | (get_rook_rays(ksq) & (self.pieces(Piece::Rook) | self.pieces(Piece::Queen))));
1000
1001 for sq in pinners {
1002 let between = between(sq, ksq) & self.combined();
1003 if between == EMPTY {
1004 self.checkers ^= BitBoard::from_square(sq);
1005 } else if between.popcnt() == 1 {
1006 self.pinned ^= between;
1007 }
1008 }
1009
1010 self.checkers ^= get_knight_moves(ksq) & self.color_combined(!self.side_to_move) & self.pieces(Piece::Knight);
1011
1012 self.checkers ^= get_pawn_attacks(ksq, self.side_to_move, self.color_combined(!self.side_to_move) & self.pieces(Piece::Pawn));
1013 }
1014
1015 #[inline]
1017 pub fn pinned(&self) -> &BitBoard {
1018 &self.pinned
1019 }
1020
1021 #[inline]
1023 pub fn checkers(&self) -> &BitBoard {
1024 &self.checkers
1025 }
1026}
1027
1028impl fmt::Display for Board {
1029 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1030 let fen: BoardBuilder = self.into();
1031 write!(f, "{}", fen)
1032 }
1033}
1034
1035impl TryFrom<&BoardBuilder> for Board {
1036 type Error = Error;
1037
1038 fn try_from(fen: &BoardBuilder) -> Result<Self, Self::Error> {
1039 let mut board = Board::new();
1040
1041 for sq in ALL_SQUARES.iter() {
1042 if let Some((piece, color)) = fen[*sq] {
1043 board.xor(piece, BitBoard::from_square(*sq), color);
1044 }
1045 }
1046
1047 board.side_to_move = fen.get_side_to_move();
1048
1049 if let Some(ep) = fen.get_en_passant() {
1050 board.side_to_move = !board.side_to_move;
1051 board.set_ep(ep);
1052 board.side_to_move = !board.side_to_move;
1053 }
1054
1055 #[allow(deprecated)]
1056 board.add_castle_rights(Color::White, fen.get_castle_rights(Color::White));
1057 #[allow(deprecated)]
1058 board.add_castle_rights(Color::Black, fen.get_castle_rights(Color::Black));
1059
1060 board.update_pin_info();
1061
1062 if board.is_sane() { Ok(board) } else { Err(Error::InvalidBoard) }
1063 }
1064}
1065
1066impl TryFrom<&mut BoardBuilder> for Board {
1067 type Error = Error;
1068
1069 fn try_from(fen: &mut BoardBuilder) -> Result<Self, Self::Error> {
1070 (&*fen).try_into()
1071 }
1072}
1073
1074impl TryFrom<BoardBuilder> for Board {
1075 type Error = Error;
1076
1077 fn try_from(fen: BoardBuilder) -> Result<Self, Self::Error> {
1078 (&fen).try_into()
1079 }
1080}
1081
1082impl FromStr for Board {
1083 type Err = Error;
1084
1085 fn from_str(value: &str) -> Result<Self, Self::Err> {
1086 BoardBuilder::from_str(value)?.try_into()
1087 }
1088}
1089
1090#[test]
1091fn test_null_move_en_passant() {
1092 let start = Board::from_str("rnbqkbnr/pppp2pp/8/4pP2/8/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 0").unwrap();
1093 let expected = Board::from_str("rnbqkbnr/pppp2pp/8/4pP2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 0").unwrap();
1094 assert_eq!(start.null_move().unwrap(), expected);
1095}