diff --git a/src/board.rs b/src/board.rs index 57e7504..1811844 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,144 +1,161 @@ -// Bitpacked board: -// One piece can be represented with 4 bits. Therefore, we can fit two pieces in one byte: -// 0b0110_1110 - -// | \ -// Black king \- White king -// As there are 64 spaces on the board, we can pack the board into a 256-bit integer, and we use that as the index for transposition tables and such. -// Boards are stored as two 128-bit bitfields, to make accessing them as efficient as possible. +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::num::ParseIntError; +use crate::boardfield::{Boardfield, BoardfieldOps}; +use crate::piece::{PieceColor, PieceOnBoard, PieceType}; +use crate::utils::{algebraic_to_boardloc, AlgebraicNotationError, boardloc_to_algebraic}; -/* +#[derive(Debug)] +pub struct Board { + bitfield: Boardfield, + // While this is a less efficient memory layout, having an array of where all the pieces are will make move generation significantly faster. + piecelist: Vec, - a b c d e f g h + turn: PieceColor, -8 56 57 58 59 60 61 62 63 8 -+ -7 48 49 50 51 52 53 54 55 7 |- bf2 -6 40 41 42 43 44 45 46 47 6 | -5 32 33 34 35 36 37 38 39 5 -+ -4 24 25 26 27 28 29 30 31 4 -+ -3 16 17 18 19 20 21 22 23 3 | -2 8 9 10 11 12 13 14 15 2 |- bf1 -1 0 1 2 3 4 5 6 7 1 -+ + castle_white_kingside: bool, + castle_white_queenside: bool, + castle_black_kingside: bool, + castle_black_queenside: bool, - a b c d e f g h - */ + en_passant_target: Option, -use crate::piece::{PieceColor, PieceType}; - -type Boardfield = [u8; 32]; -pub trait BoardfieldOps { - fn new() -> Self where Self: Sized; - fn startpos() -> Self where Self: Sized; - fn get_pos(&self, boardloc: usize) -> u8; - fn set_pos(&mut self, boardloc: usize, piece: u8); + move_counter: usize, + halfmove_counter: usize } -impl BoardfieldOps for Boardfield { - fn new() -> Self where Self: Sized { - [0u8; 32] + +#[derive(Debug)] +pub enum FENParseError { + InvalidNumberOfComponents { got: usize }, + InvalidPlayerToMove { got: String }, + EnPassantTargetParseError { e: AlgebraicNotationError }, + InvalidMoveCounter { e: ParseIntError }, + InvalidPieceCharacter { got: char }, + CannotSkipToOutsideOfBoard { got: usize, which_is: usize }, +} +impl Error for FENParseError {} +impl Display for FENParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidNumberOfComponents { got } => write!(f, "Invalid number of components: got {}, expected 6", got), + Self::InvalidPlayerToMove { got } => write!(f, "Invalid player to move: expected one of `wb`, got `{}`", got), + Self::EnPassantTargetParseError { e } => write!(f, "Error parsing en passant target: {}", e), + Self::InvalidMoveCounter { e } => write!(f, "Invalid move counter: {}", e), + Self::InvalidPieceCharacter { got } => write!(f, "Invalid piece character: expected one of `rnbqkpRNBQKP`, got `{}`", got), + Self::CannotSkipToOutsideOfBoard { got, which_is } => write!(f, "Cannot skip files to outside the board (tried to skip {} positions, which would be at position {})", got, which_is) + } } +} - fn startpos() -> Self where Self: Sized { - let mut field = Self::new(); - - // a1-h1 (RNBQKBNR) - field.set_pos(0, PieceColor::White as u8 | PieceType::Rook as u8); - field.set_pos(1, PieceColor::White as u8 | PieceType::Knight as u8); - field.set_pos(2, PieceColor::White as u8 | PieceType::Bishop as u8); - field.set_pos(3, PieceColor::White as u8 | PieceType::Queen as u8); - field.set_pos(4, PieceColor::White as u8 | PieceType::King as u8); - field.set_pos(5, PieceColor::White as u8 | PieceType::Bishop as u8); - field.set_pos(6, PieceColor::White as u8 | PieceType::Knight as u8); - field.set_pos(7, PieceColor::White as u8 | PieceType::Rook as u8); - - // a2-h2 (PPPPPPPP) - field.set_pos(8, PieceColor::White as u8 | PieceType::Pawn as u8); - field.set_pos(9, PieceColor::White as u8 | PieceType::Pawn as u8); - field.set_pos(10, PieceColor::White as u8 | PieceType::Pawn as u8); - field.set_pos(11, PieceColor::White as u8 | PieceType::Pawn as u8); - field.set_pos(12, PieceColor::White as u8 | PieceType::Pawn as u8); - field.set_pos(13, PieceColor::White as u8 | PieceType::Pawn as u8); - field.set_pos(14, PieceColor::White as u8 | PieceType::Pawn as u8); - field.set_pos(15, PieceColor::White as u8 | PieceType::Pawn as u8); - - // a7-h7 (pppppppp) - field.set_pos(48, PieceColor::Black as u8 | PieceType::Pawn as u8); - field.set_pos(49, PieceColor::Black as u8 | PieceType::Pawn as u8); - field.set_pos(50, PieceColor::Black as u8 | PieceType::Pawn as u8); - field.set_pos(51, PieceColor::Black as u8 | PieceType::Pawn as u8); - field.set_pos(52, PieceColor::Black as u8 | PieceType::Pawn as u8); - field.set_pos(53, PieceColor::Black as u8 | PieceType::Pawn as u8); - field.set_pos(54, PieceColor::Black as u8 | PieceType::Pawn as u8); - field.set_pos(55, PieceColor::Black as u8 | PieceType::Pawn as u8); - - // a8-h8 (RNBQKBNR) - field.set_pos(56, PieceColor::Black as u8 | PieceType::Rook as u8); - field.set_pos(57, PieceColor::Black as u8 | PieceType::Knight as u8); - field.set_pos(58, PieceColor::Black as u8 | PieceType::Bishop as u8); - field.set_pos(59, PieceColor::Black as u8 | PieceType::Queen as u8); - field.set_pos(60, PieceColor::Black as u8 | PieceType::King as u8); - field.set_pos(61, PieceColor::Black as u8 | PieceType::Bishop as u8); - field.set_pos(62, PieceColor::Black as u8 | PieceType::Knight as u8); - field.set_pos(63, PieceColor::Black as u8 | PieceType::Rook as u8); - - - field - } - - fn get_pos(&self, boardloc: usize) -> u8 { - // bf1: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 - // bf2: 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 - - if boardloc > 63 { - panic!("boardloc out of range"); +impl Board { + pub fn from_fen(fen: &str) -> Result { + let components = fen.split(' ').collect::>(); + if components.len() != 6 { + return Err(FENParseError::InvalidNumberOfComponents { got: components.len() }) } - let field = self[boardloc / 2]; - let shift = 4 * (boardloc % 2); + // turn to move + let turn = match components[1] { + "w" => PieceColor::White, + "b" => PieceColor::Black, + _ => return Err(FENParseError::InvalidPlayerToMove { got: components[0].to_string() }) + }; - (field & (0b1111 << shift)) >> shift + // castling rights + let castle_white_kingside = components[2].contains("K"); + let castle_white_queenside = components[2].contains("Q"); + let castle_black_kingside = components[2].contains("k"); + let castle_black_queenside = components[2].contains("q"); + + let en_passant_target = match components[3] { + "-" => None, + _ => match algebraic_to_boardloc(components[3]) { + Ok(t) => Some(t), + Err(e) => return Err(FENParseError::EnPassantTargetParseError { e }) + } + }; + + let halfmove_counter = match components[4].parse() { + Ok(r) => r, + Err(e) => return Err(FENParseError::InvalidMoveCounter { e }) + }; + + let move_counter = match components[5].parse() { + Ok(r) => r, + Err(e) => return Err(FENParseError::InvalidMoveCounter { e }) + }; + + // parse the actual piece string + + let mut piecelist = Vec::new(); + let mut bitfield = Boardfield::new(); + + + + Ok(Self { + bitfield, + piecelist, + turn, + castle_white_kingside, + castle_white_queenside, + castle_black_kingside, + castle_black_queenside, + en_passant_target, + move_counter, + halfmove_counter, + }) } +} - fn set_pos(&mut self, boardloc: usize, piece: u8) { - if boardloc > 63 { - panic!("boardloc out of range"); - } - - let field = self[boardloc / 2]; - let shift = 4 * (boardloc % 2); - - // clear out the field - let bf_cleared = field & !(0b1111 << shift); - // then shift over the actual piece data - let piece_shifted = piece << shift; - // then OR them together - let bf_new = bf_cleared | piece_shifted; - - self[boardloc / 2] = bf_new; +fn create_fen_piece(c: char, rank: usize, file: usize) -> Result { + Ok(PieceOnBoard { + loc: rank + file * 8, + value: match c { + 'r' => PieceColor::Black as u8 | PieceType::Rook as u8, + 'n' => PieceColor::Black as u8 | PieceType::Knight as u8, + 'b' => PieceColor::Black as u8 | PieceType::Bishop as u8, + 'q' => PieceColor::Black as u8 | PieceType::Queen as u8, + 'k' => PieceColor::Black as u8 | PieceType::Knight as u8, + 'p' => PieceColor::Black as u8 | PieceType::Pawn as u8, + 'R' => PieceColor::White as u8 | PieceType::Rook as u8, + 'N' => PieceColor::White as u8 | PieceType::Knight as u8, + 'B' => PieceColor::White as u8 | PieceType::Bishop as u8, + 'Q' => PieceColor::White as u8 | PieceType::Queen as u8, + 'K' => PieceColor::White as u8 | PieceType::Knight as u8, + 'P' => PieceColor::White as u8 | PieceType::Pawn as u8, + _ => return Err(FENParseError::InvalidPieceCharacter { got: c }) + }, + }) +} +fn create_bitfield_piece(c: char) -> Result { + match c { + 'r' => Ok(PieceColor::Black as u8 | PieceType::Rook as u8), + 'n' => Ok(PieceColor::Black as u8 | PieceType::Knight as u8), + 'b' => Ok(PieceColor::Black as u8 | PieceType::Bishop as u8), + 'q' => Ok(PieceColor::Black as u8 | PieceType::Queen as u8), + 'k' => Ok(PieceColor::Black as u8 | PieceType::Knight as u8), + 'p' => Ok(PieceColor::Black as u8 | PieceType::Pawn as u8), + 'R' => Ok(PieceColor::White as u8 | PieceType::Rook as u8), + 'N' => Ok(PieceColor::White as u8 | PieceType::Knight as u8), + 'B' => Ok(PieceColor::White as u8 | PieceType::Bishop as u8), + 'Q' => Ok(PieceColor::White as u8 | PieceType::Queen as u8), + 'K' => Ok(PieceColor::White as u8 | PieceType::Knight as u8), + 'P' => Ok(PieceColor::White as u8 | PieceType::Pawn as u8), + _ => Err(FENParseError::InvalidPieceCharacter { got: c }) } } #[cfg(test)] mod tests { - use crate::board::{Boardfield, BoardfieldOps}; + use crate::board::Board; + use crate::boardfield::BoardfieldOps; use crate::piece::{PieceColor, PieceType}; #[test] - #[should_panic] - fn get_out_of_bounds() { - let field = Boardfield::startpos(); - field.get_pos(64); - } - - #[test] - #[should_panic] - fn set_out_of_bounds() { - let mut field = Boardfield::startpos(); - field.set_pos(64, 0u8); - } - - #[test] - fn bitfield_board() { - let field = Boardfield::startpos(); + pub fn fen_parse_testing() { + let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap(); + let field = board.bitfield; assert_eq!(field.get_pos(0), PieceType::Rook as u8 | PieceColor::White as u8); assert_eq!(field.get_pos(1), PieceType::Knight as u8 | PieceColor::White as u8); assert_eq!(field.get_pos(2), PieceType::Bishop as u8 | PieceColor::White as u8); diff --git a/src/boardfield.rs b/src/boardfield.rs new file mode 100644 index 0000000..b1ca6d8 --- /dev/null +++ b/src/boardfield.rs @@ -0,0 +1,180 @@ +// Bitpacked board: +// One piece can be represented with 4 bits. Therefore, we can fit two pieces in one byte: +// 0b0110_1110 - +// | \ +// Black king \- White king +// As there are 64 spaces on the board, we can pack the board into a 256-bit integer, and we use that as the index for transposition tables and such. +// Boards are stored as a 32-byte bitfield, to make accessing them as efficient as possible. +// We could theoretically pack it into two u128s, but some basic testing seems like LLVM can optimize array indexing faster than the bucketload of shift ops needed to pack it into u128s. + + +/* + + a b c d e f g h + +8 56 57 58 59 60 61 62 63 8 -+ +7 48 49 50 51 52 53 54 55 7 |- bf2 +6 40 41 42 43 44 45 46 47 6 | +5 32 33 34 35 36 37 38 39 5 -+ +4 24 25 26 27 28 29 30 31 4 -+ +3 16 17 18 19 20 21 22 23 3 | +2 8 9 10 11 12 13 14 15 2 |- bf1 +1 0 1 2 3 4 5 6 7 1 -+ + + a b c d e f g h + */ + +use crate::piece::{PieceColor, PieceType}; + +pub type Boardfield = [u8; 32]; +pub trait BoardfieldOps { + fn new() -> Self where Self: Sized; + fn startpos() -> Self where Self: Sized; + fn get_pos(&self, boardloc: usize) -> u8; + fn set_pos(&mut self, boardloc: usize, piece: u8); +} +impl BoardfieldOps for Boardfield { + fn new() -> Self where Self: Sized { + [0u8; 32] + } + + fn startpos() -> Self where Self: Sized { + let mut field = Self::new(); + + // a1-h1 (RNBQKBNR) + field.set_pos(0, PieceColor::White as u8 | PieceType::Rook as u8); + field.set_pos(1, PieceColor::White as u8 | PieceType::Knight as u8); + field.set_pos(2, PieceColor::White as u8 | PieceType::Bishop as u8); + field.set_pos(3, PieceColor::White as u8 | PieceType::Queen as u8); + field.set_pos(4, PieceColor::White as u8 | PieceType::King as u8); + field.set_pos(5, PieceColor::White as u8 | PieceType::Bishop as u8); + field.set_pos(6, PieceColor::White as u8 | PieceType::Knight as u8); + field.set_pos(7, PieceColor::White as u8 | PieceType::Rook as u8); + + // a2-h2 (PPPPPPPP) + field.set_pos(8, PieceColor::White as u8 | PieceType::Pawn as u8); + field.set_pos(9, PieceColor::White as u8 | PieceType::Pawn as u8); + field.set_pos(10, PieceColor::White as u8 | PieceType::Pawn as u8); + field.set_pos(11, PieceColor::White as u8 | PieceType::Pawn as u8); + field.set_pos(12, PieceColor::White as u8 | PieceType::Pawn as u8); + field.set_pos(13, PieceColor::White as u8 | PieceType::Pawn as u8); + field.set_pos(14, PieceColor::White as u8 | PieceType::Pawn as u8); + field.set_pos(15, PieceColor::White as u8 | PieceType::Pawn as u8); + + // a7-h7 (pppppppp) + field.set_pos(48, PieceColor::Black as u8 | PieceType::Pawn as u8); + field.set_pos(49, PieceColor::Black as u8 | PieceType::Pawn as u8); + field.set_pos(50, PieceColor::Black as u8 | PieceType::Pawn as u8); + field.set_pos(51, PieceColor::Black as u8 | PieceType::Pawn as u8); + field.set_pos(52, PieceColor::Black as u8 | PieceType::Pawn as u8); + field.set_pos(53, PieceColor::Black as u8 | PieceType::Pawn as u8); + field.set_pos(54, PieceColor::Black as u8 | PieceType::Pawn as u8); + field.set_pos(55, PieceColor::Black as u8 | PieceType::Pawn as u8); + + // a8-h8 (RNBQKBNR) + field.set_pos(56, PieceColor::Black as u8 | PieceType::Rook as u8); + field.set_pos(57, PieceColor::Black as u8 | PieceType::Knight as u8); + field.set_pos(58, PieceColor::Black as u8 | PieceType::Bishop as u8); + field.set_pos(59, PieceColor::Black as u8 | PieceType::Queen as u8); + field.set_pos(60, PieceColor::Black as u8 | PieceType::King as u8); + field.set_pos(61, PieceColor::Black as u8 | PieceType::Bishop as u8); + field.set_pos(62, PieceColor::Black as u8 | PieceType::Knight as u8); + field.set_pos(63, PieceColor::Black as u8 | PieceType::Rook as u8); + + + field + } + + fn get_pos(&self, boardloc: usize) -> u8 { + // bf1: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + // bf2: 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 + + if boardloc > 63 { + panic!("boardloc out of range"); + } + + let field = self[boardloc / 2]; + let shift = 4 * (boardloc % 2); + + (field & (0b1111 << shift)) >> shift + } + + fn set_pos(&mut self, boardloc: usize, piece: u8) { + if boardloc > 63 { + panic!("boardloc out of range"); + } + + let field = self[boardloc / 2]; + let shift = 4 * (boardloc % 2); + + // clear out the field + let bf_cleared = field & !(0b1111 << shift); + // then shift over the actual piece data + let piece_shifted = piece << shift; + // then OR them together + let bf_new = bf_cleared | piece_shifted; + + self[boardloc / 2] = bf_new; + } +} + +#[cfg(test)] +mod tests { + use crate::boardfield::{Boardfield, BoardfieldOps}; + use crate::piece::{PieceColor, PieceType}; + + #[test] + #[should_panic] + fn get_out_of_bounds() { + let field = Boardfield::startpos(); + field.get_pos(64); + } + + #[test] + #[should_panic] + fn set_out_of_bounds() { + let mut field = Boardfield::startpos(); + field.set_pos(64, 0u8); + } + + #[test] + fn bitfield_board() { + let field = Boardfield::startpos(); + + assert_eq!(field.get_pos(0), PieceType::Rook as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(1), PieceType::Knight as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(2), PieceType::Bishop as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(3), PieceType::Queen as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(4), PieceType::King as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(5), PieceType::Bishop as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(6), PieceType::Knight as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(7), PieceType::Rook as u8 | PieceColor::White as u8); + + assert_eq!(field.get_pos(8), PieceType::Pawn as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(9), PieceType::Pawn as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(10), PieceType::Pawn as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(11), PieceType::Pawn as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(12), PieceType::Pawn as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(13), PieceType::Pawn as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(14), PieceType::Pawn as u8 | PieceColor::White as u8); + assert_eq!(field.get_pos(15), PieceType::Pawn as u8 | PieceColor::White as u8); + + assert_eq!(field.get_pos(48), PieceType::Pawn as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(49), PieceType::Pawn as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(50), PieceType::Pawn as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(51), PieceType::Pawn as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(52), PieceType::Pawn as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(53), PieceType::Pawn as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(54), PieceType::Pawn as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(55), PieceType::Pawn as u8 | PieceColor::Black as u8); + + assert_eq!(field.get_pos(56), PieceType::Rook as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(57), PieceType::Knight as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(58), PieceType::Bishop as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(59), PieceType::Queen as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(60), PieceType::King as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(61), PieceType::Bishop as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(62), PieceType::Knight as u8 | PieceColor::Black as u8); + assert_eq!(field.get_pos(63), PieceType::Rook as u8 | PieceColor::Black as u8); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 1013d4c..a7d6507 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,10 @@ use spin::Mutex; #[cfg(not(tarpaulin_include))] // UCI parse engine is not tested with tarpaulin, outer test harness is planned pub mod uci; -pub mod board; +pub mod boardfield; pub mod piece; +pub mod board; +pub mod utils; pub const ENGINE_NAME: &str = "Bamboo"; pub const ENGINE_VERSION: &str = "1.0"; diff --git a/src/piece.rs b/src/piece.rs index 95829b9..84423db 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -1,3 +1,18 @@ +#[derive(Debug)] +pub struct PieceOnBoard { + pub loc: usize, + pub value: Piece +} +impl PieceOnBoard { + fn new(loc: usize, value: Piece) -> Self { + Self { + loc, + value + } + } +} + +#[derive(Debug)] #[repr(u8)] pub enum PieceType { Pawn = 1, @@ -8,6 +23,7 @@ pub enum PieceType { King = 6 } +#[derive(Debug)] #[repr(u8)] pub enum PieceColor { White = 8, diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..3be98af --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,103 @@ +/* + + a b c d e f g h + +8 56 57 58 59 60 61 62 63 8 -+ +7 48 49 50 51 52 53 54 55 7 |- bf2 +6 40 41 42 43 44 45 46 47 6 | +5 32 33 34 35 36 37 38 39 5 -+ +4 24 25 26 27 28 29 30 31 4 -+ +3 16 17 18 19 20 21 22 23 3 | +2 8 9 10 11 12 13 14 15 2 |- bf1 +1 0 1 2 3 4 5 6 7 1 -+ + + a b c d e f g h + */ + +use std::error::Error; +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +pub enum AlgebraicNotationError { + InvalidRank { got: String }, + InvalidFile { got: String }, + InvalidLength { got: usize } +} +impl Error for AlgebraicNotationError {} +impl Display for AlgebraicNotationError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AlgebraicNotationError::InvalidRank { got } => write!(f, "Invalid rank: expected one of `012345678`, got `{}`", got), + AlgebraicNotationError::InvalidFile { got } => write!(f, "Invalid file: expected one of `abcdefgh`, got `{}`", got), + AlgebraicNotationError::InvalidLength { got } => write!(f, "Invalid length, expected 2, got `{}`", got) + } + } +} + +pub fn algebraic_to_boardloc(algebraic: &str) -> Result { + if algebraic.chars().count() != 2 { + return Err(AlgebraicNotationError::InvalidLength { got: algebraic.chars().count() }) + } + + let chars: Vec = algebraic.chars().collect(); + + let file_char = chars[0]; + let rank_char = chars[1]; + + if (file_char as u8) < b'a' || (file_char as u8) > b'h' { + return Err(AlgebraicNotationError::InvalidFile { got: file_char.to_string() }) + } + if (rank_char as u8) < b'0' || (rank_char as u8) > b'8' { + return Err(AlgebraicNotationError::InvalidRank { got: rank_char.to_string() }) + } + + let file = (file_char as u8 - 97) as usize; + let rank = (rank_char as u8 - 48) as usize; + + Ok(rank + file * 8) +} + +const BOARDLOC_TO_ALG: [&str; 64] = ["a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7", "h0", "h1", "h2", "h3", "h4", "h5", "h6", "h7"]; + +pub fn boardloc_to_algebraic<'a>(boardloc: usize) -> &'a str { + BOARDLOC_TO_ALG[boardloc] +} + +#[cfg(test)] +mod tests { + use crate::utils::{algebraic_to_boardloc, boardloc_to_algebraic}; + + #[test] + fn boardloc_testing() { + for i in 0..64 { + assert_eq!(algebraic_to_boardloc(boardloc_to_algebraic(i)).unwrap(), i); + } + } + + #[test] + #[should_panic] + fn boardloc_invalid_file() { + if let Err(e) = algebraic_to_boardloc("j8") { + println!("{}", e); + panic!(); + } + } + + #[test] + #[should_panic] + fn boardloc_invalid_length() { + if let Err(e) = algebraic_to_boardloc("jsdf8") { + println!("{}", e); + panic!(); + } + } + + #[test] + #[should_panic] + fn boardloc_invalid_rank() { + if let Err(e) = algebraic_to_boardloc("a9") { + println!("{}", e); + panic!(); + } + } +} \ No newline at end of file