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]
}
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));
}

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 cli;
pub mod engine;
pub mod notation;
pub mod rules;
pub mod uci;

View file

@ -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<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::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<fs::File>,
}
@ -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<RemoteCmd> {
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())),
}
}