uci: handle basic position command

This commit is contained in:
dece 2020-06-01 14:26:23 +02:00
parent d72042e455
commit 8c0a083cb0
5 changed files with 123 additions and 41 deletions

View file

@ -95,11 +95,7 @@ pub fn new_empty() -> Board {
[SQ_E; 64] [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 placement string.
/// Generate a board from a FEN string.
///
/// This will only parse the first FEN field.
pub fn new_from_fen(fen: &str) -> Board { pub fn new_from_fen(fen: &str) -> Board {
let mut board = [SQ_E; 64]; let mut board = [SQ_E; 64];
let mut f = 0; let mut f = 0;
@ -216,6 +212,7 @@ pub fn draw(board: &Board) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::notation;
#[test] #[test]
fn test_opposite() { fn test_opposite() {
@ -235,7 +232,7 @@ mod tests {
#[test] #[test]
fn test_new_from_fen() { fn test_new_from_fen() {
let b1 = new(); let b1 = new();
let b2 = new_from_fen(FEN_START); let b2 = new_from_fen(notation::FEN_START);
assert!(eq(&b1, &b2)); assert!(eq(&b1, &b2));
} }

24
src/engine.rs Normal file
View file

@ -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: &notation::Fen) {
self.set_placement(&fen.placement);
}
fn set_placement(&mut self, placement: &str) {
self.board = board::new_from_fen(placement);
}
}

View file

@ -4,6 +4,8 @@ use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
pub mod board; pub mod board;
pub mod cli; pub mod cli;
pub mod engine;
pub mod notation;
pub mod rules; pub mod rules;
pub mod uci; pub mod uci;

View file

@ -1,17 +1,49 @@
//! Functions using various notations. //! 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. /// FEN notation for positions, split into fields.
#[derive(Debug, Clone)]
pub struct Fen { pub struct Fen {
placement: String, pub placement: String,
color: String, pub color: String,
castling: String, pub castling: String,
en_passant: String, pub en_passant: String,
halfmove: String, pub halfmove: String,
fullmove: String, pub fullmove: String,
} }
fn parse_fen(i: &str) -> IResult<&str, Fen> { pub fn parse_fen(i: &str) -> Option<Fen> {
let fields: Vec<&str> = i.split_whitespace().collect();
parse_fen_fields(fields)
}
pub fn parse_fen_fields(fields: Vec<&str>) -> Option<Fen> {
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");
}
} }

View file

@ -3,12 +3,8 @@
use std::fs; use std::fs;
use std::io::{self, Write}; use std::io::{self, Write};
use nom::IResult; use crate::engine;
use nom::branch::alt; use crate::notation;
use nom::character::is_space;
use nom::bytes::complete::{tag, take_while};
use crate::board;
const VATU_NAME: &str = env!("CARGO_PKG_NAME"); const VATU_NAME: &str = env!("CARGO_PKG_NAME");
const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); 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. /// Hold some values related to UCI comms.
pub struct Uci { pub struct Uci {
state: State, state: State,
board: board::Board, engine: engine::Engine,
logfile: Option<fs::File>, logfile: Option<fs::File>,
} }
@ -34,22 +30,29 @@ pub enum RemoteCmd {
IsReady, IsReady,
UciNewGame, UciNewGame,
Stop, Stop,
Position(String), Position(PositionArgs),
Quit, Quit,
Unknown(String), Unknown(String),
} }
/// Arguments for the position remote command.
#[derive(Debug)]
pub enum PositionArgs {
Startpos,
Fen(notation::Fen),
}
impl Uci { impl Uci {
fn listen(&mut self) { fn listen(&mut self) {
loop { loop {
if let Some(cmd) = self.receive() { if let Some(cmd) = self.receive() {
match parse_command(&cmd) { match parse_command(&cmd) {
Ok((_, cmd)) => { Some(cmd) => {
if !self.handle_command(&cmd) { if !self.handle_command(&cmd) {
break break
} }
} }
_ => { None => {
self.log(format!("Unknown command: {}", cmd)) self.log(format!("Unknown command: {}", cmd))
} }
} }
@ -92,6 +95,7 @@ impl Uci {
RemoteCmd::IsReady => if self.state == State::Ready { self.ready() }, RemoteCmd::IsReady => if self.state == State::Ready { self.ready() },
RemoteCmd::UciNewGame => if self.state == State::Ready { /* Nothing to do. */ }, RemoteCmd::UciNewGame => if self.state == State::Ready { /* Nothing to do. */ },
RemoteCmd::Stop => 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, RemoteCmd::Quit => return false,
_ => { self.log(format!("Unknown command: {:?}", cmd)); } _ => { self.log(format!("Unknown command: {:?}", cmd)); }
} }
@ -106,17 +110,30 @@ impl Uci {
self.state = State::Ready; self.state = State::Ready;
} }
/// Notify interface that it is ready.
fn ready(&mut self) { fn ready(&mut self) {
self.send("readyok"); self.send("readyok");
self.state = State::Ready; 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>) { pub fn start(output: Option<&str>) {
let mut uci = Uci { let mut uci = Uci {
state: State::Init, state: State::Init,
board: board::new_empty(), engine: engine::Engine::new(),
logfile: None logfile: None
}; };
if let Some(output) = output { if let Some(output) = output {
@ -128,19 +145,29 @@ pub fn start(output: Option<&str>) {
uci.listen(); uci.listen();
} }
fn take_non_space(i: &str) -> IResult<&str, &str> { fn parse_command(s: &str) -> Option<RemoteCmd> {
take_while(|c| c != ' ')(i) let fields: Vec<&str> = s.split_whitespace().collect();
} match fields[0] {
"uci" => Some(RemoteCmd::Uci),
fn parse_command(i: &str) -> IResult<&str, RemoteCmd> { "isready" => Some(RemoteCmd::IsReady),
let (i, cmd) = take_non_space(i)?; "ucinewgame" => Some(RemoteCmd::UciNewGame),
match cmd { "stop" => Some(RemoteCmd::Stop),
"uci" => Ok((i, RemoteCmd::Uci)), "position" => {
"isready" => Ok((i, RemoteCmd::IsReady)), match fields[1] {
"ucinewgame" => Ok((i, RemoteCmd::UciNewGame)), // Subcommand "fen" is followed by a FEN string.
"stop" => Ok((i, RemoteCmd::Stop)), "fen" => {
"position" => Ok((i, RemoteCmd::Position(i.trim().to_string()))), if let Some(fen) = notation::parse_fen_fields(fields[2..8].to_vec()) {
"quit" => Ok((i, RemoteCmd::Quit)), Some(RemoteCmd::Position(PositionArgs::Fen(fen)))
c => Ok((i, RemoteCmd::Unknown(c.to_string()))), } 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())),
} }
} }