Spawn a separate thread to receive UCI messages via mpsc channel, simplify output to println
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{BufRead, Write},
|
io::{self, BufRead},
|
||||||
str::SplitWhitespace,
|
str::SplitWhitespace,
|
||||||
|
sync::mpsc,
|
||||||
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
@@ -51,12 +53,6 @@ pub enum Response {
|
|||||||
Info(String),
|
Info(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_response(handle_out: &mut impl Write, response: &Response) -> anyhow::Result<()> {
|
|
||||||
writeln!(handle_out, "{response}").map_err(|e| anyhow!(e))?;
|
|
||||||
handle_out.flush().map_err(|e| anyhow!(e))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
impl fmt::Display for Response {
|
impl fmt::Display for Response {
|
||||||
@@ -220,16 +216,24 @@ fn parse_next<T: std::str::FromStr>(iter: &mut SplitWhitespace, val: &str) -> an
|
|||||||
.and_then(|v| v.parse::<T>().map_err(|_| anyhow!("Invalid {val}")))
|
.and_then(|v| v.parse::<T>().map_err(|_| anyhow!("Invalid {val}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result<()> {
|
pub fn uci_loop() -> anyhow::Result<()> {
|
||||||
let mut params = UciParameters::new();
|
let mut params = UciParameters::new();
|
||||||
|
let (uci_sender, uci_receiver) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let input = io::stdin().lock();
|
||||||
for line in input.lines() {
|
for line in input.lines() {
|
||||||
|
uci_sender.send(line).expect("Failed to send line");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Ok(line) = uci_receiver.recv() {
|
||||||
let line_str = line.unwrap_or_else(|_| "quit".to_string());
|
let line_str = line.unwrap_or_else(|_| "quit".to_string());
|
||||||
let mut parts = line_str.split_whitespace();
|
let mut parts = line_str.split_whitespace();
|
||||||
let command = parse_command(&mut parts)?;
|
let command = parse_command(&mut parts)?;
|
||||||
match command {
|
match command {
|
||||||
Command::Uci => write_response(&mut output, &Response::UciOk)?,
|
Command::Uci => println!("{}", Response::UciOk),
|
||||||
Command::IsReady => write_response(&mut output, &Response::ReadyOk)?,
|
Command::IsReady => println!("{}", Response::ReadyOk),
|
||||||
Command::UciNewGame => {
|
Command::UciNewGame => {
|
||||||
if let Some(game) = params.game.as_mut() {
|
if let Some(game) = params.game.as_mut() {
|
||||||
game.tt = TranspositionTable::new();
|
game.tt = TranspositionTable::new();
|
||||||
@@ -243,17 +247,16 @@ pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result
|
|||||||
Command::Go => {
|
Command::Go => {
|
||||||
if let Some(game) = params.game.as_mut() {
|
if let Some(game) = params.game.as_mut() {
|
||||||
match uci_go(&mut parts, game) {
|
match uci_go(&mut parts, game) {
|
||||||
Ok(best_move) => write_response(
|
Ok(best_move) => {
|
||||||
&mut output,
|
println!("{}", Response::BestMove(best_move.parse_into_str()))
|
||||||
&Response::BestMove(best_move.parse_into_str()),
|
}
|
||||||
),
|
Err(e) => println!("{}", Response::Info(e.to_string())),
|
||||||
Err(e) => write_response(&mut output, &Response::Info(e.to_string())),
|
}
|
||||||
}?;
|
|
||||||
} else {
|
} else {
|
||||||
write_response(
|
println!(
|
||||||
&mut output,
|
"{}",
|
||||||
&Response::Info("Failed to unwrap from UciParameter".to_string()),
|
Response::Info("Failed to unwrap from UciParameter".to_string())
|
||||||
)?;
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Command::Stop => break,
|
Command::Stop => break,
|
||||||
@@ -267,8 +270,6 @@ pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
output.flush().map_err(|e| anyhow!(e))?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -276,8 +277,6 @@ pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{fen::from_fen, square::Square},
|
board::{fen::from_fen, square::Square},
|
||||||
interface::uci::{parse_command, Command},
|
interface::uci::{parse_command, Command},
|
||||||
@@ -285,7 +284,6 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::uci_go;
|
use super::uci_go;
|
||||||
use super::uci_loop;
|
|
||||||
use super::uci_position;
|
use super::uci_position;
|
||||||
|
|
||||||
const FEN: [&str; 2] = [
|
const FEN: [&str; 2] = [
|
||||||
@@ -326,28 +324,4 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_uci_loop() -> anyhow::Result<()> {
|
|
||||||
init_attacks();
|
|
||||||
let commands = "uci\n\
|
|
||||||
ucinewgame\n\
|
|
||||||
position fen 8/8/8/8/8/4q1k1/8/5K2 b - - 0 1\n\
|
|
||||||
go\n\
|
|
||||||
quit";
|
|
||||||
let input = Cursor::new(commands);
|
|
||||||
let mut output: Vec<_> = vec![];
|
|
||||||
|
|
||||||
uci_loop(input, &mut output)?;
|
|
||||||
|
|
||||||
let expected_response = "id name zeal\n\
|
|
||||||
id author stefiosif\n\
|
|
||||||
uciok\n\
|
|
||||||
bestmove e3f2\n";
|
|
||||||
let actual_response = String::from_utf8(output).expect("Invalid UTF-8 in output");
|
|
||||||
|
|
||||||
assert_eq!(expected_response, actual_response);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::io;
|
|
||||||
|
|
||||||
mod board;
|
mod board;
|
||||||
mod evaluation;
|
mod evaluation;
|
||||||
mod interface;
|
mod interface;
|
||||||
@@ -28,9 +26,7 @@ fn main() {
|
|||||||
match Args::parse().mode {
|
match Args::parse().mode {
|
||||||
Some(Mode::Perft) => search::perft::perftree_script(),
|
Some(Mode::Perft) => search::perft::perftree_script(),
|
||||||
Some(Mode::Uci) | None => {
|
Some(Mode::Uci) | None => {
|
||||||
let input = io::stdin().lock();
|
uci::uci_loop().unwrap_or_else(|e| println!("{e}"));
|
||||||
let output = io::stdout().lock();
|
|
||||||
uci::uci_loop(input, output).unwrap_or_else(|e| println!("{e}"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use std::io;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::game::Game,
|
board::game::Game,
|
||||||
evaluation::{MAX_SCORE, MIN_SCORE},
|
evaluation::{MAX_SCORE, MIN_SCORE},
|
||||||
interface::uci::{write_response, Response},
|
interface::uci::Response,
|
||||||
movegen::r#move::Move,
|
movegen::r#move::Move,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,10 +17,7 @@ pub fn iterative_deepening(
|
|||||||
|
|
||||||
for depth in 1..=max_depth {
|
for depth in 1..=max_depth {
|
||||||
if time.exceed_soft_limit() {
|
if time.exceed_soft_limit() {
|
||||||
write_response(
|
println!("Soft limit exceeded in negamax");
|
||||||
&mut io::stdout(),
|
|
||||||
&Response::Info("Soft limit exceeded in negamax".to_string()),
|
|
||||||
)?;
|
|
||||||
return Ok(best_move);
|
return Ok(best_move);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +39,7 @@ pub fn iterative_deepening(
|
|||||||
let score = match score {
|
let score = match score {
|
||||||
Ok(score) => score,
|
Ok(score) => score,
|
||||||
Err(ref e) => {
|
Err(ref e) => {
|
||||||
write_response(&mut io::stdout(), &Response::Info(format!("{e}")))?;
|
println!("Error: {}", Response::Info(e.to_string()));
|
||||||
break_from_aw = false;
|
break_from_aw = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -68,46 +63,25 @@ pub fn iterative_deepening(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = score {
|
if let Err(e) = score {
|
||||||
write_response(&mut io::stdout(), &Response::Info(format!("{e}")))?;
|
println!("Error: {}", Response::Info(e.to_string()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
best_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv);
|
best_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv);
|
||||||
|
|
||||||
write_response(
|
println!(
|
||||||
&mut io::stdout(),
|
"info depth {} ms {} nodes {} nps {} eval {} bestmove {}",
|
||||||
&log_depth_results(
|
|
||||||
depth,
|
depth,
|
||||||
time.instant.elapsed().as_secs(),
|
time.instant.elapsed().as_millis(),
|
||||||
nodes,
|
nodes,
|
||||||
time.nps(nodes),
|
time.nps(nodes),
|
||||||
score?,
|
score?,
|
||||||
best_move,
|
best_move.expect("No best move found")
|
||||||
),
|
);
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Ok(best_move)
|
Ok(best_move)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_depth_results(
|
|
||||||
depth: u8,
|
|
||||||
seconds: u64,
|
|
||||||
nodes: u64,
|
|
||||||
nps: u64,
|
|
||||||
best_score: i16,
|
|
||||||
best_move: Option<Move>,
|
|
||||||
) -> Response {
|
|
||||||
Response::Info(format!(
|
|
||||||
"info depth {} seconds {} nodes {} nps {} eval {} bestmove {}",
|
|
||||||
depth,
|
|
||||||
seconds,
|
|
||||||
nodes,
|
|
||||||
nps,
|
|
||||||
best_score,
|
|
||||||
best_move.expect("No best move found")
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|||||||
Reference in New Issue
Block a user