1use std::fmt;
4use std::ops::{Index, IndexMut};
5use std::str::FromStr;
6
7use crate::*;
8
9#[derive(Copy, Clone, Eq, PartialEq, Debug)]
48pub struct Board {
49 squares: [Option<(Piece, Color)>; NUM_SQUARES],
50 side_to_move: Color,
51 castle_rights: [CastleRights; NUM_COLORS],
52 en_passant: Option<Square>,
53 halfmoves: u64,
54 fullmoves: u64,
55}
56
57impl Board {
58 pub fn new() -> Self {
62 Board {
63 squares: [None; NUM_SQUARES],
64 side_to_move: Color::White,
65 castle_rights: [CastleRights::NoRights; NUM_COLORS],
66 en_passant: None,
67 halfmoves: 0,
68 fullmoves: 1,
69 }
70 }
71
72 pub fn side_to_move(&self) -> Color {
74 self.side_to_move
75 }
76
77 pub fn castle_rights(&self, color: Color) -> CastleRights {
79 self.castle_rights[color.to_index()]
80 }
81
82 pub fn en_passant(&self) -> Option<Square> {
84 self.en_passant
85 }
86
87 pub fn halfmoves(&self) -> u64 {
89 self.halfmoves
90 }
91
92 pub fn fullmoves(&self) -> u64 {
94 self.fullmoves
95 }
96
97 pub fn state(&self) -> GameState {
99 let mut state = GameState::Ongoing;
100 if !self.has_any_move() {
101 state = GameState::Stalemate;
102 if self.is_check() {
103 state = GameState::Checkmates(self.side_to_move);
104 }
105 }
106 state
107 }
108
109 pub fn is_valid(&self, m: ChessMove) -> bool {
111 let mut is_valid = false;
112 if let Some(side) = self.color_on(m.from) {
113 if side == self.side_to_move {
114 if self.get_valid_moves(m.from).contains(&m.to) {
115 is_valid = true;
116 }
117 }
118 }
119 is_valid
120 }
121
122 pub fn is_legal(&self, m: ChessMove) -> bool {
124 let mut is_legal = false;
125 if let Some(side) = self.color_on(m.from) {
126 if side == self.side_to_move {
127 if self.get_legal_moves(m.from).contains(&m.to) {
128 is_legal = true;
129 }
130 }
131 }
132 is_legal
133 }
134
135 pub fn update(&mut self, m: ChessMove) {
150 let piece_from = self.piece_on(m.from).unwrap();
151 let side = self.side_to_move;
152 let mut new_en_passant = false;
153 let reset_halfmove = self.piece_on_is(m.from, Piece::Pawn) || self.is_occupied(m.to);
154
155 match piece_from {
156 Piece::Pawn => {
158 self[m.from] = None;
159 self[m.to] = Some((Piece::Pawn, side));
160 if self.en_passant == Some(m.to) {
162 match side {
163 Color::White => self[m.to.down()] = None,
164 Color::Black => self[m.to.up()] = None,
165 }
166 }
167 if m.distance() == 2 {
169 if self.on_is(m.to.left(), (Piece::Pawn, !side))
170 || self.on_is(m.to.right(), (Piece::Pawn, !side))
171 {
172 self.en_passant = Some(m.to.backward(side));
173 new_en_passant = true;
174 }
175 } else {
176 self.en_passant = None;
177 }
178
179 if m.to.rank_for(side) == Rank::Eighth {
181 self[m.to] = Some((Piece::Queen, side));
182 }
183 }
184 Piece::King => {
186 if m.from.distance(m.to) == 2 {
187 if self.castle_rights(side).has_kingside() && m.from.file() < m.to.file() {
188 self[m.from] = None;
190 self[m.to] = Some((Piece::King, side));
191 self[m.to.right()] = None;
192 self[m.to.left()] = Some((Piece::Rook, side));
193 } else if self.castle_rights(side).has_queenside()
194 && m.to.file() < m.from.file()
195 {
196 self[m.from] = None;
198 self[m.to] = Some((Piece::King, side));
199 self[m.to.left().left()] = None;
200 self[m.to.right()] = Some((Piece::Rook, side));
201 } else {
202 panic!("Error::InvalidMove: Board: {}, invalid_move: {}", self, m);
203 }
204 } else {
205 self[m.from] = None;
207 self[m.to] = Some((Piece::King, side));
208 }
209
210 self.remove_castle_rights(side, CastleRights::Both);
212 }
213 Piece::Rook => {
215 match m.from {
217 Square::A1 | Square::A8 => {
218 self.remove_castle_rights(side, CastleRights::QueenSide)
219 }
220 Square::H1 | Square::H8 => {
221 self.remove_castle_rights(side, CastleRights::KingSide)
222 }
223 _ => {}
224 }
225 self[m.from] = None;
226 self[m.to] = Some((Piece::Rook, side));
227 }
228 _ => {
229 self[m.from] = None;
230 self[m.to] = Some((piece_from, side));
231 }
232 }
233
234 self.side_to_move = !self.side_to_move;
235 if !new_en_passant {
236 self.en_passant = None;
237 }
238 self.halfmoves += 1;
239 if reset_halfmove {
240 self.halfmoves = 0;
241 }
242 if self.side_to_move == Color::White {
243 self.fullmoves += 1;
244 }
245 }
246
247 pub fn remove_castle_rights(&mut self, color: Color, remove: CastleRights) {
265 let index = self.castle_rights(color).to_index() & !remove.to_index();
266 self.castle_rights[color.to_index()] = CastleRights::from_index(index);
267 }
268
269 pub fn piece_on(&self, square: Square) -> Option<Piece> {
271 self.squares[square.to_index()].map(|(piece, _)| piece)
272 }
273
274 pub fn piece_on_is(&self, square: Square, piece: Piece) -> bool {
276 matches!(self.piece_on(square), Some(real_piece) if real_piece == piece)
277 }
278
279 pub fn color_on(&self, square: Square) -> Option<Color> {
281 self.squares[square.to_index()].map(|(_, color)| color)
282 }
283
284 pub fn color_on_is(&self, square: Square, color: Color) -> bool {
286 matches!(self.color_on(square), Some(real_color) if real_color == color)
287 }
288
289 pub fn on(&self, square: Square) -> Option<(Piece, Color)> {
291 self.squares[square.to_index()].map(|(piece, color)| (piece, color))
292 }
293
294 pub fn on_is(&self, square: Square, (piece, color): (Piece, Color)) -> bool {
296 matches!(self.on(square), Some((real_piece, real_color)) if real_color == color && real_piece == piece)
297 }
298
299 pub fn is_pinned(&self, square: Square) -> bool {
304 if self.color_on(square) == Some(self.side_to_move) {
305 !self.has_legal_move(square)
306 } else {
307 false
308 }
309 }
310
311 pub fn pinned(&self) -> Vec<Square> {
313 let mut pinned = Vec::new();
314 for square in ALL_SQUARES {
315 if self.is_pinned(square) {
316 pinned.push(square);
317 }
318 }
319 pinned
320 }
321
322 pub fn king_of(&self, color: Color) -> Square {
324 for square in ALL_SQUARES {
325 if self.on_is(square, (Piece::King, color)) {
326 return square;
327 }
328 }
329 panic!("King square of {color:?} not found")
330 }
331
332 pub fn is_empty(&self, square: Square) -> bool {
334 self.squares[square.to_index()].is_none()
335 }
336
337 pub fn is_occupied(&self, square: Square) -> bool {
339 self.squares[square.to_index()].is_some()
340 }
341
342 pub fn is_check(&self) -> bool {
344 let king_square = self.king_of(self.side_to_move);
345 let enemy_color = !self.side_to_move;
346 self.is_targeted(king_square, enemy_color)
347 }
348
349 fn is_exposing_move(&self, m: ChessMove) -> bool {
353 let mut next_board = *self;
354 let side = self.side_to_move;
355 let enemy_side = !side;
356 next_board.update(m);
357 if next_board.is_targeted(next_board.king_of(side), enemy_side) {
358 return true;
359 }
360 false
361 }
362
363 pub fn is_targeted(&self, target: Square, attacker: Color) -> bool {
367 let mut is_targeted = false;
368 for from_square in ALL_SQUARES {
369 if self.color_on_is(from_square, attacker) {
370 if self.get_valid_moves(from_square).contains(&target) {
371 is_targeted = true;
372 break;
373 }
374 }
375 }
376 is_targeted
377 }
378
379 pub fn is_not_targeted(&self, target: Square, attacker: Color) -> bool {
383 !self.is_targeted(target, attacker)
384 }
385
386 pub fn has_valid_move(&self, square: Square) -> bool {
392 !self.get_valid_moves(square).is_empty()
393 }
394
395 pub fn has_legal_move(&self, square: Square) -> bool {
399 !self.get_legal_moves(square).is_empty()
400 }
401
402 pub fn has_any_move(&self) -> bool {
406 for from_square in ALL_SQUARES {
407 if self.color_on_is(from_square, self.side_to_move) {
408 if self.has_legal_move(from_square) {
409 return true;
410 }
411 }
412 }
413 false
414 }
415
416 pub fn get_valid_moves(&self, from: Square) -> Vec<Square> {
422 let mut valid_moves = Vec::new();
423 if let Some((piece_from, side)) = self.on(from) {
424 let mut dest_square;
425 match piece_from {
426 Piece::Pawn => {
427 dest_square = from.forward(side);
429 if self.is_empty(dest_square) {
430 valid_moves.push(dest_square);
431
432 dest_square = dest_square.forward(side);
434 if from.rank_for(side) == Rank::Second && self.is_empty(dest_square) {
435 valid_moves.push(dest_square);
436 }
437 }
438
439 if from.file_for(side) == File::A {
441 dest_square = from.forward(side).right();
442 if self.color_on_is(dest_square, !side)
443 || Some(dest_square) == self.en_passant
444 {
445 valid_moves.push(dest_square);
446 }
447 } else if from.file_for(side) == File::H {
448 dest_square = from.forward(side).left();
449 if self.color_on_is(dest_square, !side)
450 || Some(dest_square) == self.en_passant
451 {
452 valid_moves.push(dest_square);
453 }
454 } else {
455 dest_square = from.forward(side).right();
456 if self.color_on_is(dest_square, !side)
457 || Some(dest_square) == self.en_passant
458 {
459 valid_moves.push(dest_square);
460 }
461 dest_square = from.forward(side).left();
462 if self.color_on_is(dest_square, !side)
463 || Some(dest_square) == self.en_passant
464 {
465 valid_moves.push(dest_square);
466 }
467 }
468 }
469 Piece::Knight => {
470 let _knight_moves = vec![
471 from.up().up().left(),
472 from.up().up().right(),
473 from.right().right().up(),
474 from.right().right().down(),
475 from.down().down().right(),
476 from.down().down().left(),
477 from.left().left().down(),
478 from.left().left().up(),
479 ];
480 let mut knight_moves = Vec::new();
481 for dest_square in _knight_moves {
483 if from.distance(dest_square) == 2 {
484 knight_moves.push(dest_square);
485 }
486 }
487 for dest_square in knight_moves {
489 if !self.color_on_is(dest_square, side) {
490 valid_moves.push(dest_square);
491 }
492 }
493 }
494 Piece::Bishop => {
495 for direction in ALL_DIAGONAL {
496 match from.rank() {
497 Rank::First if direction.has(Direction::Down) => continue,
498 Rank::Eighth if direction.has(Direction::Up) => continue,
499 _ => {}
500 }
501 match from.file() {
502 File::A if direction.has(Direction::Left) => continue,
503 File::H if direction.has(Direction::Right) => continue,
504 _ => {}
505 }
506 let mut old_square = from;
507 dest_square = old_square.follow_direction(direction);
508 while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
509 valid_moves.push(dest_square);
510 old_square = dest_square;
511 dest_square = dest_square.follow_direction(direction);
512 }
513 if self.color_on_is(dest_square, !side)
514 && old_square.distance(dest_square) == 1
515 {
516 valid_moves.push(dest_square);
517 }
518 }
519 }
520 Piece::Rook => {
521 for direction in ALL_LINE {
522 match from.rank() {
523 Rank::First if direction.has(Direction::Down) => continue,
524 Rank::Eighth if direction.has(Direction::Up) => continue,
525 _ => {}
526 }
527 match from.file() {
528 File::A if direction.has(Direction::Left) => continue,
529 File::H if direction.has(Direction::Right) => continue,
530 _ => {}
531 }
532 let mut old_square = from;
533 dest_square = old_square.follow_direction(direction);
534 while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
535 valid_moves.push(dest_square);
536 old_square = dest_square;
537 dest_square = dest_square.follow_direction(direction);
538 }
539 if self.color_on_is(dest_square, !side)
540 && old_square.distance(dest_square) == 1
541 {
542 valid_moves.push(dest_square);
543 }
544 }
545 }
546 Piece::Queen => {
547 for direction in ALL_DIRECTION {
548 match from.rank() {
549 Rank::First if direction.has(Direction::Down) => continue,
550 Rank::Eighth if direction.has(Direction::Up) => continue,
551 _ => {}
552 }
553 match from.file() {
554 File::A if direction.has(Direction::Left) => continue,
555 File::H if direction.has(Direction::Right) => continue,
556 _ => {}
557 }
558 let mut old_square = from;
559 dest_square = old_square.follow_direction(direction);
560 while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
561 valid_moves.push(dest_square);
562 old_square = dest_square;
563 dest_square = dest_square.follow_direction(direction);
564 }
565 if self.color_on_is(dest_square, !side)
566 && old_square.distance(dest_square) == 1
567 {
568 valid_moves.push(dest_square);
569 }
570 }
571 }
572 Piece::King => {
573 for direction in ALL_DIRECTION {
574 match from.rank() {
575 Rank::First if direction.has(Direction::Down) => continue,
576 Rank::Eighth if direction.has(Direction::Up) => continue,
577 _ => {}
578 }
579 match from.file() {
580 File::A if direction.has(Direction::Left) => continue,
581 File::H if direction.has(Direction::Right) => continue,
582 _ => {}
583 }
584 dest_square = from.follow_direction(direction);
585 if !self.color_on_is(dest_square, side) {
586 valid_moves.push(dest_square);
587 }
588 }
589 }
590 }
591 }
592 valid_moves
593 }
594
595 pub fn get_legal_moves(&self, from: Square) -> Vec<Square> {
599 let mut valid_moves = Vec::new();
600 if let Some((piece_from, side)) = self.on(from) {
601 let mut dest_square;
602 match piece_from {
603 Piece::Pawn => {
604 dest_square = from.forward(side);
606 if self.is_empty(dest_square) {
607 if !self.is_exposing_move(ChessMove::new(from, dest_square)) {
608 valid_moves.push(dest_square);
609 }
610
611 dest_square = dest_square.forward(side);
613 if from.rank_for(side) == Rank::Second
614 && self.is_empty(dest_square)
615 && !self.is_exposing_move(ChessMove::new(from, dest_square))
616 {
617 valid_moves.push(dest_square);
618 }
619 }
620
621 if from.file_for(side) == File::A {
623 dest_square = from.forward(side).right();
624 if (self.color_on_is(dest_square, !side)
625 || Some(dest_square) == self.en_passant)
626 && !self.is_exposing_move(ChessMove::new(from, dest_square))
627 {
628 valid_moves.push(dest_square);
629 }
630 } else if from.file_for(side) == File::H {
631 dest_square = from.forward(side).left();
632 if (self.color_on_is(dest_square, !side)
633 || Some(dest_square) == self.en_passant)
634 && !self.is_exposing_move(ChessMove::new(from, dest_square))
635 {
636 valid_moves.push(dest_square);
637 }
638 } else {
639 dest_square = from.forward(side).right();
640 if (self.color_on_is(dest_square, !side)
641 || Some(dest_square) == self.en_passant)
642 && !self.is_exposing_move(ChessMove::new(from, dest_square))
643 {
644 valid_moves.push(dest_square);
645 }
646 dest_square = from.forward(side).left();
647 if (self.color_on_is(dest_square, !side)
648 || Some(dest_square) == self.en_passant)
649 && !self.is_exposing_move(ChessMove::new(from, dest_square))
650 {
651 valid_moves.push(dest_square);
652 }
653 }
654 }
655 Piece::Knight => {
656 let knight_moves_rule = vec![
657 from.up().up().left(),
658 from.up().up().right(),
659 from.right().right().up(),
660 from.right().right().down(),
661 from.down().down().right(),
662 from.down().down().left(),
663 from.left().left().down(),
664 from.left().left().up(),
665 ];
666
667 for square in knight_moves_rule {
668 if from.distance(square) == 2 && !self.color_on_is(square, side) {
669 if !self.is_exposing_move(ChessMove::new(from, square)) {
670 valid_moves.push(square);
671 }
672 }
673 }
674 }
675 Piece::Bishop => {
676 for direction in ALL_DIAGONAL {
677 match from.rank() {
678 Rank::First if direction.has(Direction::Down) => continue,
679 Rank::Eighth if direction.has(Direction::Up) => continue,
680 _ => {}
681 }
682 match from.file() {
683 File::A if direction.has(Direction::Left) => continue,
684 File::H if direction.has(Direction::Right) => continue,
685 _ => {}
686 }
687 let mut old_square = from;
688 dest_square = old_square.follow_direction(direction);
689 while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
690 if !self.is_exposing_move(ChessMove::new(from, dest_square)) {
691 valid_moves.push(dest_square);
692 }
693 old_square = dest_square;
694 dest_square = dest_square.follow_direction(direction);
695 }
696 if self.color_on_is(dest_square, !side)
697 && old_square.distance(dest_square) == 1
698 && !self.is_exposing_move(ChessMove::new(from, dest_square))
699 {
700 valid_moves.push(dest_square);
701 }
702 }
703 }
704 Piece::Rook => {
705 for direction in ALL_LINE {
706 match from.rank() {
707 Rank::First if direction.has(Direction::Down) => continue,
708 Rank::Eighth if direction.has(Direction::Up) => continue,
709 _ => {}
710 }
711 match from.file() {
712 File::A if direction.has(Direction::Left) => continue,
713 File::H if direction.has(Direction::Right) => continue,
714 _ => {}
715 }
716 let mut old_square = from;
717 dest_square = old_square.follow_direction(direction);
718 while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
719 if !self.is_exposing_move(ChessMove::new(from, dest_square)) {
720 valid_moves.push(dest_square);
721 }
722 old_square = dest_square;
723 dest_square = dest_square.follow_direction(direction);
724 }
725 if self.color_on_is(dest_square, !side)
726 && old_square.distance(dest_square) == 1
727 && !self.is_exposing_move(ChessMove::new(from, dest_square))
728 {
729 valid_moves.push(dest_square);
730 }
731 }
732 }
733 Piece::Queen => {
734 for direction in ALL_DIRECTION {
735 match from.rank() {
736 Rank::First if direction.has(Direction::Down) => continue,
737 Rank::Eighth if direction.has(Direction::Up) => continue,
738 _ => {}
739 }
740 match from.file() {
741 File::A if direction.has(Direction::Left) => continue,
742 File::H if direction.has(Direction::Right) => continue,
743 _ => {}
744 }
745 let mut old_square = from;
746 dest_square = old_square.follow_direction(direction);
747 while self.is_empty(dest_square) && old_square.distance(dest_square) == 1 {
748 if !self.is_exposing_move(ChessMove::new(from, dest_square)) {
749 valid_moves.push(dest_square);
750 }
751 old_square = dest_square;
752 dest_square = dest_square.follow_direction(direction);
753 }
754 if self.color_on_is(dest_square, !side)
755 && old_square.distance(dest_square) == 1
756 && !self.is_exposing_move(ChessMove::new(from, dest_square))
757 {
758 valid_moves.push(dest_square);
759 }
760 }
761 }
762 Piece::King => {
763 for direction in ALL_DIRECTION {
764 match from.rank() {
765 Rank::First if direction.has(Direction::Down) => continue,
766 Rank::Eighth if direction.has(Direction::Up) => continue,
767 _ => {}
768 }
769 match from.file() {
770 File::A if direction.has(Direction::Left) => continue,
771 File::H if direction.has(Direction::Right) => continue,
772 _ => {}
773 }
774 dest_square = from.follow_direction(direction);
775 if !self.color_on_is(dest_square, side)
776 && !self.is_exposing_move(ChessMove::new(from, dest_square))
777 {
778 valid_moves.push(dest_square);
779 }
780 }
781 if self.castle_rights(side).has_kingside() {
782 if !self.color_on_is(from.right().right(), side)
783 && !self.color_on_is(from.right(), side)
784 {
785 if !self.is_targeted(from, !side)
786 && !self.is_targeted(from.right(), !side)
787 && !self.is_targeted(from.right().right(), !side)
788 {
789 valid_moves.push(from.right().right());
790 }
791 }
792 }
793 if self.castle_rights(side).has_queenside() {
794 if !self.color_on_is(from.left().left(), side)
795 && !self.color_on_is(from.left(), side)
796 {
797 if !self.is_targeted(from, !side)
798 && !self.is_targeted(from.left(), !side)
799 && !self.is_targeted(from.left().left(), !side)
800 && !self.is_targeted(from.left().left().left(), !side)
801 {
802 valid_moves.push(from.left().left());
803 }
804 }
805 }
806 }
807 }
808 }
809 valid_moves
810 }
811
812 pub fn get_line(&self, from: Square, direction: Direction) -> Vec<Square> {
840 let mut line = Vec::with_capacity(7);
841 let mut current_square = from.follow_direction(direction);
842 while self.is_empty(current_square) {
843 line.push(current_square);
844 current_square = current_square.follow_direction(direction);
845 }
846 line.push(current_square);
847 line
848 }
849}
850
851impl Default for Board {
852 fn default() -> Self {
867 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()
868 }
869}
870
871impl FromStr for Board {
872 type Err = Error;
873
874 fn from_str(value: &str) -> Result<Self, Self::Err> {
878 let mut cur_rank = Rank::Eighth;
879 let mut cur_file = File::A;
880 let mut board = Board::new();
881
882 let tokens: Vec<&str> = value.split(' ').collect();
883 if tokens.len() < 6 {
884 return Err(Error::InvalidFen {
885 fen: value.to_string(),
886 });
887 }
888
889 let pieces = tokens[0];
890 let side = tokens[1];
891 let castles = tokens[2];
892 let ep = tokens[3];
893 let halfmoves = tokens[4];
894 let fullmoves = tokens[5];
895
896 for x in pieces.chars() {
898 match x {
899 '/' => {
900 cur_rank = cur_rank.down();
901 cur_file = File::A;
902 }
903 '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {
904 cur_file = File::new(cur_file.to_index() + (x as usize) - ('0' as usize));
905 }
906 'r' => {
907 board[Square::make_square(cur_file, cur_rank)] =
908 Some((Piece::Rook, Color::Black));
909 cur_file = cur_file.right();
910 }
911 'R' => {
912 board[Square::make_square(cur_file, cur_rank)] =
913 Some((Piece::Rook, Color::White));
914 cur_file = cur_file.right();
915 }
916 'n' => {
917 board[Square::make_square(cur_file, cur_rank)] =
918 Some((Piece::Knight, Color::Black));
919 cur_file = cur_file.right();
920 }
921 'N' => {
922 board[Square::make_square(cur_file, cur_rank)] =
923 Some((Piece::Knight, Color::White));
924 cur_file = cur_file.right();
925 }
926 'b' => {
927 board[Square::make_square(cur_file, cur_rank)] =
928 Some((Piece::Bishop, Color::Black));
929 cur_file = cur_file.right();
930 }
931 'B' => {
932 board[Square::make_square(cur_file, cur_rank)] =
933 Some((Piece::Bishop, Color::White));
934 cur_file = cur_file.right();
935 }
936 'p' => {
937 board[Square::make_square(cur_file, cur_rank)] =
938 Some((Piece::Pawn, Color::Black));
939 cur_file = cur_file.right();
940 }
941 'P' => {
942 board[Square::make_square(cur_file, cur_rank)] =
943 Some((Piece::Pawn, Color::White));
944 cur_file = cur_file.right();
945 }
946 'q' => {
947 board[Square::make_square(cur_file, cur_rank)] =
948 Some((Piece::Queen, Color::Black));
949 cur_file = cur_file.right();
950 }
951 'Q' => {
952 board[Square::make_square(cur_file, cur_rank)] =
953 Some((Piece::Queen, Color::White));
954 cur_file = cur_file.right();
955 }
956 'k' => {
957 board[Square::make_square(cur_file, cur_rank)] =
958 Some((Piece::King, Color::Black));
959 cur_file = cur_file.right();
960 }
961 'K' => {
962 board[Square::make_square(cur_file, cur_rank)] =
963 Some((Piece::King, Color::White));
964 cur_file = cur_file.right();
965 }
966 _ => {
967 return Err(Error::InvalidFen {
968 fen: value.to_string(),
969 });
970 }
971 }
972 }
973
974 match side {
976 "w" | "W" => board.side_to_move = Color::White,
977 "b" | "B" => board.side_to_move = Color::Black,
978 _ => {
979 return Err(Error::InvalidFen {
980 fen: value.to_string(),
981 })
982 }
983 }
984
985 if castles.contains('K') && castles.contains('Q') {
987 board.castle_rights[Color::White.to_index()] = CastleRights::Both;
988 } else if castles.contains('K') {
989 board.castle_rights[Color::White.to_index()] = CastleRights::KingSide;
990 } else if castles.contains('Q') {
991 board.castle_rights[Color::White.to_index()] = CastleRights::QueenSide;
992 } else {
993 board.castle_rights[Color::White.to_index()] = CastleRights::NoRights;
994 }
995
996 if castles.contains('k') && castles.contains('q') {
997 board.castle_rights[Color::Black.to_index()] = CastleRights::Both;
998 } else if castles.contains('k') {
999 board.castle_rights[Color::Black.to_index()] = CastleRights::KingSide;
1000 } else if castles.contains('q') {
1001 board.castle_rights[Color::Black.to_index()] = CastleRights::QueenSide;
1002 } else {
1003 board.castle_rights[Color::Black.to_index()] = CastleRights::NoRights;
1004 }
1005
1006 if let Ok(square) = Square::from_str(ep) {
1008 board.en_passant = Some(square);
1009 }
1010
1011 board.halfmoves = halfmoves.parse().unwrap_or(0);
1013 board.fullmoves = fullmoves.parse().unwrap_or(1);
1014
1015 Ok(board)
1016 }
1017}
1018
1019impl fmt::Display for Board {
1020 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1021 let mut count = 0;
1023 for rank in ALL_RANKS.iter().rev() {
1024 for file in ALL_FILES.iter() {
1025 let square_index = Square::make_square(*file, *rank).to_index();
1026
1027 if let Some((piece, color)) = self.squares[square_index] {
1028 if count != 0 {
1029 write!(f, "{}", count)?;
1030 count = 0;
1031 }
1032 write!(f, "{}", piece.to_string(color))?;
1033 } else {
1034 count += 1;
1035 }
1036 }
1037
1038 if count != 0 {
1039 write!(f, "{}", count)?;
1040 }
1041
1042 if *rank != Rank::First {
1043 write!(f, "/")?;
1044 }
1045 count = 0;
1046 }
1047
1048 write!(f, " ")?;
1049
1050 if self.side_to_move == Color::White {
1052 write!(f, "w ")?;
1053 } else {
1054 write!(f, "b ")?;
1055 }
1056
1057 write!(
1059 f,
1060 "{}",
1061 self.castle_rights(Color::White).to_string(Color::White)
1062 )?;
1063 write!(
1064 f,
1065 "{}",
1066 self.castle_rights(Color::Black).to_string(Color::Black)
1067 )?;
1068 if self.castle_rights[0] == CastleRights::NoRights
1069 && self.castle_rights[1] == CastleRights::NoRights
1070 {
1071 write!(f, "-")?;
1072 }
1073
1074 write!(f, " ")?;
1075
1076 if let Some(sq) = self.en_passant() {
1078 write!(f, "{}", sq)?;
1079 } else {
1080 write!(f, "-")?;
1081 }
1082
1083 write!(f, " {} {}", self.halfmoves, self.fullmoves)
1085 }
1086}
1087
1088impl Index<Square> for Board {
1089 type Output = Option<(Piece, Color)>;
1090
1091 fn index(&self, index: Square) -> &Self::Output {
1092 &self.squares[index.to_index()]
1093 }
1094}
1095
1096impl IndexMut<Square> for Board {
1097 fn index_mut(&mut self, index: Square) -> &mut Self::Output {
1098 &mut self.squares[index.to_index()]
1099 }
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104 use super::*;
1105
1106 #[test]
1107 #[ignore]
1108 fn state() {
1109 todo!()
1110 }
1111
1112 #[test]
1113 #[ignore]
1114 fn is_valid() {
1115 todo!()
1116 }
1117
1118 #[test]
1119 #[ignore]
1120 fn is_legal() {
1121 todo!()
1122 }
1123
1124 #[test]
1125 #[ignore]
1126 fn update() {
1127 todo!()
1128 }
1129
1130 #[test]
1131 fn remove_castle_rights() {
1132 let mut board = Board::default();
1133
1134 assert_eq!(board.castle_rights(Color::White), CastleRights::Both);
1135 assert_eq!(board.castle_rights(Color::Black), CastleRights::Both);
1136
1137 board.remove_castle_rights(Color::White, CastleRights::QueenSide);
1138 board.remove_castle_rights(Color::Black, CastleRights::NoRights);
1139
1140 assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide);
1141 assert_eq!(board.castle_rights(Color::Black), CastleRights::Both);
1142
1143 board.remove_castle_rights(Color::White, CastleRights::KingSide);
1144 board.remove_castle_rights(Color::Black, CastleRights::Both);
1145
1146 assert_eq!(board.castle_rights(Color::White), CastleRights::NoRights);
1147 assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights);
1148 }
1149
1150 #[test]
1151 fn piece_on() {
1152 let board = Board::default();
1153 assert_eq!(board.piece_on(Square::A1), Some(Piece::Rook));
1154 assert_eq!(board.piece_on(Square::A2), Some(Piece::Pawn));
1155 assert_eq!(board.piece_on(Square::A3), None);
1156 }
1157
1158 #[test]
1159 fn piece_on_is() {
1160 let board = Board::default();
1161 assert!(board.piece_on_is(Square::A1, Piece::Rook));
1162 assert!(board.piece_on_is(Square::A7, Piece::Pawn));
1163 assert!(!board.piece_on_is(Square::A2, Piece::Queen));
1164 assert!(!board.piece_on_is(Square::A3, Piece::King));
1165 }
1166
1167 #[test]
1168 fn color_on() {
1169 let board = Board::default();
1170 assert_eq!(board.color_on(Square::A1), Some(Color::White));
1171 assert_eq!(board.color_on(Square::A8), Some(Color::Black));
1172 assert_eq!(board.color_on(Square::A3), None);
1173 }
1174
1175 #[test]
1176 fn color_on_is() {
1177 let board = Board::default();
1178 assert!(board.color_on_is(Square::A1, Color::White));
1179 assert!(board.color_on_is(Square::A8, Color::Black));
1180 assert!(!board.color_on_is(Square::A2, Color::Black));
1181 assert!(!board.color_on_is(Square::A3, Color::White));
1182 }
1183
1184 #[test]
1185 fn on() {
1186 let board = Board::default();
1187 assert_eq!(board.on(Square::A1), Some((Piece::Rook, Color::White)));
1188 assert_eq!(board.on(Square::A7), Some((Piece::Pawn, Color::Black)));
1189 assert_eq!(board.on(Square::A3), None);
1190 }
1191
1192 #[test]
1193 fn is_pinned() {
1194 let mut board = Board::default();
1195
1196 board.update(ChessMove::new(Square::E2, Square::E4));
1197 board.update(ChessMove::new(Square::F7, Square::F5));
1198
1199 board.update(ChessMove::new(Square::D1, Square::H5));
1200
1201 assert!(!board.is_pinned(Square::G7));
1203
1204 for square in ALL_SQUARES {
1206 if board.color_on_is(square, board.side_to_move()) {
1207 if square != Square::G7 {
1208 assert!(board.is_pinned(square));
1209 }
1210 }
1211 }
1212 }
1213
1214 #[test]
1215 fn king_of() {
1216 let board = Board::default();
1217
1218 assert_eq!(board.king_of(Color::White), Square::E1);
1219 assert_eq!(board.king_of(Color::Black), Square::E8);
1220 }
1221
1222 #[test]
1223 fn is_empty() {
1224 let board = Board::default();
1225 for square in ALL_SQUARES {
1226 if square.rank().between(Rank::Third, Rank::Sixth) {
1227 assert!(board.is_empty(square));
1228 }
1229 }
1230 }
1231
1232 #[test]
1233 fn is_occupied() {
1234 let board = Board::default();
1235 for square in ALL_SQUARES {
1236 if square.rank().between(Rank::First, Rank::Second)
1237 || square.rank().between(Rank::Seventh, Rank::Eighth)
1238 {
1239 assert!(board.is_occupied(square));
1240 }
1241 }
1242 }
1243
1244 #[test]
1245 fn is_targeted() {
1246 let fen = "r1bqk2r/pppp1ppp/2n2n2/2b1p3/2B1P3/2N2N2/PPPP1PPP/R1BQK2R w KQkq - 6 5";
1247 let board = Board::from_str(fen).expect("valid fen");
1248 assert!(board.is_targeted(Square::E4, Color::Black)); assert!(board.is_targeted(Square::E5, Color::White)); assert!(board.is_targeted(Square::F2, Color::Black)); assert!(board.is_targeted(Square::F7, Color::White)); assert!(!board.is_targeted(Square::A8, Color::White));
1267 assert!(!board.is_targeted(Square::H1, Color::Black));
1268 assert!(!board.is_targeted(Square::E1, Color::Black));
1269 assert!(!board.is_targeted(Square::D8, Color::White));
1270 }
1271
1272 #[test]
1273 #[ignore]
1274 fn is_check() {
1275 todo!()
1276 }
1277
1278 #[test]
1279 #[ignore]
1280 fn is_exposing_move() {
1281 todo!()
1282 }
1283
1284 #[test]
1285 #[ignore]
1286 fn has_valid_moves() {
1287 todo!()
1288 }
1289
1290 #[test]
1291 #[ignore]
1292 fn has_legal_moves() {
1293 todo!()
1294 }
1295
1296 #[test]
1297 #[ignore]
1298 fn get_valid_move() {
1299 todo!()
1300 }
1301
1302 #[test]
1303 #[ignore]
1304 fn get_legal_move() {
1305 todo!()
1306 }
1307
1308 #[test]
1309 #[ignore]
1310 fn get_line() {
1311 todo!()
1312 }
1313
1314 #[test]
1315 fn from_fen() {
1316 let fen = "r1bqk2r/pppp1ppp/2n2n2/2b1p3/2B1P3/2N2N2/PPPP1PPP/R1BQK2R w KQkq - 6 5";
1317 let fen_board = Board::from_str(fen).expect("valid fen");
1318
1319 let mut board = Board::default();
1320
1321 board.update(ChessMove::new(Square::E2, Square::E4));
1322 board.update(ChessMove::new(Square::E7, Square::E5));
1323
1324 board.update(ChessMove::new(Square::B1, Square::C3));
1325 board.update(ChessMove::new(Square::B8, Square::C6));
1326
1327 board.update(ChessMove::new(Square::G1, Square::F3));
1328 board.update(ChessMove::new(Square::G8, Square::F6));
1329
1330 board.update(ChessMove::new(Square::F1, Square::C4));
1331 board.update(ChessMove::new(Square::F8, Square::C5));
1332
1333 assert_eq!(board, fen_board, "update don't work?");
1334
1335 let fen = "r1bq1rk1/pppp1ppp/2n2n2/2b1p3/2B1P3/2N2N2/PPPP1PPP/R1BQK1R1 w Q - 8 6";
1337 let fen_board = Board::from_str(fen).expect("valid fen");
1338
1339 board.update(ChessMove::new(Square::H1, Square::G1)); board.update(ChessMove::new(Square::E8, Square::G8)); assert_eq!(board, fen_board, "Castle remove don't work?");
1343 }
1344
1345 #[test]
1346 fn to_fen() {
1347 let mut board = Board::default();
1348
1349 board.update(ChessMove::new(Square::E2, Square::E4));
1350 board.update(ChessMove::new(Square::E7, Square::E5));
1351
1352 board.update(ChessMove::new(Square::B1, Square::C3));
1353 board.update(ChessMove::new(Square::B8, Square::C6));
1354
1355 board.update(ChessMove::new(Square::G1, Square::F3));
1356 board.update(ChessMove::new(Square::G8, Square::F6));
1357
1358 board.update(ChessMove::new(Square::F1, Square::C4));
1359 board.update(ChessMove::new(Square::F8, Square::C5));
1360
1361 let fen = "r1bqk2r/pppp1ppp/2n2n2/2b1p3/2B1P3/2N2N2/PPPP1PPP/R1BQK2R w KQkq - 6 5";
1362 assert_eq!(board.to_string(), fen.to_string());
1363 }
1364}