From e4d2b20e234152eb6baec40c58811c591a9722f9 Mon Sep 17 00:00:00 2001 From: dece Date: Fri, 19 Jun 2020 17:36:26 +0200 Subject: [PATCH] notation: rename module to fen Move UCI movement stuff to the Move struct impl. --- src/board.rs | 4 +- src/fen.rs | 55 ++++++++ src/main.rs | 2 +- src/movement.rs | 341 ++++++++++++++++++++++++++++-------------------- src/notation.rs | 111 ---------------- 5 files changed, 256 insertions(+), 257 deletions(-) create mode 100644 src/fen.rs delete mode 100644 src/notation.rs diff --git a/src/board.rs b/src/board.rs index 4b1902c..b4ed44e 100644 --- a/src/board.rs +++ b/src/board.rs @@ -253,7 +253,7 @@ impl Board { } /// Debug only: count number of pieces on board. - pub fn num_pieces(&self) -> u8 { + pub(crate) fn num_pieces(&self) -> u8 { let cbb = self.combined(); let mut count = 0; while cbb > 0 { @@ -264,7 +264,7 @@ impl Board { } /// Debug only: write a text view of the board. - pub fn draw(&self, f: &mut dyn std::io::Write) { + pub(crate) fn draw(&self, f: &mut dyn std::io::Write) { let cbb = self.colors[WHITE] | self.colors[BLACK]; for rank in (0..8).rev() { let mut rank_str = String::with_capacity(8); diff --git a/src/fen.rs b/src/fen.rs new file mode 100644 index 0000000..9c11bf9 --- /dev/null +++ b/src/fen.rs @@ -0,0 +1,55 @@ +//! Functions to parse FEN strings. + +use crate::board; + +pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + +/// FEN notation for positions, split into fields. +#[derive(Debug, Clone)] +pub struct Fen { + pub placement: String, + pub color: String, + pub castling: String, + pub en_passant: String, + pub halfmove: String, + pub fullmove: String, +} + +pub fn parse_fen(i: &str) -> Option { + let fields: Vec<&str> = i.split_whitespace().collect(); + parse_fen_fields(&fields) +} + +pub fn parse_fen_fields(fields: &[&str]) -> Option { + if fields.len() < 6 { + return None + } + Some(Fen { + placement: fields[0].to_string(), + color: fields[1].to_string(), + castling: fields[2].to_string(), + en_passant: fields[3].to_string(), + halfmove: fields[4].to_string(), + fullmove: fields[5].to_string(), + }) +} + +pub fn en_passant_to_string(ep: Option) -> String { + ep.and_then(|p| Some(board::sq_to_string(p))).unwrap_or("-".to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_fen() { + let fen_start = parse_fen(FEN_START).unwrap(); + assert_eq!(&fen_start.placement, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"); + assert_eq!(&fen_start.color, "w"); + assert_eq!(&fen_start.castling, "KQkq"); + assert_eq!(&fen_start.en_passant, "-"); + assert_eq!(&fen_start.halfmove, "0"); + assert_eq!(&fen_start.fullmove, "1"); + } +} diff --git a/src/main.rs b/src/main.rs index 0cba92e..9ea603e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,9 @@ pub mod analysis; pub mod board; pub mod castling; pub mod engine; +pub mod fen; pub mod movement; pub mod node; -pub mod notation; pub mod rules; pub mod stats; pub mod uci; diff --git a/src/movement.rs b/src/movement.rs index 808ad6d..f586b43 100644 --- a/src/movement.rs +++ b/src/movement.rs @@ -1,174 +1,214 @@ //! Move functions along with some castling helpers. +use std::fmt; + use crate::board::*; use crate::castling::*; -use crate::rules; +use crate::rules::GameState; const START_WH_K_POS: Square = E1; const START_BL_K_POS: Square = E8; /// A movement, with before/after positions and optional promotion. -pub type Move = (Square, Square, Option); - -/// Apply a move `m` to copies to `board` and `game_state`. -/// -/// Can be used for conveniance but it's better to write in existing -/// instances as often as possible using `apply_move_to`. -pub fn apply_move( - board: &Board, - game_state: &rules::GameState, - m: &Move -) -> (Board, rules::GameState) { - let mut new_board = board.clone(); - let mut new_state = game_state.clone(); - apply_move_to(&mut new_board, &mut new_state, m); - (new_board, new_state) +#[derive(PartialEq)] +pub struct Move { + pub source: Square, + pub dest: Square, + pub promotion: Option, } -/// Update `board` and `game_state` to reflect the move `m`. -/// -/// The board is updated with correct piece placement. -/// -/// The game state is updated with the new player turn and the new -/// castling options. -pub fn apply_move_to( - board: &mut Board, - game_state: &mut rules::GameState, - m: &Move -) { - let (source, dest) = (m.0, m.1); +impl fmt::Debug for Move { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_uci_string()) + } +} - // If a rook is taken, remove its castling option. Needs to be checked before we update board. - // Note that we only check for a piece going to rook's initial position: it means the rook - // either moved previously, or it has been taken. - match source { - A1 => { game_state.castling &= !CASTLING_WH_Q; } - H1 => { game_state.castling &= !CASTLING_WH_K; } - A8 => { game_state.castling &= !CASTLING_BL_Q; } - H8 => { game_state.castling &= !CASTLING_BL_K; } +pub const SAN_NULL_MOVE: &str = "0000"; + +impl Move { + /// Build a move from `source` to `dest`, no promotion. + pub const fn new(source: Square, dest: Square) -> Move { + Move { source, dest, promotion: None } } - // Update board and game state. - apply_move_to_board(board, m); - game_state.color = opposite(game_state.color); - - // If the move is a castle, remove it from castling options. - if let Some(castle) = get_castle(m) { - match castle { - CASTLING_WH_K | CASTLING_WH_Q => game_state.castling &= !CASTLING_WH_MASK, - CASTLING_BL_K | CASTLING_BL_Q => game_state.castling &= !CASTLING_BL_MASK, - _ => {} - }; + /// Build a move from `source` to `dest`, with a promotion. + pub const fn new_promotion(source: Square, dest: Square, promotion: Piece) -> Move { + Move { source, dest, promotion: Some(promotion) } } - // Else, check if the king or a rook moved to update castling options. - else { - let color = board.get_color(dest); - if color == WHITE && game_state.castling & CASTLING_WH_MASK != 0 { - match board.get_piece(dest) { - KING => { - if source == E1 { - game_state.castling &= !CASTLING_WH_MASK; + + /// Apply this move to `board` and `game_state`. + pub fn apply_to(&self, board: &mut Board, game_state: &mut GameState) { + // If a rook is taken, remove its castling option. Needs to be checked before we update + // board. Note that we only check for a piece going to rook's initial position: it means + // the rook either moved previously, or it has been taken. + match self.source { + A1 => { game_state.castling &= !CASTLING_WH_Q; } + H1 => { game_state.castling &= !CASTLING_WH_K; } + A8 => { game_state.castling &= !CASTLING_BL_Q; } + H8 => { game_state.castling &= !CASTLING_BL_K; } + } + + // Update board and game state. + self.apply_to_board(board); + game_state.color = opposite(game_state.color); + + // If the move is a castle, remove it from castling options. + if let Some(castle) = self.get_castle() { + match castle { + CASTLING_WH_K | CASTLING_WH_Q => game_state.castling &= !CASTLING_WH_MASK, + CASTLING_BL_K | CASTLING_BL_Q => game_state.castling &= !CASTLING_BL_MASK, + _ => {} + }; + } + // Else, check if the king or a rook moved to update castling options. + else { + let color = board.get_color(self.dest); + if color == WHITE && game_state.castling & CASTLING_WH_MASK != 0 { + match board.get_piece(self.dest) { + KING => { + if self.source == E1 { + game_state.castling &= !CASTLING_WH_MASK; + } } + ROOK => { + if self.source == A1 { + game_state.castling &= !CASTLING_WH_Q; + } else if self.source == H1 { + game_state.castling &= !CASTLING_WH_K; + } + } + _ => {} } - ROOK => { - if source == A1 { - game_state.castling &= !CASTLING_WH_Q; - } else if source == H1 { - game_state.castling &= !CASTLING_WH_K; + } else if color == BLACK && game_state.castling & CASTLING_BL_MASK != 0 { + match board.get_piece(self.dest) { + KING => { + if self.source == E8 { + game_state.castling &= !CASTLING_BL_MASK; + } } + ROOK => { + if self.source == A8 { + game_state.castling &= !CASTLING_BL_Q; + } else if self.source == H8 { + game_state.castling &= !CASTLING_BL_K; + } + } + _ => {} + } + } + } + } + + /// Apply the move into `board`. + pub fn apply_to_board(&self, board: &mut Board) { + if let Some(castle) = self.get_castle() { + match castle { + CASTLING_WH_K => { + board.move_square(START_WH_K_POS, G1); + board.move_square(H1, F1); + } + CASTLING_WH_Q => { + board.move_square(START_WH_K_POS, C1); + board.move_square(A1, D1); + } + CASTLING_BL_K => { + board.move_square(START_BL_K_POS, G8); + board.move_square(H8, F8); + } + CASTLING_BL_Q => { + board.move_square(START_BL_K_POS, C8); + board.move_square(A8, D8); } _ => {} } - } else if color == BLACK && game_state.castling & CASTLING_BL_MASK != 0 { - match board.get_piece(dest) { - KING => { - if source == E8 { - game_state.castling &= !CASTLING_BL_MASK; - } - } - ROOK => { - if source == A8 { - game_state.castling &= !CASTLING_BL_Q; - } else if source == H8 { - game_state.castling &= !CASTLING_BL_K; - } - } - _ => {} + } else { + board.move_square(self.source, self.dest); + if let Some(piece) = self.promotion { + let color = board.get_color(self.dest); + board.set_square(self.dest, color, piece); } } } -} -/// Apply a move `m` into `board`. -pub fn apply_move_to_board(board: &mut Board, m: &Move) { - if let Some(castle) = get_castle(m) { + /// Get the corresponding castling flag for this move. + pub fn get_castle(&self) -> Option { + if self.source == E1 { + if self.dest == C1 { + Some(CASTLING_WH_Q) + } else if self.dest == G1 { + Some(CASTLING_WH_K) + } else { + None + } + } else if self.source == E8 { + if self.dest == C8 { + Some(CASTLING_BL_Q) + } else if self.dest == G8 { + Some(CASTLING_BL_K) + } else { + None + } + } else { + None + } + } + + /// Get the move for this castle. + pub fn get_castle_move(castle: u8) -> Move { match castle { - CASTLING_WH_K => { - board.move_square(START_WH_K_POS, G1); - board.move_square(H1, F1); - } - CASTLING_WH_Q => { - board.move_square(START_WH_K_POS, C1); - board.move_square(A1, D1); - } - CASTLING_BL_K => { - board.move_square(START_BL_K_POS, G8); - board.move_square(H8, F8); - } - CASTLING_BL_Q => { - board.move_square(START_BL_K_POS, C8); - board.move_square(A8, D8); - } - _ => {} - } - } else { - board.move_square(m.0, m.1); - if let Some(prom_type) = m.2 { - let color = board.get_color(m.1); - board.set_square(m.1, color, prom_type); + CASTLING_WH_Q => Move::new(E1, C1), + CASTLING_WH_K => Move::new(E1, G1), + CASTLING_BL_Q => Move::new(E8, C8), + CASTLING_BL_K => Move::new(E8, G8), + _ => panic!("Illegal castling requested: {:08b}", castle), } } -} -/// Get the corresponding castling flag for this move. -pub fn get_castle(m: &Move) -> Option { - let (source, dest) = (m.0, m.1); - if source == E1 { - if dest == C1 { - Some(CASTLING_WH_Q) - } else if dest == G1 { - Some(CASTLING_WH_K) - } else { - None + /// Parse an UCI move algebraic notation string to a Move. + pub fn from_uci_string(m_str: &str) -> Move { + Move { + source: sq_from_string(&m_str[0..2]), + dest: sq_from_string(&m_str[2..4]), + promotion: if m_str.len() == 5 { + Some(match m_str.as_bytes()[4] { + b'b' => BISHOP, + b'n' => KNIGHT, + b'r' => ROOK, + b'q' => QUEEN, + _ => panic!("What is the opponent doing? This is illegal, I'm out."), + }) + } else { + None + } } - } else if source == E8 { - if dest == C8 { - Some(CASTLING_BL_Q) - } else if dest == G8 { - Some(CASTLING_BL_K) - } else { - None - } - } else { - None } -} -/// Get the move for this castle. -pub fn get_castle_move(castle: u8) -> Move { - match castle { - CASTLING_WH_Q => (E1, C1, None), - CASTLING_WH_K => (E1, G1, None), - CASTLING_BL_Q => (E8, C8, None), - CASTLING_BL_K => (E8, G8, None), - _ => panic!("Illegal castling requested: {:08b}", castle), + /// Create a string containing the UCI algebraic notation of this move. + pub fn to_uci_string(&self) -> String { + let mut move_string = String::new(); + move_string.push_str(&sq_to_string(self.source)); + move_string.push_str(&sq_to_string(self.dest)); + if let Some(piece) = self.promotion { + move_string.push(match piece { + QUEEN => 'q', + BISHOP => 'b', + KNIGHT => 'n', + ROOK => 'r', + _ => panic!("What are you doing? Promote to a legal piece.") + }); + } + move_string + } + + /// Debug only: create a space-separated string of moves. + pub(crate) fn list_to_uci_string(moves: &Vec) -> String { + moves.iter().map(|m| m.to_uci_string()).collect::>().join(" ") } } #[cfg(test)] mod tests { use super::*; - use crate::notation::parse_move; #[test] fn test_apply_move_to_board() { @@ -178,13 +218,13 @@ mod tests { b.set_square(D4, WHITE, KNIGHT); b.set_square(F4, BLACK, KNIGHT); // Move white knight in a position attacked by black knight. - apply_move_to_board(&mut b, &(D4, E6, None)); + Move::new(D4, E6).apply_to_board(&mut b); assert!(b.is_empty(D4)); assert_eq!(b.get_color(E6), WHITE); assert_eq!(b.get_piece(E6), KNIGHT); assert_eq!(b.num_pieces(), 2); // Sack it with black knight - apply_move_to_board(&mut b, &(F4, E6, None)); + Move::new(F4, E6).apply_to_board(&mut b); assert_eq!(b.get_color(E6), BLACK); assert_eq!(b.get_piece(E6), KNIGHT); assert_eq!(b.num_pieces(), 1); @@ -193,7 +233,7 @@ mod tests { #[test] fn test_apply_move_to_castling() { let mut b = Board::new(); - let mut gs = rules::GameState::new(); + let mut gs = GameState::new(); assert_eq!(gs.castling, CASTLING_MASK); // On a starting board, start by making place for all castles. @@ -208,7 +248,7 @@ mod tests { b.clear_square(F8); b.clear_square(G8); // White queen-side castling. - apply_move_to(&mut b, &mut gs, &parse_move("e1c1")); + Move::new(E1, C1).apply_to(&mut b, &mut gs); assert_eq!(b.get_color(C1), WHITE); assert_eq!(b.get_piece(C1), KING); assert_eq!(b.get_color(D1), WHITE); @@ -217,7 +257,7 @@ mod tests { assert!(b.is_empty(E1)); assert_eq!(gs.castling, CASTLING_BL_MASK); // Black king-side castling. - apply_move_to(&mut b, &mut gs, &parse_move("e8g8")); + Move::new(E8, G8).apply_to(&mut b, &mut gs); assert_eq!(b.get_color(G1), BLACK); assert_eq!(b.get_piece(G1), KING); assert_eq!(b.get_color(F1), BLACK); @@ -230,10 +270,25 @@ mod tests { #[test] fn test_get_castle() { - assert_eq!(get_castle(&parse_move("e1c1")), Some(CASTLING_WH_Q)); - assert_eq!(get_castle(&parse_move("e1g1")), Some(CASTLING_WH_K)); - assert_eq!(get_castle(&parse_move("e8c8")), Some(CASTLING_BL_Q)); - assert_eq!(get_castle(&parse_move("e8g8")), Some(CASTLING_BL_K)); - assert_eq!(get_castle(&parse_move("d2d4")), None); + assert_eq!(Move::new(E1, C1).get_castle(), Some(CASTLING_WH_Q)); + assert_eq!(Move::new(E1, G1).get_castle(), Some(CASTLING_WH_K)); + assert_eq!(Move::new(E8, C8).get_castle(), Some(CASTLING_BL_Q)); + assert_eq!(Move::new(E8, G8).get_castle(), Some(CASTLING_BL_K)); + assert_eq!(Move::new(D2, D4).get_castle(), None); + } + + #[test] + fn test_to_uci_string() { + assert_eq!(Move::new(A1, D4).to_uci_string(), "a1d4"); + assert_eq!(Move::new(H8, A8).to_uci_string(), "h8a8"); + assert_eq!(Move::new_promotion(H7, H8, QUEEN).to_uci_string(), "h7h8q"); + assert_eq!(Move::new_promotion(H7, H8, KNIGHT).to_uci_string(), "h7h8n"); + } + + #[test] + fn test_from_uci_string() { + assert_eq!(Move::from_uci_string("a1d4"), Move::new(A1, D4)); + assert_eq!(Move::from_uci_string("a7a8q"), Move::new_promotion(A7, A8, QUEEN)); + assert_eq!(Move::from_uci_string("a7a8r"), Move::new_promotion(A7, A8, ROOK)); } } diff --git a/src/notation.rs b/src/notation.rs deleted file mode 100644 index 317eabe..0000000 --- a/src/notation.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Functions using various notations. - -use crate::board::*; -use crate::movement::Move; - -pub const NULL_MOVE: &str = "0000"; - -/// Create a string containing the UCI algebraic notation of this move. -pub fn move_to_string(m: &Move) -> String { - let mut move_string = String::new(); - move_string.push_str(&pos_string(&m.0)); - move_string.push_str(&pos_string(&m.1)); - if let Some(prom) = m.2 { - move_string.push(match prom { - SQ_Q => 'q', - SQ_B => 'b', - SQ_N => 'n', - SQ_R => 'r', - _ => panic!("What are you doing? Promote to a legal piece.") - }); - } - move_string -} - -/// Parse an UCI move algebraic notation string to a Move. -pub fn parse_move(m_str: &str) -> Move { - let prom = if m_str.len() == 5 { - Some(match m_str.as_bytes()[4] { - b'b' => SQ_B, - b'n' => SQ_N, - b'r' => SQ_R, - b'q' => SQ_Q, - _ => panic!("What is the opponent doing? This is illegal, I'm out."), - }) - } else { - None - }; - (pos(&m_str[0..2]), pos(&m_str[2..4]), prom) -} - -/// Create a space-separated string of moves. Used for debugging. -pub fn move_list_to_string(moves: &Vec) -> String { - moves.iter().map(|m| move_to_string(m)).collect::>().join(" ") -} - -pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - -/// FEN notation for positions, split into fields. -#[derive(Debug, Clone)] -pub struct Fen { - pub placement: String, - pub color: String, - pub castling: String, - pub en_passant: String, - pub halfmove: String, - pub fullmove: String, -} - -pub fn parse_fen(i: &str) -> Option { - let fields: Vec<&str> = i.split_whitespace().collect(); - parse_fen_fields(&fields) -} - -pub fn parse_fen_fields(fields: &[&str]) -> Option { - if fields.len() < 6 { - return None - } - Some(Fen { - placement: fields[0].to_string(), - color: fields[1].to_string(), - castling: fields[2].to_string(), - en_passant: fields[3].to_string(), - halfmove: fields[4].to_string(), - fullmove: fields[5].to_string(), - }) -} - -pub fn en_passant_to_string(ep: Option) -> String { - ep.and_then(|p| Some(pos_string(&p))).unwrap_or("-".to_string()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_move_to_string() { - assert_eq!(move_to_string(&((0, 0), (3, 3), None)), "a1d4"); - assert_eq!(move_to_string(&((7, 7), (0, 7), None)), "h8a8"); - assert_eq!(move_to_string(&((7, 6), (7, 7), Some(SQ_Q))), "h7h8q"); - assert_eq!(move_to_string(&((7, 6), (7, 7), Some(SQ_N))), "h7h8n"); - } - - #[test] - fn test_parse_move() { - assert_eq!(parse_move("a1d4"), ((0, 0), (3, 3), None)); - assert_eq!(parse_move("a7a8q"), ((0, 6), (0, 7), Some(SQ_Q))); - assert_eq!(parse_move("a7a8r"), ((0, 6), (0, 7), Some(SQ_R))); - } - - #[test] - fn test_parse_fen() { - let fen_start = parse_fen(FEN_START).unwrap(); - assert_eq!(&fen_start.placement, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"); - assert_eq!(&fen_start.color, "w"); - assert_eq!(&fen_start.castling, "KQkq"); - assert_eq!(&fen_start.en_passant, "-"); - assert_eq!(&fen_start.halfmove, "0"); - assert_eq!(&fen_start.fullmove, "1"); - } -}