fail miserably at parsing fen (i have failed in the same way like 6 times now)

This commit is contained in:
c0repwn3r 2023-02-12 22:08:16 -05:00
parent e64128fbf1
commit 530d316dc1
Signed by: core
GPG Key ID: FDBF740DADDCEECF
5 changed files with 438 additions and 120 deletions

View File

@ -1,144 +1,161 @@
// Bitpacked board: use std::error::Error;
// One piece can be represented with 4 bits. Therefore, we can fit two pieces in one byte: use std::fmt::{Display, Formatter};
// 0b0110_1110 - use std::num::ParseIntError;
// | \ use crate::boardfield::{Boardfield, BoardfieldOps};
// Black king \- White king use crate::piece::{PieceColor, PieceOnBoard, PieceType};
// 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. use crate::utils::{algebraic_to_boardloc, AlgebraicNotationError, boardloc_to_algebraic};
// Boards are stored as two 128-bit bitfields, to make accessing them as efficient as possible.
/* #[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<PieceOnBoard>,
a b c d e f g h turn: PieceColor,
8 56 57 58 59 60 61 62 63 8 -+ castle_white_kingside: bool,
7 48 49 50 51 52 53 54 55 7 |- bf2 castle_white_queenside: bool,
6 40 41 42 43 44 45 46 47 6 | castle_black_kingside: bool,
5 32 33 34 35 36 37 38 39 5 -+ castle_black_queenside: bool,
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 en_passant_target: Option<usize>,
*/
use crate::piece::{PieceColor, PieceType}; move_counter: usize,
halfmove_counter: usize
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 { #[derive(Debug)]
let mut field = Self::new(); pub enum FENParseError {
InvalidNumberOfComponents { got: usize },
// a1-h1 (RNBQKBNR) InvalidPlayerToMove { got: String },
field.set_pos(0, PieceColor::White as u8 | PieceType::Rook as u8); EnPassantTargetParseError { e: AlgebraicNotationError },
field.set_pos(1, PieceColor::White as u8 | PieceType::Knight as u8); InvalidMoveCounter { e: ParseIntError },
field.set_pos(2, PieceColor::White as u8 | PieceType::Bishop as u8); InvalidPieceCharacter { got: char },
field.set_pos(3, PieceColor::White as u8 | PieceType::Queen as u8); CannotSkipToOutsideOfBoard { got: usize, which_is: usize },
field.set_pos(4, PieceColor::White as u8 | PieceType::King as u8); }
field.set_pos(5, PieceColor::White as u8 | PieceType::Bishop as u8); impl Error for FENParseError {}
field.set_pos(6, PieceColor::White as u8 | PieceType::Knight as u8); impl Display for FENParseError {
field.set_pos(7, PieceColor::White as u8 | PieceType::Rook as u8); fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
// a2-h2 (PPPPPPPP) Self::InvalidNumberOfComponents { got } => write!(f, "Invalid number of components: got {}, expected 6", got),
field.set_pos(8, PieceColor::White as u8 | PieceType::Pawn as u8); Self::InvalidPlayerToMove { got } => write!(f, "Invalid player to move: expected one of `wb`, got `{}`", got),
field.set_pos(9, PieceColor::White as u8 | PieceType::Pawn as u8); Self::EnPassantTargetParseError { e } => write!(f, "Error parsing en passant target: {}", e),
field.set_pos(10, PieceColor::White as u8 | PieceType::Pawn as u8); Self::InvalidMoveCounter { e } => write!(f, "Invalid move counter: {}", e),
field.set_pos(11, PieceColor::White as u8 | PieceType::Pawn as u8); Self::InvalidPieceCharacter { got } => write!(f, "Invalid piece character: expected one of `rnbqkpRNBQKP`, got `{}`", got),
field.set_pos(12, PieceColor::White as u8 | PieceType::Pawn as u8); 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)
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 { impl Board {
// 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 pub fn from_fen(fen: &str) -> Result<Self, FENParseError> {
// 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 let components = fen.split(' ').collect::<Vec<&str>>();
if components.len() != 6 {
if boardloc > 63 { return Err(FENParseError::InvalidNumberOfComponents { got: components.len() })
panic!("boardloc out of range");
} }
let field = self[boardloc / 2]; // turn to move
let shift = 4 * (boardloc % 2); 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) { fn create_fen_piece(c: char, rank: usize, file: usize) -> Result<PieceOnBoard, FENParseError> {
if boardloc > 63 { Ok(PieceOnBoard {
panic!("boardloc out of range"); 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<u8, FENParseError> {
let field = self[boardloc / 2]; match c {
let shift = 4 * (boardloc % 2); 'r' => Ok(PieceColor::Black as u8 | PieceType::Rook as u8),
'n' => Ok(PieceColor::Black as u8 | PieceType::Knight as u8),
// clear out the field 'b' => Ok(PieceColor::Black as u8 | PieceType::Bishop as u8),
let bf_cleared = field & !(0b1111 << shift); 'q' => Ok(PieceColor::Black as u8 | PieceType::Queen as u8),
// then shift over the actual piece data 'k' => Ok(PieceColor::Black as u8 | PieceType::Knight as u8),
let piece_shifted = piece << shift; 'p' => Ok(PieceColor::Black as u8 | PieceType::Pawn as u8),
// then OR them together 'R' => Ok(PieceColor::White as u8 | PieceType::Rook as u8),
let bf_new = bf_cleared | piece_shifted; 'N' => Ok(PieceColor::White as u8 | PieceType::Knight as u8),
'B' => Ok(PieceColor::White as u8 | PieceType::Bishop as u8),
self[boardloc / 2] = bf_new; '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)] #[cfg(test)]
mod tests { mod tests {
use crate::board::{Boardfield, BoardfieldOps}; use crate::board::Board;
use crate::boardfield::BoardfieldOps;
use crate::piece::{PieceColor, PieceType}; use crate::piece::{PieceColor, PieceType};
#[test] #[test]
#[should_panic] pub fn fen_parse_testing() {
fn get_out_of_bounds() { let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
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();
let field = board.bitfield;
assert_eq!(field.get_pos(0), PieceType::Rook as u8 | PieceColor::White as u8); 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(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(2), PieceType::Bishop as u8 | PieceColor::White as u8);

180
src/boardfield.rs Normal file
View File

@ -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);
}
}

View File

@ -7,8 +7,10 @@ use spin::Mutex;
#[cfg(not(tarpaulin_include))] // UCI parse engine is not tested with tarpaulin, outer test harness is planned #[cfg(not(tarpaulin_include))] // UCI parse engine is not tested with tarpaulin, outer test harness is planned
pub mod uci; pub mod uci;
pub mod board; pub mod boardfield;
pub mod piece; pub mod piece;
pub mod board;
pub mod utils;
pub const ENGINE_NAME: &str = "Bamboo"; pub const ENGINE_NAME: &str = "Bamboo";
pub const ENGINE_VERSION: &str = "1.0"; pub const ENGINE_VERSION: &str = "1.0";

View File

@ -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)] #[repr(u8)]
pub enum PieceType { pub enum PieceType {
Pawn = 1, Pawn = 1,
@ -8,6 +23,7 @@ pub enum PieceType {
King = 6 King = 6
} }
#[derive(Debug)]
#[repr(u8)] #[repr(u8)]
pub enum PieceColor { pub enum PieceColor {
White = 8, White = 8,

103
src/utils.rs Normal file
View File

@ -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<usize, AlgebraicNotationError> {
if algebraic.chars().count() != 2 {
return Err(AlgebraicNotationError::InvalidLength { got: algebraic.chars().count() })
}
let chars: Vec<char> = 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!();
}
}
}