Parse FEN string into game state
This commit is contained in:
60
src/board.rs
60
src/board.rs
@@ -1,6 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use u64 as Bitboard;
|
use u64 as Bitboard;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
white_pieces: [Piece; 6],
|
white_pieces: [Piece; 6],
|
||||||
black_pieces: [Piece; 6],
|
black_pieces: [Piece; 6],
|
||||||
@@ -22,11 +23,45 @@ impl Board {
|
|||||||
Piece::new(0x8100000000000000, Kind::Rook, Color::Black),
|
Piece::new(0x8100000000000000, Kind::Rook, Color::Black),
|
||||||
Piece::new(0x4200000000000000, Kind::Knight, Color::Black),
|
Piece::new(0x4200000000000000, Kind::Knight, Color::Black),
|
||||||
Piece::new(0x2400000000000000, Kind::Bishop, Color::Black),
|
Piece::new(0x2400000000000000, Kind::Bishop, Color::Black),
|
||||||
Piece::new(0x1000000000000000, Kind::Queen, Color::Black),
|
Piece::new(0x800000000000000, Kind::Queen, Color::Black),
|
||||||
Piece::new(0x800000000000000, Kind::King, Color::Black),
|
Piece::new(0x1000000000000000, Kind::King, Color::Black),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn empty_board() -> Board {
|
||||||
|
Board {
|
||||||
|
white_pieces: [
|
||||||
|
Piece::new(0x0, Kind::Pawn, Color::White),
|
||||||
|
Piece::new(0x0, Kind::Rook, Color::White),
|
||||||
|
Piece::new(0x0, Kind::Knight, Color::White),
|
||||||
|
Piece::new(0x0, Kind::Bishop, Color::White),
|
||||||
|
Piece::new(0x0, Kind::Queen, Color::White),
|
||||||
|
Piece::new(0x0, Kind::King, Color::White),
|
||||||
|
],
|
||||||
|
black_pieces: [
|
||||||
|
Piece::new(0x0, Kind::Pawn, Color::Black),
|
||||||
|
Piece::new(0x0, Kind::Rook, Color::Black),
|
||||||
|
Piece::new(0x0, Kind::Knight, Color::Black),
|
||||||
|
Piece::new(0x0, Kind::Bishop, Color::Black),
|
||||||
|
Piece::new(0x0, Kind::Queen, Color::Black),
|
||||||
|
Piece::new(0x0, Kind::King, Color::Black),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_piece(&mut self, piece: Piece) {
|
||||||
|
match piece.color {
|
||||||
|
Color::Black => self.black_pieces[piece.kind as usize].bitboard |= piece.bitboard,
|
||||||
|
Color::White => self.white_pieces[piece.kind as usize].bitboard |= piece.bitboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Board {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Board {
|
impl fmt::Display for Board {
|
||||||
@@ -38,7 +73,7 @@ impl fmt::Display for Board {
|
|||||||
piece.color, piece.kind
|
piece.color, piece.kind
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
writeln!(f, "")?;
|
writeln!(f)?;
|
||||||
for piece in &self.black_pieces {
|
for piece in &self.black_pieces {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
@@ -46,20 +81,21 @@ impl fmt::Display for Board {
|
|||||||
piece.color, piece.kind
|
piece.color, piece.kind
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
write!(f, "")
|
writeln!(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Piece {
|
pub struct Piece {
|
||||||
pub position: Bitboard,
|
pub bitboard: Bitboard,
|
||||||
pub kind: Kind,
|
pub kind: Kind,
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Piece {
|
impl Piece {
|
||||||
pub fn new(position: Bitboard, kind: Kind, color: Color) -> Piece {
|
pub fn new(bitboard: Bitboard, kind: Kind, color: Color) -> Piece {
|
||||||
Piece {
|
Piece {
|
||||||
position,
|
bitboard,
|
||||||
kind,
|
kind,
|
||||||
color,
|
color,
|
||||||
}
|
}
|
||||||
@@ -71,16 +107,16 @@ impl fmt::Display for Piece {
|
|||||||
for rank in (0..8).rev() {
|
for rank in (0..8).rev() {
|
||||||
for file in 0..8 {
|
for file in 0..8 {
|
||||||
let square = 1 << (rank * 8 + file);
|
let square = 1 << (rank * 8 + file);
|
||||||
let position = if square & self.position != 0x0 { 1 } else { 0 };
|
let position = if square & self.bitboard != 0x0 { 1 } else { 0 };
|
||||||
write!(f, "{} ", position)?;
|
write!(f, "{} ", position)?;
|
||||||
}
|
}
|
||||||
writeln!(f, "")?;
|
writeln!(f)?;
|
||||||
}
|
}
|
||||||
write!(f, "")
|
writeln!(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
Pawn,
|
Pawn,
|
||||||
Rook,
|
Rook,
|
||||||
@@ -90,7 +126,7 @@ pub enum Kind {
|
|||||||
King,
|
King,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
White,
|
White,
|
||||||
Black,
|
Black,
|
||||||
|
|||||||
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/game.rs
82
src/game.rs
@@ -1,14 +1,86 @@
|
|||||||
use crate::board::Board;
|
use crate::{
|
||||||
|
board::{Board, Color},
|
||||||
|
fen::from_fen,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Game {}
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Game {
|
||||||
|
pub board: Board,
|
||||||
|
pub state: State,
|
||||||
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
pub fn new() -> Game {
|
pub fn new() -> Game {
|
||||||
Game {}
|
Game {
|
||||||
|
board: Board::new(),
|
||||||
|
state: State::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_fen(fen: &str) -> Game {
|
||||||
|
from_fen(fen)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) {
|
pub fn run(&self) {
|
||||||
let board = Board::new();
|
Board::new();
|
||||||
println!("{}", board)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Game {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct State {
|
||||||
|
side_to_move: Color,
|
||||||
|
castling_ability: u8,
|
||||||
|
en_passant_target_square: u8,
|
||||||
|
halfmove_clock: u8,
|
||||||
|
fullmove_counter: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new() -> State {
|
||||||
|
State {
|
||||||
|
side_to_move: Color::White,
|
||||||
|
castling_ability: 0b1111,
|
||||||
|
en_passant_target_square: 0,
|
||||||
|
halfmove_clock: 0,
|
||||||
|
fullmove_counter: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_state(
|
||||||
|
side_to_move: Color,
|
||||||
|
castling_ability: u8,
|
||||||
|
en_passant_target_square: u8,
|
||||||
|
halfmove_clock: u8,
|
||||||
|
fullmove_counter: u8,
|
||||||
|
) -> State {
|
||||||
|
State {
|
||||||
|
side_to_move,
|
||||||
|
castling_ability,
|
||||||
|
en_passant_target_square,
|
||||||
|
halfmove_clock,
|
||||||
|
fullmove_counter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
impl fmt::Display for State {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
writeln!(f,
|
||||||
|
"side_to_move: {:?}\ncastling_ability: {:b}\nen_passant_target_square: {:b}\nhalfmove_clock: {}\nfullmove_counter: {}\n",
|
||||||
|
self.side_to_move, self.castling_ability, 1u64 << self.en_passant_target_square, self.halfmove_clock, self.fullmove_counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
pub mod board;
|
pub mod board;
|
||||||
|
pub mod fen;
|
||||||
pub mod game;
|
pub mod game;
|
||||||
|
pub mod r#move;
|
||||||
|
|
||||||
use game::Game;
|
use game::Game;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user