use crate::{
CLASSIC_RULESET, Columns, Field,
Fields::{FIELD_A1, FIELD_E1},
Piece, PieceType, PlayerColor, Position, Rows, Turn,
};
use pest::{Parser, iterators::Pair};
use super::{San, SanParserError};
#[derive(Parser)]
#[grammar = "parser/standard_algebraic_notation/standard_algebraic_notation.pest"]
struct SanStruct;
impl San {
pub fn import(raw: &str, current_position: &Position) -> Result<Turn, SanParserError> {
let mut san_data = raw;
if let Some(index) = san_data.find('+') {
san_data = &san_data[0..index];
}
let Ok(mut parsed_data) = SanStruct::parse(Rule::turn, san_data) else {
return Err(SanParserError::InvalidData(san_data.to_string()));
};
if let Some(turn_type) = parsed_data.next().unwrap().into_inner().next() {
return match turn_type.as_rule() {
Rule::pawn_move => Self::import_pawn_movement(turn_type, current_position),
Rule::castling => Ok(Self::import_handle_castling(&turn_type, current_position)),
Rule::piece_move_full => Self::import_piece_move_full(turn_type, current_position),
_ => unreachable!(),
};
}
unreachable!()
}
fn import_piece_move_full(
san_data: Pair<Rule>,
position: &Position,
) -> Result<Turn, SanParserError> {
let possible_moves = CLASSIC_RULESET.get_possible_turns(position);
let raw_turn = san_data.as_str().to_string();
let mut piece_type: Option<Piece> = None;
let mut target_field: Option<Field> = None;
let mut from_column: Option<u8> = None;
let mut from_row: Option<u8> = None;
for parts in san_data.into_inner() {
match parts.as_rule() {
Rule::piece_symbol => {
let letter = parts.as_str().as_bytes()[0] as char;
let piece = PieceType::import_piecetype(letter.to_ascii_lowercase());
if let Some(piece) = piece {
piece_type = Some(Piece::new(piece, position.get_active_color()));
} else {
piece_type = None;
}
}
Rule::piece_move => {
let (target, column, row) = Self::import_piece_move(parts);
target_field = Some(target);
from_column = column;
from_row = row;
}
_ => return Err(SanParserError::InvalidData(raw_turn)),
}
}
for turn in possible_moves {
if target_field.unwrap() == turn.target
&& position.get_field_occupation(&turn.current) == piece_type
{
let mut ok: bool = true;
if let Some(column_value) = from_column {
if turn.current.get_column() != column_value {
ok = false;
}
}
if let Some(row_value) = from_row {
if turn.current.get_row() != row_value {
ok = false;
}
}
if ok {
return Ok(turn);
}
}
}
Err(SanParserError::InvalidMove(raw_turn))
}
fn import_piece_move(san_data: Pair<Rule>) -> (Field, Option<u8>, Option<u8>) {
let mut from_column: Option<u8> = None;
let mut from_row: Option<u8> = None;
for part in san_data.into_inner() {
match part.as_rule() {
Rule::to_field => {
return (
Field::new_from_string(part.as_str()).unwrap(),
from_column,
from_row,
);
}
Rule::from_field => {
let data = part.as_str().as_bytes();
if data[0] >= b'a' && data[0] <= b'h' {
from_column = Some(data[0] - b'a');
if data.len() > 1 {
from_row = Some(data[1] - b'1');
}
} else {
from_row = Some(data[0] - b'1');
}
}
_ => unreachable!(),
}
}
unreachable!();
}
fn import_handle_castling(san_data: &Pair<Rule>, position: &Position) -> Turn {
let possible_moves = CLASSIC_RULESET.get_possible_turns(position);
let player_color = position.get_active_color();
let mut target_field = FIELD_A1;
let mut starting_field = FIELD_E1;
if player_color == PlayerColor::Black {
starting_field.set_row(Rows::ROW_8);
target_field.set_row(Rows::ROW_8);
}
match san_data.as_str() {
"O-O" | "0-0" => {
target_field.set_column(Columns::COLUMN_G);
}
"O-O-O" | "0-0-0" => {
target_field.set_column(Columns::COLUMN_C);
}
_ => unreachable!(),
};
possible_moves
.into_iter()
.find(|&turn| turn.target == target_field && turn.current == starting_field)
.unwrap()
}
fn import_pawn_movement(
san_data: Pair<Rule>,
position: &Position,
) -> Result<Turn, SanParserError> {
let possible_moves = CLASSIC_RULESET.get_possible_turns(position);
let raw_turn = san_data.as_str().to_string();
let mut target_field: Option<Field> = None;
let mut promotion_piece: Option<PieceType> = None;
let mut from_column: Option<u8> = None;
let mut from_row: Option<u8> = None;
for pawn_push in san_data.into_inner() {
match pawn_push.as_rule() {
Rule::to_field => target_field = Field::new_from_string(pawn_push.as_str()),
Rule::promotion_piece => {
let letter = (pawn_push.as_str().as_bytes()[0] as char).to_ascii_lowercase();
let piece_type = PieceType::import_piecetype(letter).unwrap();
promotion_piece = Some(piece_type);
}
Rule::from_field => {
let data = pawn_push.as_str().as_bytes();
from_column = Some(data[0] - b'a');
if data.len() > 1 {
from_row = Some(data[1] - 1);
}
}
_ => unreachable!(),
}
}
for turn in possible_moves {
let from_occupation = position.get_field_occupation(&turn.current);
let Some(moving_piece) = from_occupation else {
return Err(SanParserError::InvalidMove(raw_turn));
};
if target_field.unwrap() == turn.target
&& promotion_piece == turn.promotion
&& moving_piece.get_type() == PieceType::Pawn
{
match from_column {
Some(column) => {
match from_row {
Some(row) => {
if column == turn.current.get_column()
&& row == turn.current.get_row()
{
return Ok(turn);
}
}
None => {
if column == turn.current.get_column() {
return Ok(turn);
}
}
}
}
None => return Ok(turn),
}
}
}
Err(SanParserError::InvalidMove(raw_turn))
}
}