chess_uci/chess_uci/src/lib.rs

191 lines
5.9 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![allow(dead_code)]
/// Manage UCI messages and link into chess component
mod uci_command;
use log::{info, warn};
use std::io::*;
use crate::uci_command::*;
use chess::{Game, Board, ChessMove};
/// Structure used to manage a chess game with a uci engine.
///
/// It needs a in / out communication channel to read / push line by line uci commands
///c
/// ```rust
/// use chess_uci::UciEngine;
/// use std::process::{Command, Stdio};
/// use std::io::BufReader;
///
/// let process = match Command::new("stockfish")
/// .stdin(Stdio::piped())
/// .stdout(Stdio::piped())
/// .spawn() {
/// Err(why) => panic!("couldn't spawn stockfish: {}", why),
/// Ok(process) => process,
/// };
/// let (sf_in,sf_out) = (process.stdin.expect("Program stdin"), process.stdout.expect("Program stdout"));
///
/// let mut uci = UciEngine::new(BufReader::new(sf_out), sf_in);
/// // .. uci.init(); ..
/// uci.push_raw("quit\n");
/// ```
pub struct UciEngine<Fi: BufRead, Fo: Write> {
source: Fi,
destination: Fo,
uciok: bool,
id: Id,
initial: Board,
game: Game,
}
impl<Fi: BufRead, Fo: Write> UciEngine<Fi, Fo> {
/// Create new game manager
///
/// Requires line by line input and output streams to communicate with uci engine
pub fn new(source: Fi, destination: Fo) -> UciEngine<Fi, Fo> {
UciEngine::<Fi, Fo>{
source, destination,
id: Id::new(),
uciok: false,
initial: Board::default(),
game: Game::new()
}
}
/// Launch uci engine initialisation
///
/// Retrieve data from uci engine (until uciok command from engine)
pub fn init(&mut self){
self.push(GuiCommand::Uci);
// Consume commands until uciok messages
while !self.uciok {
self.pull();
}
}
/// Initialize a new game
///
/// Set a new board internally and tell the engine
pub fn new_game(&mut self) {
self.initial = Board::default();
self.game = Game::new_with_board(self.initial);
self.push(GuiCommand::UciNewGame);
}
/// Play a move
///
/// Update internal game reference and tell the uci engine
///
/// ```rust
/// use chess::ChessMove;
/// use std::str::FromStr;
/// use chess_uci::*;
/// use std::io;
///
/// let mut uci = UciEngine::new(io::empty(), io::sink());
///
/// uci.make_move(ChessMove::from_str("e2e4").expect("error converting e2e4"));
/// ```
pub fn make_move(&mut self, chess_move: ChessMove) {
self.game.make_move(chess_move);
self.push(GuiCommand::Position { position: Some(self.initial), moves: self.game.actions().to_vec() })
}
fn terminate(&mut self, reason: &str) {
self.uciok = false;
info!("UCI termination: {}", reason);
}
/// Provides the name of the uci engine
pub fn name(&self) -> Option<String> {
self.id.name()
}
/// Provides the author sof the uci engine
pub fn author(&self) -> Option<String> {
self.id.author()
}
fn update(&mut self, id: &Id){
if self.is_uciok() {warn!("Engine info should not be updated now (uciok)");}
self.id.update(id);
}
fn update_from_str(&mut self, name: Option<&str>, author: Option<&str>){
if self.is_uciok() {warn!("Engine info should not be updated now (uciok)");}
self.id.update_from_str(name, author);
}
fn uciok(&mut self){
self.uciok = true;
}
/// Tell whether the uci engine has initialized
pub fn is_uciok(&self) -> bool {
self.uciok
}
/// Execute a uci command
///
/// if `EngineCommand::Id`: update name or authorship of the engine
/// if `EngineCommand::UciOk`: end engine initialization phase
pub fn exec(&mut self, command: &str){
match parse (&mut command.to_string()) {
Ok(EngineCommand::Id{id}) => self.update(&id),
Ok(EngineCommand::UciOk) => self.uciok(),
Ok(EngineCommand::Opt { options: _ }) => {},
Ok(_) => {unimplemented!("command not implemented")},
Err(_) => warn!("Not a command"),
}
}
/// Retrieve a line from the uci engine input stream and parse it
pub fn pull(&mut self) {
let mut command = String::new();
match self.source.read_line(&mut command) {
Ok(0) => self.terminate("Chess engine closed connection."),
Ok(_) => {
info!("← {}", command);
self.exec(&command)
},
Err(reason) => warn!("Unable to read from engine: {reason}"),
}
}
/// Read a line from the uci engine input stream (do not parse)
pub fn pull_raw(&mut self) -> Option<String> {
let mut data = String::new();
match self.source.read_line(&mut data) {
Ok(0) => {self.terminate("Chess engine closed connection."); None},
Ok(_) => {info!("↜ {}", data); Some(data.clone())},
Err(reason) => {warn!("Unable to read from engine: {reason}"); None},
}
}
/// Push a Uci Gui command to the engine
fn push(&mut self, command: GuiCommand) {
let command_str = command.to_string();
match self.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}"),
}
}
/// Push string (not a game manager integrated command) to the Uci engine output stream
pub fn push_raw(&mut self, data: &str){
match self.destination.write(data.as_bytes()) {
Ok(n) if n == data.len() => info!("↝ raw: {data}"),
Ok(n) => warn!("⚠ raw: {data} truncated at {n}"),
Err(reason) => warn!("Unable to send raw {data}: {reason}"),
}
}
}
// LocalWords: uci