diff --git a/src/board.rs b/src/board.rs index ff503122..80679ff3 100644 --- a/src/board.rs +++ b/src/board.rs @@ -32,6 +32,7 @@ pub struct Board { checkers: BitBoard, hash: u64, en_passant: Option, + en_passant_target: Option, } /// What is the status of this game? @@ -71,6 +72,7 @@ impl Board { checkers: EMPTY, hash: 0, en_passant: None, + en_passant_target: None, } } @@ -807,6 +809,39 @@ impl Board { self.en_passant } + /// Give me the en_passant_target square, if it exists. + /// + /// ``` + /// use chess::{Board, ChessMove, Square}; + /// + /// let move1 = ChessMove::new(Square::D2, + /// Square::D4, + /// None); + /// + /// let move2 = ChessMove::new(Square::H7, + /// Square::H5, + /// None); + /// + /// let move3 = ChessMove::new(Square::D4, + /// Square::D5, + /// None); + /// + /// let move4 = ChessMove::new(Square::E7, + /// Square::E5, + /// None); + /// + /// let board = Board::default().make_move_new(move1) + /// .make_move_new(move2) + /// .make_move_new(move3) + /// .make_move_new(move4); + /// + /// assert_eq!(board.en_passant_target(), Some(Square::E6)); + /// ``` + #[inline] + pub fn en_passant_target(self) -> Option { + self.en_passant_target + } + /// Set the en_passant square. Note: This must only be called when self.en_passant is already /// None. fn set_ep(&mut self, sq: Square) { @@ -821,6 +856,10 @@ impl Board { } } + fn set_ep_target(&mut self, sq: Option) { + self.en_passant_target = sq; + } + /// Is a particular move legal? This function is very slow, but will work on unsanitized /// input. /// @@ -904,6 +943,9 @@ impl Board { result.xor(captured, dest_bb, !self.side_to_move); } + // Reset en_passant target + result.set_ep_target(None); + #[allow(deprecated)] result.remove_their_castle_rights(CastleRights::square_to_castle_rights( !self.side_to_move, @@ -957,6 +999,7 @@ impl Board { && (dest_bb & get_pawn_dest_double_moves()) != EMPTY { result.set_ep(dest); + result.set_ep_target(Some(dest.ubackward(self.side_to_move))); result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); } else if Some(dest.ubackward(self.side_to_move)) == self.en_passant { result.xor( @@ -1041,6 +1084,11 @@ impl Board { pub fn checkers(&self) -> &BitBoard { &self.checkers } + + pub fn get_psuedo_fen(&self) -> String { + let board_builder: BoardBuilder = self.into(); + board_builder.get_psuedo_fen() + } } impl fmt::Display for Board { diff --git a/src/board_builder.rs b/src/board_builder.rs index da2f6795..a92361ef 100644 --- a/src/board_builder.rs +++ b/src/board_builder.rs @@ -53,6 +53,7 @@ pub struct BoardBuilder { side_to_move: Color, castle_rights: [CastleRights; 2], en_passant: Option, + en_passant_target: Option, } impl BoardBuilder { @@ -81,6 +82,7 @@ impl BoardBuilder { side_to_move: Color::White, castle_rights: [CastleRights::NoRights, CastleRights::NoRights], en_passant: None, + en_passant_target: None, } } @@ -100,6 +102,7 @@ impl BoardBuilder { /// Color::Black, /// CastleRights::NoRights, /// CastleRights::NoRights, + /// None, /// None) /// .try_into()?; /// # Ok(()) @@ -110,12 +113,14 @@ impl BoardBuilder { white_castle_rights: CastleRights, black_castle_rights: CastleRights, en_passant: Option, + en_passant_target: Option, ) -> BoardBuilder { let mut result = BoardBuilder { pieces: [None; 64], side_to_move: side_to_move, castle_rights: [white_castle_rights, black_castle_rights], en_passant: en_passant, + en_passant_target: en_passant_target, }; for piece in pieces.into_iter() { @@ -259,83 +264,90 @@ impl BoardBuilder { self.en_passant = file; self } -} - -impl Index for BoardBuilder { - type Output = Option<(Piece, Color)>; - - fn index<'a>(&'a self, index: Square) -> &'a Self::Output { - &self.pieces[index.to_index()] - } -} -impl IndexMut for BoardBuilder { - fn index_mut<'a>(&'a mut self, index: Square) -> &'a mut Self::Output { - &mut self.pieces[index.to_index()] - } -} - -impl fmt::Display for BoardBuilder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// A FEN representaion with the last two fileds, the half move clock and the full move + /// counter, omitted. + pub(crate) fn get_psuedo_fen(&self) -> String { + let mut psuedo_fen = String::new(); let mut count = 0; for rank in ALL_RANKS.iter().rev() { for file in ALL_FILES.iter() { let square = Square::make_square(*rank, *file).to_index(); if self.pieces[square].is_some() && count != 0 { - write!(f, "{}", count)?; + psuedo_fen.push_str(count.to_string().as_str()); count = 0; } if let Some((piece, color)) = self.pieces[square] { - write!(f, "{}", piece.to_string(color))?; + psuedo_fen.push_str(piece.to_string(color).as_str()); } else { count += 1; } } if count != 0 { - write!(f, "{}", count)?; + psuedo_fen.push_str(count.to_string().as_str()); } if *rank != Rank::First { - write!(f, "/")?; + psuedo_fen.push_str("/"); } count = 0; } - write!(f, " ")?; + psuedo_fen.push_str(" "); if self.side_to_move == Color::White { - write!(f, "w ")?; + psuedo_fen.push_str("w "); } else { - write!(f, "b ")?; + psuedo_fen.push_str("b "); } - write!( - f, - "{}", - self.castle_rights[Color::White.to_index()].to_string(Color::White) - )?; - write!( - f, - "{}", - self.castle_rights[Color::Black.to_index()].to_string(Color::Black) - )?; + psuedo_fen.push_str( + self.castle_rights[Color::White.to_index()] + .to_string(Color::White) + .as_str(), + ); + psuedo_fen.push_str( + self.castle_rights[Color::Black.to_index()] + .to_string(Color::Black) + .as_str(), + ); if self.castle_rights[0] == CastleRights::NoRights && self.castle_rights[1] == CastleRights::NoRights { - write!(f, "-")?; + psuedo_fen.push_str("-"); } - write!(f, " ")?; - if let Some(sq) = self.get_en_passant() { - write!(f, "{}", sq)?; + psuedo_fen.push_str(" "); + if let Some(sq) = self.en_passant_target { + psuedo_fen.push_str(sq.to_string().as_str()); } else { - write!(f, "-")?; + psuedo_fen.push_str("-"); } - write!(f, " 0 1") + psuedo_fen + } +} + +impl Index for BoardBuilder { + type Output = Option<(Piece, Color)>; + + fn index<'a>(&'a self, index: Square) -> &'a Self::Output { + &self.pieces[index.to_index()] + } +} + +impl IndexMut for BoardBuilder { + fn index_mut<'a>(&'a mut self, index: Square) -> &'a mut Self::Output { + &mut self.pieces[index.to_index()] + } +} + +impl fmt::Display for BoardBuilder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} 0 1", self.get_psuedo_fen()) } } @@ -496,6 +508,7 @@ impl From<&Board> for BoardBuilder { board.castle_rights(Color::White), board.castle_rights(Color::Black), board.en_passant().map(|sq| sq.get_file()), + board.en_passant_target(), ) } } diff --git a/src/game.rs b/src/game.rs index bee7ae19..752b4fb2 100644 --- a/src/game.rs +++ b/src/game.rs @@ -4,6 +4,7 @@ use crate::color::Color; use crate::error::Error; use crate::movegen::MoveGen; use crate::piece::Piece; +use std::fmt; use std::str::FromStr; /// Contains all actions supported within the game @@ -37,6 +38,8 @@ pub enum GameResult { pub struct Game { start_pos: Board, moves: Vec, + start_half_move_clock: usize, + start_full_move_counter: usize, } impl Game { @@ -49,10 +52,7 @@ impl Game { /// assert_eq!(game.current_position(), Board::default()); /// ``` pub fn new() -> Game { - Game { - start_pos: Board::default(), - moves: vec![], - } + Game::new_with_board(Board::default()) } /// Create a new `Game` with a specific starting position. @@ -64,9 +64,19 @@ impl Game { /// assert_eq!(game.current_position(), Board::default()); /// ``` pub fn new_with_board(board: Board) -> Game { + Game::new_with_board_and_counters(board, 0, 1) + } + + fn new_with_board_and_counters( + board: Board, + start_half_move_clock: usize, + start_full_move_counter: usize, + ) -> Game { Game { start_pos: board, moves: vec![], + start_half_move_clock, + start_full_move_counter, } } @@ -170,6 +180,48 @@ impl Game { copy } + fn get_full_move_counter(&self) -> usize { + let mut half_moves = self.start_full_move_counter * 2; + for x in self.moves.iter() { + match *x { + Action::MakeMove(_) => { + half_moves += 1; + } + _ => (), + } + } + half_moves / 2 + } + + fn get_half_move_clock(&self) -> usize { + let mut reversible_moves = self.start_half_move_clock; + let mut board = self.start_pos; + for x in self.moves.iter() { + match *x { + Action::MakeMove(m) => { + let white_castle_rights = board.castle_rights(Color::White); + let black_castle_rights = board.castle_rights(Color::Black); + if board.piece_on(m.get_source()) == Some(Piece::Pawn) { + reversible_moves = 0; + } else if board.piece_on(m.get_dest()).is_some() { + reversible_moves = 0; + } else { + reversible_moves += 1; + } + board = board.make_move_new(m); + + if board.castle_rights(Color::White) != white_castle_rights + || board.castle_rights(Color::Black) != black_castle_rights + { + reversible_moves = 0; + } + } + _ => {} + } + } + reversible_moves + } + /// Determine if a player can legally declare a draw by 3-fold repetition or 50-move rule. /// /// ``` @@ -205,7 +257,6 @@ impl Game { let mut legal_moves_per_turn: Vec<(u64, Vec)> = vec![]; let mut board = self.start_pos; - let mut reversible_moves = 0; // Loop over each move, counting the reversible_moves for draw by 50 move rule, // and filling a list of legal_moves_per_turn list for 3-fold repitition @@ -216,20 +267,16 @@ impl Game { let white_castle_rights = board.castle_rights(Color::White); let black_castle_rights = board.castle_rights(Color::Black); if board.piece_on(m.get_source()) == Some(Piece::Pawn) { - reversible_moves = 0; legal_moves_per_turn.clear(); } else if board.piece_on(m.get_dest()).is_some() { - reversible_moves = 0; legal_moves_per_turn.clear(); } else { - reversible_moves += 1; } board = board.make_move_new(m); if board.castle_rights(Color::White) != white_castle_rights || board.castle_rights(Color::Black) != black_castle_rights { - reversible_moves = 0; legal_moves_per_turn.clear(); } legal_moves_per_turn @@ -239,7 +286,7 @@ impl Game { } } - if reversible_moves >= 100 { + if self.get_half_move_clock() >= 100 { return true; } @@ -423,7 +470,45 @@ impl FromStr for Game { type Err = Error; fn from_str(fen: &str) -> Result { - Ok(Game::new_with_board(Board::from_str(fen)?)) + let half_move_clock = fen + .split(" ") + .nth(4) + .ok_or_else(|| Error::InvalidFen { + fen: String::from(fen), + })? + .parse::() + .map_err(|_| Error::InvalidFen { + fen: String::from(fen), + })?; + + let full_move_counter = fen + .split(" ") + .nth(5) + .ok_or_else(|| Error::InvalidFen { + fen: String::from(fen), + })? + .parse::() + .map_err(|_| Error::InvalidFen { + fen: String::from(fen), + })?; + + Ok(Game::new_with_board_and_counters( + Board::from_str(fen)?, + half_move_clock, + full_move_counter, + )) + } +} + +impl fmt::Display for Game { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{} {} {}", + self.current_position().get_psuedo_fen(), + self.get_half_move_clock(), + self.get_full_move_counter() + ) } } @@ -438,6 +523,36 @@ pub fn fake_pgn_parser(moves: &str) -> Game { }) } +#[test] +fn test_fen_string() { + use crate::square::Square; + let mut game = Game::new(); + assert_eq!( + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + format!("{}", game) + ); + game.make_move(ChessMove::new(Square::E2, Square::E4, None)); + assert_eq!( + "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", + format!("{}", game) + ); + game.make_move(ChessMove::new(Square::C7, Square::C5, None)); + assert_eq!( + "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2", + format!("{}", game) + ); + game.make_move(ChessMove::new(Square::G1, Square::F3, None)); + let final_serialized_game = format!("{}", game); + assert_eq!( + "rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2", + final_serialized_game + ); + assert_eq!( + final_serialized_game, + format!("{}", Game::from_str(&final_serialized_game).unwrap()), + ); +} + #[test] pub fn test_can_declare_draw() { let game = fake_pgn_parser(