Parse FEN string into game state
This commit is contained in:
171
src/fen.rs
Normal file
171
src/fen.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use crate::board::{Board, Color, Kind, Piece};
|
||||
use crate::game::{Game, State};
|
||||
use String as FenError;
|
||||
|
||||
pub fn from_fen(fen: &str) -> Game {
|
||||
let mut board = Board::empty_board();
|
||||
let fen_parts: Vec<_> = fen.split_whitespace().collect();
|
||||
|
||||
piece_placement(fen_parts[0], &mut board);
|
||||
|
||||
let side_to_move = side_to_move(fen_parts[1]);
|
||||
|
||||
let castling_ability = castling_ability(fen_parts[2]);
|
||||
|
||||
let en_passant_target_square: Result<u8, FenError> = en_passant_target_square(fen_parts[3]);
|
||||
|
||||
let halfmove_clock = halfmove_clock(fen_parts[4]);
|
||||
|
||||
let fullmove_counter = fullmove_counter(fen_parts[5]);
|
||||
|
||||
Game {
|
||||
board,
|
||||
state: State::load_state(
|
||||
side_to_move.unwrap(),
|
||||
castling_ability.unwrap(),
|
||||
en_passant_target_square.unwrap(),
|
||||
halfmove_clock.unwrap(),
|
||||
fullmove_counter.unwrap(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn piece_placement(pieces: &str, board: &mut Board) {
|
||||
let (mut file, mut rank): (u8, u8) = (0, 7);
|
||||
|
||||
for c in pieces.chars() {
|
||||
let square: u8 = rank * 8 + file;
|
||||
let color = if c.is_ascii_lowercase() {
|
||||
Color::Black
|
||||
} else {
|
||||
Color::White
|
||||
};
|
||||
|
||||
if let Some(kind) = match c.to_ascii_lowercase() {
|
||||
'r' => Some(Kind::Rook),
|
||||
'n' => Some(Kind::Knight),
|
||||
'b' => Some(Kind::Bishop),
|
||||
'q' => Some(Kind::Queen),
|
||||
'k' => Some(Kind::King),
|
||||
'p' => Some(Kind::Pawn),
|
||||
'/' => {
|
||||
file = 0;
|
||||
rank = rank.saturating_sub(1);
|
||||
continue;
|
||||
}
|
||||
n if n.is_numeric() => {
|
||||
rank %= n.to_digit(10).unwrap_or(0) as u8;
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
} {
|
||||
board.set_piece(Piece::new(1 << square, kind, color));
|
||||
};
|
||||
|
||||
file += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn side_to_move(side: &str) -> Result<Color, FenError> {
|
||||
match side {
|
||||
"w" => Ok(Color::White),
|
||||
"b" => Ok(Color::Black),
|
||||
_ => Err(FenError::from(
|
||||
"Found invalid character while parsing in side_to_move",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return castling ability as a bitflag. LSb K, 2nd LSb Q, 3rd LSb k, 4th LSb q. Ignore 4 MSb's
|
||||
fn castling_ability(castling: &str) -> Result<u8, FenError> {
|
||||
let mut rights = 0b0;
|
||||
|
||||
for c in castling.chars() {
|
||||
match c {
|
||||
'K' => rights |= 0b1,
|
||||
'Q' => rights |= 0b10,
|
||||
'k' => rights |= 0b100,
|
||||
'q' => rights |= 0b1000,
|
||||
'-' => rights |= 0b0,
|
||||
_ => {
|
||||
return Err(FenError::from(
|
||||
"Found invalid character while parsing in castling_ability",
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(rights)
|
||||
}
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn en_passant_target_square(square: &str) -> Result<u8, FenError> {
|
||||
let mut sqr = square.chars();
|
||||
|
||||
let mut files: HashMap<char, u8> = HashMap::new();
|
||||
files.insert('a', 0);
|
||||
files.insert('b', 1);
|
||||
files.insert('c', 2);
|
||||
files.insert('d', 3);
|
||||
files.insert('e', 4);
|
||||
files.insert('f', 5);
|
||||
files.insert('g', 6);
|
||||
files.insert('h', 7);
|
||||
|
||||
let mut ranks: HashMap<char, u8> = HashMap::new();
|
||||
ranks.insert('3', 2);
|
||||
ranks.insert('6', 5);
|
||||
|
||||
match sqr.next() {
|
||||
Some(file) if files.contains_key(&file) => match sqr.next() {
|
||||
Some(rank) if ranks.contains_key(&rank) => {
|
||||
Ok(ranks.get(&rank).unwrap() * 8 + files.get(&file).unwrap())
|
||||
}
|
||||
Some(_) | None => Err(FenError::from(
|
||||
"Not a valid rank (3 or 6) for an en passant target square",
|
||||
)),
|
||||
},
|
||||
Some('-') => Ok(0),
|
||||
Some(_) | None => Err(FenError::from("Not a file (a..h) or dash (-) character")),
|
||||
}
|
||||
}
|
||||
|
||||
fn halfmove_clock(halfmove: &str) -> Result<u8, FenError> {
|
||||
halfmove
|
||||
.parse::<u8>()
|
||||
.map_err(|_| FenError::from("Invalid halfmove_clock value"))
|
||||
}
|
||||
|
||||
fn fullmove_counter(fullmove: &str) -> Result<u8, FenError> {
|
||||
if "0".eq(fullmove) {
|
||||
Err(FenError::from(
|
||||
"Full move counter is not allowed to start with 0",
|
||||
))
|
||||
} else {
|
||||
fullmove
|
||||
.parse::<u8>()
|
||||
.map_err(|_| FenError::from("Invalid fullmove_counter value"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const FEN_EXAMPLE: [&str; 4] = [
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
||||
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
|
||||
"rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2",
|
||||
"rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2 ",
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_sqrt() -> Result<(), String> {
|
||||
let game = from_fen(FEN_EXAMPLE[3]);
|
||||
println!("{}\n{}", game.board, game.state);
|
||||
|
||||
assert_eq!(from_fen(FEN_EXAMPLE[0]), Game::new());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user