use crate::{
turn_move, Castling, CastlingType, Flag, Move, Piece, Square, Turn,
};
pub fn parse_castling(castling: &str) -> Result<Turn, &'static str> {
let flags = get_flags(castling);
let r#type = if castling.contains("0-0-0") || castling.contains("O-O-O") {
CastlingType::Long
} else if castling.contains("0-0") || castling.contains("O-O") {
CastlingType::Short
} else {
return Err("Unknown chess notation found");
};
Ok(Turn::Castling(Castling { r#type, flags }))
}
pub fn get_piece_char(piece: Piece) -> char {
match piece {
Piece::King => 'K',
Piece::Queen => 'Q',
Piece::Rook => 'R',
Piece::Knight => 'N',
_ => 'B', }
}
unsafe fn get_char_from_idx(slice: &str, idx: usize) -> char {
*slice.as_ptr().offset(idx.try_into().unwrap()) as char
}
fn verify_promotion(piece: Piece, dst: Square) -> Result<(), &'static str> {
match piece {
Piece::King | Piece::Pawn => return Err("Invalid promotion piece"),
_ => (),
}
match Square::get_rank('8').unwrap().contains(&dst)
|| Square::get_rank('1').unwrap().contains(&dst)
{
true => Ok(()),
_ => Err("Invalid promotion square"),
}
}
fn verify_no_promotion(piece: Piece, dst: Square) -> Result<(), &'static str> {
match piece {
Piece::Pawn
if Square::get_rank('8').unwrap().contains(&dst)
|| Square::get_rank('1').unwrap().contains(&dst) =>
{
Err("Pawn must be promoted on the final rank")
}
_ => Ok(()),
}
}
fn get_piece_and_promotion(turn_move: &str) -> (Piece, Option<Piece>) {
let piece_idx = match turn_move.find(char::is_uppercase) {
None => return (Piece::Pawn, None),
Some(idx) => idx,
};
let piece_char = unsafe { get_char_from_idx(turn_move, piece_idx) };
let piece = match piece_char {
'K' => Piece::King,
'Q' => Piece::Queen,
'R' => Piece::Rook,
'N' => Piece::Knight,
'B' => Piece::Bishop,
_ => Piece::Pawn,
};
match turn_move.contains('=') {
false => (piece, None),
true => (Piece::Pawn, Some(piece)),
}
}
fn get_flags(turn: &str) -> u8 {
let mut flags: u8 = 0x00;
if turn.contains('x') {
flags |= Flag::CAPTURE;
}
if turn.contains('+') {
flags |= Flag::CHECK;
} else if turn.contains('#') {
flags |= Flag::CHECKMATE;
}
flags
}
fn get_squares(
turn_move: &str,
) -> Result<(Square, Option<Vec<Square>>), &'static str> {
const SQUARE_CHARS: &[char] = &[
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '1', '2', '3', '4', '5', '6',
'7', '8',
];
const STR_DST_SQR_NOT_FOUND: &str = "Destination square not found";
let (dst_idx_start, dst_idx_end) = match turn_move.rfind(SQUARE_CHARS) {
Some(i) if i > 0 => (i - 1, i),
_ => return Err(STR_DST_SQR_NOT_FOUND),
};
let dst_idx_range = dst_idx_start..=dst_idx_end;
let dst_square = Square::try_from(&turn_move[dst_idx_range])
.map_err(|_| STR_DST_SQR_NOT_FOUND)?;
let src_idx_start = turn_move.find(SQUARE_CHARS).unwrap();
if src_idx_start == dst_idx_start {
return Ok((dst_square, None));
}
let src_idx_range = src_idx_start..=src_idx_start + 1;
let src_squares = Some(match Square::try_from(&turn_move[src_idx_range]) {
Ok(square) => vec![square],
Err(_) => {
let c = unsafe { get_char_from_idx(turn_move, src_idx_start) };
match c.is_numeric() {
true => Square::get_rank(c)?,
false => Square::get_file(c)?,
}
}
});
Ok((dst_square, src_squares))
}
pub fn parse_move(turn_move: &str) -> Result<Turn, &'static str> {
const ALLOWED_CHARS: &str = "abcdefgh12345678#+=x?!KQRNBP";
for c in turn_move.chars() {
if !ALLOWED_CHARS.contains(c) {
return Err("Turn contains invalid characters");
}
}
let flags = get_flags(turn_move);
let (piece, promotion) = get_piece_and_promotion(turn_move);
let (dst, src) = get_squares(turn_move)?;
match promotion {
Some(promotion) => verify_promotion(promotion, dst)?,
None => verify_no_promotion(piece, dst)?,
}
Ok(turn_move!(piece, dst, flags, src, promotion))
}