Manage engine go command

This commit is contained in:
Baptiste Fouques 2023-01-30 13:24:22 +01:00
parent d68d7023e1
commit b48f52c41c
4 changed files with 87 additions and 51 deletions

View File

@ -71,9 +71,9 @@ impl<Fi: Read, Fo: Write> UciEngine<Fi, Fo> {
/// Requires line by line input and output streams to communicate with uci engine
pub fn new(source: Option<Fi>, destination: Option<Fo>) -> UciEngine<Fi, Fo> {
UciEngine::<Fi, Fo>{
source: if let Some(source) = source {Some(BufReader::new(source))} else {None},
source: source.map(|x| BufReader::new(x)),
destination,
id: Id::new(),
id: Id::default(),
uciok: false,
initial: Board::default(),
game: Game::new(),
@ -185,7 +185,12 @@ impl<Fi: Read, Fo: Write> UciEngine<Fi, Fo> {
Ok(EngineCommand::Id{id}) => self.update(&id),
Ok(EngineCommand::UciOk) => self.uciok(),
Ok(EngineCommand::Opt { options: _ }) => {},
Ok(_) => {unimplemented!("command not implemented")},
Ok(EngineCommand::Info { infos: _ }) |
Ok(EngineCommand::BestMove { best_move: _, ponder: _ }) |
Ok(EngineCommand::ReadyOk)
=> {},
Ok(EngineCommand::Registration)|
Ok(EngineCommand::CopyProtection)=> {unimplemented!("command not implemented")},
Err(_) => warn!("Not a command"),
};
uci_command
@ -226,10 +231,10 @@ impl<Fi: Read, Fo: Write> UciEngine<Fi, Fo> {
}
/// Push a Uci Gui command to the engine
fn push(&mut self, command: GuiCommand) {
pub fn push(&mut self, command: GuiCommand) {
if let Some(destination) = &mut self.destination {
let command_str = command.to_string();
match destination.write(&command_str.as_bytes()){
match destination.write(command_str.as_bytes()){
Ok(n) if n == command_str.len() => info!("→ gui: {command_str}"),
Ok(n) => warn!("⚠ gui: {command_str} truncated at {n}"),
Err(reason) => warn!("Unable to send command {command_str}: {reason}"),

View File

@ -1,18 +1,12 @@
use itertools::join;
use std::time::Duration;
use std::{time::Duration, str::FromStr};
use std::fmt;
use chess::{ChessMove, Board, Action};
mod uci_command{}
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct Id {name: Option<String>, author: Option<String>}
impl Id {
pub fn new() -> Id {
Id{name: None, author: None}
}
pub fn update(&mut self, id: &Id){
if let Some(name) = &id.name {self.name = Some(name.clone());}
if let Some(author) = &id.author {self.author = Some(author.clone());}
@ -37,6 +31,25 @@ pub struct Info {}
pub enum Opt{
UCILimitStrength{value: bool},
UCIElo{value: u32},
SlowMover{value: u32},
}
impl Opt {
pub fn name(&self) -> &str {
match self {
Opt::UCILimitStrength { .. } => "UCI_LimitStrength",
Opt::UCIElo { .. } => "UCI_Elo",
Opt::SlowMover { .. } => "Slow Mover",
}
}
}
impl fmt::Display for Opt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result{
match self {
Opt::UCILimitStrength { value } => write!(f, "{value}"),
Opt::UCIElo { value } => write!(f, "{value}"),
Opt::SlowMover { value } => write!(f, "{value}"),
}
}
}
#[derive(Clone)]
@ -63,24 +76,12 @@ pub enum GuiCommand {
Position{position: Option<Board>, moves: Vec<Action>},
Go{wtime: Option<Duration>, wincr: Option<Duration>,
btime: Option<Duration>, bincr: Option<Duration>},
SearchMoves{moves: Vec<ChessMove>},
Ponder,
WTime{time: Duration},
BTime{time: Duration},
WInc{increment: Duration},
BInc{increment: Duration},
MovesToGo{number: u16},
Depth{number: u16},
Nodes{number: u16},
Mate{number: u16},
MoveTime{time: Duration},
Infinite,
Stop,
PonderHit,
Quit,
}
pub fn parse(message: &mut String) -> Result<EngineCommand,&'static str> {
pub fn parse(message: &mut str) -> Result<EngineCommand,&'static str> {
let mut message_iter = message.split_whitespace();
match message_iter.next() {
@ -91,16 +92,25 @@ pub fn parse(message: &mut String) -> Result<EngineCommand,&'static str> {
[] => Err("Empty id command"),
["name"] => Err("Empty id name command"),
["author"] => Err("Empty id author command"),
["name", tail @ ..] => Ok(EngineCommand::Id{id: { let mut id = Id::new(); id.update_from_str(Some(&join(tail, " ")), None); id}}),
["author", tail @ ..] => Ok(EngineCommand::Id{id: { let mut id = Id::new(); id.update_from_str(None, Some(&join(tail, " "))); id}}),
["name", tail @ ..] => Ok(EngineCommand::Id{id: { let mut id = Id::default(); id.update_from_str(Some(&join(tail, " ")), None); id}}),
["author", tail @ ..] => Ok(EngineCommand::Id{id: { let mut id = Id::default(); id.update_from_str(None, Some(&join(tail, " "))); id}}),
_ => Err("Invalid id subcommand")
},
Some("uciok") => Ok(EngineCommand::UciOk),
Some("readyok") => unimplemented!(),
Some("bestmove") => unimplemented!(),
Some("bestmove") =>
match message_iter.collect::<Vec<&str>>().as_slice() {
[] => Err("Empty bestmove command"),
[chessmove] => Ok(EngineCommand::BestMove { best_move: ChessMove::from_str(chessmove).expect("chessmove is invalid"), ponder: None }),
[_, "ponder"] => Err("Empty ponder in bestmove command"),
[chessmove, "ponder", chess_ponder] => Ok(EngineCommand::BestMove {
best_move: ChessMove::from_str(chessmove).expect("chessmove is invalid"),
ponder: Some(ChessMove::from_str(chess_ponder).expect("chessmove ponder is invalid")) }),
_ => Err("Invalid chessmove subcommand")
},
Some("copyprotection") => unimplemented!(),
Some("registration") => unimplemented!(),
Some("info") => unimplemented!(),
Some("info") => Ok(EngineCommand::Info { infos: Vec::new() }), //todo!("parse info lines")
Some("option") => Ok(EngineCommand::Opt { options: Vec::new() }), // todo!("Parse options lines")
Some(_) => Err("Unknown command provided"),
}
@ -110,34 +120,35 @@ impl fmt::Display for GuiCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result{
match self {
GuiCommand::Uci => write!(f, "uci\n"),
GuiCommand::UciNewGame => write!(f, "ucinewgame\n"),
GuiCommand::Uci => writeln!(f, "uci"),
GuiCommand::UciNewGame => writeln!(f, "ucinewgame"),
GuiCommand::Position { position, moves } => {
write!(f, "position {} moves {}\n",
match position { None => "startpos".to_string(),
writeln!(f, "position {} moves {}",
match position { None => "startpos".to_string(),
Some(board) if *board == Board::default() => "startpos".to_string(),
Some(board) =>"fen ".to_string() + &board.to_string()},
join(moves.into_iter().map(|x| if let Action::MakeMove(chessmove) = x
{chessmove.to_string()} else {"".to_string()}), "")
join(moves.iter().map(|x| if let Action::MakeMove(chessmove) = x
{" ".to_string() + &chessmove.to_string()} else {"".to_string()}), "")
)
}
GuiCommand::SetOption { option } => {
match option {
Opt::UCILimitStrength { value } => write!(f, "setoption name UCI_LimitStrength value {value}"),
Opt::UCIElo { value } => write!(f,"setoption name UCI_Elo value {value}"),
}
writeln!(f, "setoption name {} value {}", option.name(), option)
}
GuiCommand::Go { wtime, wincr, btime, bincr } => {
GuiCommand::Go { wtime, wincr, btime, bincr } => {
let wtime = if let Some(wtime) = wtime {format!(" wtime {}", wtime.as_millis())} else {"".to_string()};
let btime = if let Some(btime) = btime {format!(" btime {}", btime.as_millis())} else {"".to_string()};
let wincr = if let Some(wincr) = wincr {format!(" wincr {}", wincr.as_millis())} else {"".to_string()};
let bincr = if let Some(bincr) = bincr {format!(" bincr {}", bincr.as_millis())} else {"".to_string()};
write!(f, "go{}{}{}{}", wtime, btime, wincr, bincr)
writeln!(f, "go{}{}{}{}", wtime, btime, wincr, bincr)
}
a => unimplemented!("{:?}", a),
GuiCommand::Stop => writeln!(f, "stop"),
GuiCommand::PonderHit => writeln!(f, "ponderhit"),
GuiCommand::Quit => writeln!(f, "quit"),
GuiCommand::Register|GuiCommand::Debug { .. }|GuiCommand::IsReady
=> unimplemented!("{:?}", self),
}
}
}

View File

@ -3,6 +3,7 @@ extern crate chess_uci;
use chess::ChessMove;
use chess_uci::*;
use chess_uci::uci_command::EngineCommand;
use std::process::{Command, Stdio};
use std::str::FromStr;
@ -23,18 +24,37 @@ pub fn main(){
let (sf_in,sf_out) = (process.stdin.expect("Program stdin"), process.stdout.expect("Program stdout"));
let mut uci = UciEngine::new(Some(sf_out), Some(sf_in));
uci.game_option(GameOption::WhitePlayer { value: Player::Human { elo: None } });
uci.game_option(GameOption::BlackPlayer { value: Player::Machine { elo: Some(1500) } });
uci.init();
println!("Engine: {} \nby: {}",
println!("Engine: {} \nby: {}",
if let Some(name) = uci.name() {name} else {"Not defined".to_string()},
if let Some(author) = uci.author() {author} else {"Not defined".to_string()});
uci.push(uci_command::GuiCommand::SetOption { option: uci_command::Opt::SlowMover { value: 11 }});
uci.new_game();
uci.make_move(ChessMove::from_str("e2e4").expect("error converting e2e4"));
uci.push_raw("d\n");
uci.human_play(ChessMove::from_str("e2e4").expect("error converting e2e4")).expect("can not make human move");
uci.push_raw("d\n");
uci.go().expect("can not make engine move");
loop {
match uci.pull() {
Some(EngineCommand::BestMove { best_move, .. }) => {
uci.make_move(best_move);
break
},
Some(EngineCommand::Info { .. }) => continue,
_ => continue,
}
}
uci.push_raw("d\n");
uci.push_raw("quit\n");
while uci.pull_raw() != None {}
while uci.pull_raw().is_some() {}
}

View File

@ -79,10 +79,10 @@ These are all the command the engine gets from the interface.
to ping the engine to find out if it is still alive.
E.g. this should be sent after setting the path to the tablebases as this can take some time.
This command is also required once before the engine is asked to do any search
to wait for the engine to finish initializing.
$ to wait for the engine to finish initializing.
This command must always be answered with "readyok" and can be sent also when the engine is calculating
in which case the engine should also immediately answer with "readyok" without stopping the search.
$
* setoption name <id> [value <x>]
this is sent to the engine when the user wants to change the internal parameters
of the engine. For the "button" type no value is needed.