diff --git a/src/board.rs b/src/board.rs index c08361a..d3dc12d 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,6 +1,7 @@ use std::fmt; use u64 as Bitboard; +#[derive(Debug, PartialEq)] pub struct Board { white_pieces: [Piece; 6], black_pieces: [Piece; 6], @@ -22,11 +23,45 @@ impl Board { Piece::new(0x8100000000000000, Kind::Rook, Color::Black), Piece::new(0x4200000000000000, Kind::Knight, Color::Black), Piece::new(0x2400000000000000, Kind::Bishop, Color::Black), - Piece::new(0x1000000000000000, Kind::Queen, Color::Black), - Piece::new(0x800000000000000, Kind::King, Color::Black), + Piece::new(0x800000000000000, Kind::Queen, Color::Black), + Piece::new(0x1000000000000000, Kind::King, Color::Black), ], } } + + pub fn empty_board() -> Board { + Board { + white_pieces: [ + Piece::new(0x0, Kind::Pawn, Color::White), + Piece::new(0x0, Kind::Rook, Color::White), + Piece::new(0x0, Kind::Knight, Color::White), + Piece::new(0x0, Kind::Bishop, Color::White), + Piece::new(0x0, Kind::Queen, Color::White), + Piece::new(0x0, Kind::King, Color::White), + ], + black_pieces: [ + Piece::new(0x0, Kind::Pawn, Color::Black), + Piece::new(0x0, Kind::Rook, Color::Black), + Piece::new(0x0, Kind::Knight, Color::Black), + Piece::new(0x0, Kind::Bishop, Color::Black), + Piece::new(0x0, Kind::Queen, Color::Black), + Piece::new(0x0, Kind::King, Color::Black), + ], + } + } + + pub fn set_piece(&mut self, piece: Piece) { + match piece.color { + Color::Black => self.black_pieces[piece.kind as usize].bitboard |= piece.bitboard, + Color::White => self.white_pieces[piece.kind as usize].bitboard |= piece.bitboard, + }; + } +} + +impl Default for Board { + fn default() -> Self { + Self::new() + } } impl fmt::Display for Board { @@ -38,7 +73,7 @@ impl fmt::Display for Board { piece.color, piece.kind )?; } - writeln!(f, "")?; + writeln!(f)?; for piece in &self.black_pieces { writeln!( f, @@ -46,20 +81,21 @@ impl fmt::Display for Board { piece.color, piece.kind )?; } - write!(f, "") + writeln!(f) } } +#[derive(Debug, PartialEq)] pub struct Piece { - pub position: Bitboard, + pub bitboard: Bitboard, pub kind: Kind, pub color: Color, } impl Piece { - pub fn new(position: Bitboard, kind: Kind, color: Color) -> Piece { + pub fn new(bitboard: Bitboard, kind: Kind, color: Color) -> Piece { Piece { - position, + bitboard, kind, color, } @@ -71,16 +107,16 @@ impl fmt::Display for Piece { for rank in (0..8).rev() { for file in 0..8 { let square = 1 << (rank * 8 + file); - let position = if square & self.position != 0x0 { 1 } else { 0 }; + let position = if square & self.bitboard != 0x0 { 1 } else { 0 }; write!(f, "{} ", position)?; } - writeln!(f, "")?; + writeln!(f)?; } - write!(f, "") + writeln!(f) } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Kind { Pawn, Rook, @@ -90,7 +126,7 @@ pub enum Kind { King, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Color { White, Black, diff --git a/src/fen.rs b/src/fen.rs new file mode 100644 index 0000000..b2d1237 --- /dev/null +++ b/src/fen.rs @@ -0,0 +1,171 @@ +use crate::board::{Board, Color, Kind, Piece}; +use crate::game::{Game, State}; +use String as FenError; + +pub fn from_fen(fen: &str) -> Game { + let mut board = Board::empty_board(); + let fen_parts: Vec<_> = fen.split_whitespace().collect(); + + piece_placement(fen_parts[0], &mut board); + + let side_to_move = side_to_move(fen_parts[1]); + + let castling_ability = castling_ability(fen_parts[2]); + + let en_passant_target_square: Result = en_passant_target_square(fen_parts[3]); + + let halfmove_clock = halfmove_clock(fen_parts[4]); + + let fullmove_counter = fullmove_counter(fen_parts[5]); + + Game { + board, + state: State::load_state( + side_to_move.unwrap(), + castling_ability.unwrap(), + en_passant_target_square.unwrap(), + halfmove_clock.unwrap(), + fullmove_counter.unwrap(), + ), + } +} + +fn piece_placement(pieces: &str, board: &mut Board) { + let (mut file, mut rank): (u8, u8) = (0, 7); + + for c in pieces.chars() { + let square: u8 = rank * 8 + file; + let color = if c.is_ascii_lowercase() { + Color::Black + } else { + Color::White + }; + + if let Some(kind) = match c.to_ascii_lowercase() { + 'r' => Some(Kind::Rook), + 'n' => Some(Kind::Knight), + 'b' => Some(Kind::Bishop), + 'q' => Some(Kind::Queen), + 'k' => Some(Kind::King), + 'p' => Some(Kind::Pawn), + '/' => { + file = 0; + rank = rank.saturating_sub(1); + continue; + } + n if n.is_numeric() => { + rank %= n.to_digit(10).unwrap_or(0) as u8; + None + } + _ => None, + } { + board.set_piece(Piece::new(1 << square, kind, color)); + }; + + file += 1; + } +} + +fn side_to_move(side: &str) -> Result { + match side { + "w" => Ok(Color::White), + "b" => Ok(Color::Black), + _ => Err(FenError::from( + "Found invalid character while parsing in side_to_move", + )), + } +} + +/// Return castling ability as a bitflag. LSb K, 2nd LSb Q, 3rd LSb k, 4th LSb q. Ignore 4 MSb's +fn castling_ability(castling: &str) -> Result { + let mut rights = 0b0; + + for c in castling.chars() { + match c { + 'K' => rights |= 0b1, + 'Q' => rights |= 0b10, + 'k' => rights |= 0b100, + 'q' => rights |= 0b1000, + '-' => rights |= 0b0, + _ => { + return Err(FenError::from( + "Found invalid character while parsing in castling_ability", + )) + } + }; + } + Ok(rights) +} + +use std::collections::HashMap; + +fn en_passant_target_square(square: &str) -> Result { + let mut sqr = square.chars(); + + let mut files: HashMap = HashMap::new(); + files.insert('a', 0); + files.insert('b', 1); + files.insert('c', 2); + files.insert('d', 3); + files.insert('e', 4); + files.insert('f', 5); + files.insert('g', 6); + files.insert('h', 7); + + let mut ranks: HashMap = HashMap::new(); + ranks.insert('3', 2); + ranks.insert('6', 5); + + match sqr.next() { + Some(file) if files.contains_key(&file) => match sqr.next() { + Some(rank) if ranks.contains_key(&rank) => { + Ok(ranks.get(&rank).unwrap() * 8 + files.get(&file).unwrap()) + } + Some(_) | None => Err(FenError::from( + "Not a valid rank (3 or 6) for an en passant target square", + )), + }, + Some('-') => Ok(0), + Some(_) | None => Err(FenError::from("Not a file (a..h) or dash (-) character")), + } +} + +fn halfmove_clock(halfmove: &str) -> Result { + halfmove + .parse::() + .map_err(|_| FenError::from("Invalid halfmove_clock value")) +} + +fn fullmove_counter(fullmove: &str) -> Result { + if "0".eq(fullmove) { + Err(FenError::from( + "Full move counter is not allowed to start with 0", + )) + } else { + fullmove + .parse::() + .map_err(|_| FenError::from("Invalid fullmove_counter value")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const FEN_EXAMPLE: [&str; 4] = [ + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", + "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2", + "rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2 ", + ]; + + #[test] + fn test_sqrt() -> Result<(), String> { + let game = from_fen(FEN_EXAMPLE[3]); + println!("{}\n{}", game.board, game.state); + + assert_eq!(from_fen(FEN_EXAMPLE[0]), Game::new()); + + Ok(()) + } +} diff --git a/src/game.rs b/src/game.rs index f308607..a2e1f32 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,14 +1,86 @@ -use crate::board::Board; +use crate::{ + board::{Board, Color}, + fen::from_fen, +}; -pub struct Game {} +#[derive(Debug, PartialEq)] +pub struct Game { + pub board: Board, + pub state: State, +} impl Game { pub fn new() -> Game { - Game {} + Game { + board: Board::new(), + state: State::new(), + } + } + + pub fn new_from_fen(fen: &str) -> Game { + from_fen(fen) } pub fn run(&self) { - let board = Board::new(); - println!("{}", board) + Board::new(); + } +} + +impl Default for Game { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, PartialEq)] +pub struct State { + side_to_move: Color, + castling_ability: u8, + en_passant_target_square: u8, + halfmove_clock: u8, + fullmove_counter: u8, +} + +impl State { + pub fn new() -> State { + State { + side_to_move: Color::White, + castling_ability: 0b1111, + en_passant_target_square: 0, + halfmove_clock: 0, + fullmove_counter: 1, + } + } + + pub fn load_state( + side_to_move: Color, + castling_ability: u8, + en_passant_target_square: u8, + halfmove_clock: u8, + fullmove_counter: u8, + ) -> State { + State { + side_to_move, + castling_ability, + en_passant_target_square, + halfmove_clock, + fullmove_counter, + } + } +} + +use std::fmt; + +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, + "side_to_move: {:?}\ncastling_ability: {:b}\nen_passant_target_square: {:b}\nhalfmove_clock: {}\nfullmove_counter: {}\n", + self.side_to_move, self.castling_ability, 1u64 << self.en_passant_target_square, self.halfmove_clock, self.fullmove_counter) + } +} + +impl Default for State { + fn default() -> Self { + Self::new() } } diff --git a/src/main.rs b/src/main.rs index c118b6f..ba43516 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ pub mod board; +pub mod fen; pub mod game; +pub mod r#move; use game::Game;