diff --git a/src/board.rs b/src/board.rs index 9b0d002..9631df1 100644 --- a/src/board.rs +++ b/src/board.rs @@ -95,11 +95,7 @@ pub fn new_empty() -> Board { [SQ_E; 64] } -pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - -/// Generate a board from a FEN string. -/// -/// This will only parse the first FEN field. +/// Generate a board from a FEN placement string. pub fn new_from_fen(fen: &str) -> Board { let mut board = [SQ_E; 64]; let mut f = 0; @@ -216,6 +212,7 @@ pub fn draw(board: &Board) { #[cfg(test)] mod tests { use super::*; + use crate::notation; #[test] fn test_opposite() { @@ -235,7 +232,7 @@ mod tests { #[test] fn test_new_from_fen() { let b1 = new(); - let b2 = new_from_fen(FEN_START); + let b2 = new_from_fen(notation::FEN_START); assert!(eq(&b1, &b2)); } diff --git a/src/engine.rs b/src/engine.rs new file mode 100644 index 0000000..60e8dfc --- /dev/null +++ b/src/engine.rs @@ -0,0 +1,24 @@ +//! Vatu engine. + +use crate::board; +use crate::notation; + +pub struct Engine { + board: board::Board, +} + +impl Engine { + pub fn new() -> Engine { + Engine { + board: board::new_empty(), + } + } + + pub fn apply_fen(&mut self, fen: ¬ation::Fen) { + self.set_placement(&fen.placement); + } + + fn set_placement(&mut self, placement: &str) { + self.board = board::new_from_fen(placement); + } +} diff --git a/src/main.rs b/src/main.rs index 125b8fd..dbabd78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; pub mod board; pub mod cli; +pub mod engine; +pub mod notation; pub mod rules; pub mod uci; diff --git a/src/notation.rs b/src/notation.rs index f0ea101..465d28f 100644 --- a/src/notation.rs +++ b/src/notation.rs @@ -1,17 +1,49 @@ //! Functions using various notations. -use nom::IResult; +pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; /// FEN notation for positions, split into fields. +#[derive(Debug, Clone)] pub struct Fen { - placement: String, - color: String, - castling: String, - en_passant: String, - halfmove: String, - fullmove: String, + pub placement: String, + pub color: String, + pub castling: String, + pub en_passant: String, + pub halfmove: String, + pub fullmove: String, } -fn parse_fen(i: &str) -> IResult<&str, Fen> { - +pub fn parse_fen(i: &str) -> Option { + let fields: Vec<&str> = i.split_whitespace().collect(); + parse_fen_fields(fields) +} + +pub fn parse_fen_fields(fields: Vec<&str>) -> Option { + if fields.len() < 6 { + return None + } + Some(Fen { + placement: fields[0].to_string(), + color: fields[1].to_string(), + castling: fields[2].to_string(), + en_passant: fields[3].to_string(), + halfmove: fields[4].to_string(), + fullmove: fields[5].to_string(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_fen() { + let fen_start = parse_fen(FEN_START).unwrap(); + assert_eq!(&fen_start.placement, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"); + assert_eq!(&fen_start.color, "w"); + assert_eq!(&fen_start.castling, "KQkq"); + assert_eq!(&fen_start.en_passant, "-"); + assert_eq!(&fen_start.halfmove, "0"); + assert_eq!(&fen_start.fullmove, "1"); + } } diff --git a/src/uci.rs b/src/uci.rs index 6d33258..98a7f71 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -3,12 +3,8 @@ use std::fs; use std::io::{self, Write}; -use nom::IResult; -use nom::branch::alt; -use nom::character::is_space; -use nom::bytes::complete::{tag, take_while}; - -use crate::board; +use crate::engine; +use crate::notation; const VATU_NAME: &str = env!("CARGO_PKG_NAME"); const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); @@ -16,7 +12,7 @@ const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); /// Hold some values related to UCI comms. pub struct Uci { state: State, - board: board::Board, + engine: engine::Engine, logfile: Option, } @@ -34,22 +30,29 @@ pub enum RemoteCmd { IsReady, UciNewGame, Stop, - Position(String), + Position(PositionArgs), Quit, Unknown(String), } +/// Arguments for the position remote command. +#[derive(Debug)] +pub enum PositionArgs { + Startpos, + Fen(notation::Fen), +} + impl Uci { fn listen(&mut self) { loop { if let Some(cmd) = self.receive() { match parse_command(&cmd) { - Ok((_, cmd)) => { + Some(cmd) => { if !self.handle_command(&cmd) { break } } - _ => { + None => { self.log(format!("Unknown command: {}", cmd)) } } @@ -92,6 +95,7 @@ impl Uci { RemoteCmd::IsReady => if self.state == State::Ready { self.ready() }, RemoteCmd::UciNewGame => if self.state == State::Ready { /* Nothing to do. */ }, RemoteCmd::Stop => if self.state == State::Ready { /* Nothing to do. */ }, + RemoteCmd::Position(p) => if self.state == State::Ready { self.position(p) } RemoteCmd::Quit => return false, _ => { self.log(format!("Unknown command: {:?}", cmd)); } } @@ -106,17 +110,30 @@ impl Uci { self.state = State::Ready; } + /// Notify interface that it is ready. fn ready(&mut self) { self.send("readyok"); self.state = State::Ready; } + + fn position(&mut self, p_args: &PositionArgs) { + match p_args { + PositionArgs::Fen(fen) => { + self.engine.apply_fen(fen); + }, + PositionArgs::Startpos => { + let fen = notation::parse_fen(notation::FEN_START).unwrap(); + self.engine.apply_fen(&fen); + } + }; + } } -/// Start UCI I/O. +/// Create a new Uci object, ready for I/O. pub fn start(output: Option<&str>) { let mut uci = Uci { state: State::Init, - board: board::new_empty(), + engine: engine::Engine::new(), logfile: None }; if let Some(output) = output { @@ -128,19 +145,29 @@ pub fn start(output: Option<&str>) { uci.listen(); } -fn take_non_space(i: &str) -> IResult<&str, &str> { - take_while(|c| c != ' ')(i) -} - -fn parse_command(i: &str) -> IResult<&str, RemoteCmd> { - let (i, cmd) = take_non_space(i)?; - match cmd { - "uci" => Ok((i, RemoteCmd::Uci)), - "isready" => Ok((i, RemoteCmd::IsReady)), - "ucinewgame" => Ok((i, RemoteCmd::UciNewGame)), - "stop" => Ok((i, RemoteCmd::Stop)), - "position" => Ok((i, RemoteCmd::Position(i.trim().to_string()))), - "quit" => Ok((i, RemoteCmd::Quit)), - c => Ok((i, RemoteCmd::Unknown(c.to_string()))), +fn parse_command(s: &str) -> Option { + let fields: Vec<&str> = s.split_whitespace().collect(); + match fields[0] { + "uci" => Some(RemoteCmd::Uci), + "isready" => Some(RemoteCmd::IsReady), + "ucinewgame" => Some(RemoteCmd::UciNewGame), + "stop" => Some(RemoteCmd::Stop), + "position" => { + match fields[1] { + // Subcommand "fen" is followed by a FEN string. + "fen" => { + if let Some(fen) = notation::parse_fen_fields(fields[2..8].to_vec()) { + Some(RemoteCmd::Position(PositionArgs::Fen(fen))) + } else { + None + } + } + // Subcommand "startpos" assumes the board is a new game. + "startpos" => Some(RemoteCmd::Position(PositionArgs::Startpos)), + _ => None + } + } + "quit" => Some(RemoteCmd::Quit), + c => Some(RemoteCmd::Unknown(c.to_string())), } }