use crate::{
CastlingRights,
Columns::{self, COLUMN_AMOUNT},
Field, Piece, PlayerColor, Position,
Rows::{self, ROW_AMOUNT},
chess::position::BoardSetup,
};
use super::{Fen, error::FenParserError};
const PIECE_PLACEMENT_DATA: &str = "Piece placement data";
const ACTIVE_COLOR: &str = "Active color";
const CASTLING_AVAILABILITY: &str = "Castling availability";
const EN_PASSANT_TARGET_SQUARE: &str = "En passant target square";
const HALFMOVE_CLOCK: &str = "Halfmove clock";
const FULLMOVE_NUMBER: &str = "Fullmove number";
impl Fen {
pub fn import(fen_input: &str) -> Result<Position, FenParserError> {
let fen_parts: Vec<&str> = fen_input.split_ascii_whitespace().collect();
let Some(piece_placement_input) = fen_parts.first() else {
return Err(FenParserError::FenDataMissing {
missing_part: PIECE_PLACEMENT_DATA.to_string(),
fen_input: fen_input.to_string(),
});
};
let mut board_position = [[None; COLUMN_AMOUNT]; ROW_AMOUNT];
Self::compute_piece_placement_data(piece_placement_input, &mut board_position)?;
let Some(active_color) = fen_parts.get(1) else {
return Err(FenParserError::FenDataMissing {
missing_part: ACTIVE_COLOR.to_string(),
fen_input: fen_input.to_string(),
});
};
let active_color: PlayerColor = Self::string_to_active_color(active_color)?;
let Some(castling_availability) = fen_parts.get(2) else {
return Err(FenParserError::FenDataMissing {
missing_part: CASTLING_AVAILABILITY.to_string(),
fen_input: fen_input.to_string(),
});
};
let (white_castle, black_castle) = Self::string_to_castling_rights(castling_availability)?;
let Some(en_passant) = fen_parts.get(3) else {
return Err(FenParserError::FenDataMissing {
missing_part: EN_PASSANT_TARGET_SQUARE.to_string(),
fen_input: fen_input.to_string(),
});
};
let en_passant = Self::get_en_passant_square(en_passant)?;
let Some(halfmove_clock) = fen_parts.get(4) else {
return Err(FenParserError::FenDataMissing {
missing_part: EN_PASSANT_TARGET_SQUARE.to_string(),
fen_input: fen_input.to_string(),
});
};
let halfmove_clock = match halfmove_clock.parse::<u16>() {
Ok(value) => value,
Err(_) => {
return Err(FenParserError::UnrecognisedContent {
input: halfmove_clock.to_string(),
fen_part: HALFMOVE_CLOCK.to_string(),
});
}
};
let Some(fullmove_counter) = fen_parts.get(5) else {
return Err(FenParserError::FenDataMissing {
missing_part: EN_PASSANT_TARGET_SQUARE.to_string(),
fen_input: fen_input.to_string(),
});
};
let fullmove_counter = match fullmove_counter.parse::<u16>() {
Ok(value) => value,
Err(_) => {
return Err(FenParserError::UnrecognisedContent {
input: halfmove_clock.to_string(),
fen_part: FULLMOVE_NUMBER.to_string(),
});
}
};
let position = Position::new(
board_position,
active_color,
white_castle,
black_castle,
en_passant,
halfmove_clock,
fullmove_counter,
);
Ok(position)
}
fn compute_piece_placement_data(
piece_data: &str,
board: &mut BoardSetup,
) -> Result<(), FenParserError> {
let rows: Vec<&str> = piece_data.split('/').collect();
for row_counter in ((Rows::ROW_1 as usize)..=(Rows::ROW_8 as usize)).rev() {
let mut piece_counter: usize = 0;
let Some(current_row) = rows.get(row_counter) else {
return Err(FenParserError::InvalidPiecePlacementData(format!(
"Row {} is missing",
row_counter + 1
)));
};
for char_index in current_row.as_bytes() {
if *char_index >= b'1' && *char_index <= b'8' {
let empty_fields_amount = char_index - b'0';
if piece_counter + empty_fields_amount as usize > COLUMN_AMOUNT {
return Err(FenParserError::InvalidPiecePlacementData(format!(
"Row {} contains more than {COLUMN_AMOUNT} fields",
row_counter + 1
)));
}
piece_counter += empty_fields_amount as usize;
} else if let Some(piece) = Piece::import_piece(*char_index as char) {
if piece_counter > (COLUMN_AMOUNT - 1) {
return Err(FenParserError::InvalidPiecePlacementData(format!(
"Row {} contains more than {COLUMN_AMOUNT} fields",
row_counter + 1
)));
}
board[Columns::COLUMN_H as usize - row_counter][piece_counter] = Some(piece);
piece_counter += 1;
} else {
return Err(FenParserError::UnrecognisedContent {
input: (*char_index as char).to_string(),
fen_part: PIECE_PLACEMENT_DATA.to_string(),
});
}
}
}
Ok(())
}
fn string_to_active_color(color_data: &str) -> Result<PlayerColor, FenParserError> {
match color_data {
"w" => Ok(PlayerColor::White),
"b" => Ok(PlayerColor::Black),
_ => Err(FenParserError::UnrecognisedContent {
input: color_data.to_string(),
fen_part: ACTIVE_COLOR.to_string(),
}),
}
}
fn get_en_passant_square(field_data: &str) -> Result<Option<Field>, FenParserError> {
if field_data == "-" {
return Ok(None);
}
match Field::new_from_string(field_data) {
Some(field) => Ok(Some(field)),
None => Err(FenParserError::UnrecognisedContent {
input: field_data.to_string(),
fen_part: EN_PASSANT_TARGET_SQUARE.to_string(),
}),
}
}
fn string_to_castling_rights(
castling_data: &str,
) -> Result<(CastlingRights, CastlingRights), FenParserError> {
let letters = castling_data.as_bytes();
let mut castling_black = CastlingRights::new(false, false);
let mut castling_white = CastlingRights::new(false, false);
for position in letters {
match *position as char {
'K' => castling_white.set_kingside(true),
'Q' => castling_white.set_queenside(true),
'k' => castling_black.set_kingside(true),
'q' => castling_black.set_queenside(true),
'-' => break,
_ => {
return Err(FenParserError::UnrecognisedContent {
input: (*position as char).to_string(),
fen_part: CASTLING_AVAILABILITY.to_string(),
});
}
}
}
Ok((castling_white, castling_black))
}
}