1use crate::parse::{parse_uci, OptionType, UCI};
2use anyhow::{bail, Result};
3use async_trait::async_trait;
4use std::{
5 fmt::Display,
6 process::Stdio,
7 sync::{Arc, Mutex},
8};
9use tokio::{
10 io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
11 process::{Child, ChildStdin, ChildStdout, Command},
12};
13
14#[async_trait]
16pub trait ChessEngine {
17 async fn start_uci(&mut self) -> Result<()>;
19
20 async fn new_game(&mut self) -> Result<()>;
22
23 async fn set_position(&mut self, position: &str) -> Result<()>;
25
26 async fn go_infinite(&mut self) -> Result<()>;
28
29 async fn go_depth(&mut self, plies: usize) -> Result<()>;
31
32 async fn go_time(&mut self, ms: usize) -> Result<()>;
34
35 async fn go_mate(&mut self, mate_in: usize) -> Result<()>;
37
38 async fn stop(&mut self) -> Result<()>;
40
41 async fn get_evaluation(&mut self) -> Option<Evaluation>;
43
44 async fn get_options(&mut self) -> Result<Vec<EngineOption>>;
46
47 async fn set_option(&mut self, option: String, value: String) -> Result<()>;
49}
50
51pub struct Engine {
53 stdin: ChildStdin,
54 state: EngineState,
55 _proc: Child,
56}
57
58impl Engine {
59 pub async fn new(exe_path: &str) -> Result<Self> {
60 let (proc, stdin, stdout) = spawn_process(exe_path)?;
61 let state = EngineState::new(stdout).await;
62 Ok(Engine {
63 state: state,
64 stdin: stdin,
65 _proc: proc,
66 })
67 }
68 async fn send_command(&mut self, command: String) -> Result<()> {
70 self.stdin.write_all(command.as_bytes()).await?;
71 self.stdin.flush().await?;
72 Ok(())
73 }
74
75 async fn _expect_state(&mut self, exp_state: &EngineStateEnum) -> Result<()> {
77 let state = self.state.state.lock().expect("couldn't aquire state lock");
78 if *exp_state == *state {
79 return Ok(());
80 }
81 bail!("engine didn't respond with {:?}", exp_state)
82 }
83
84 async fn expect_state(&mut self, exp_state: EngineStateEnum) -> Result<()> {
87 for _ in 0..10 {
88 match self._expect_state(&exp_state).await {
89 Ok(_) => return Ok(()),
90 Err(_) => tokio::time::sleep(std::time::Duration::from_millis(100)).await,
91 };
92 }
93 bail!("engine didn't respond with {:?}", exp_state)
94 }
95
96 async fn expect_uciok(&mut self) -> Result<()> {
98 self.expect_state(EngineStateEnum::Initialized).await
99 }
100
101 async fn expect_readyok(&mut self) -> Result<()> {
103 self.expect_state(EngineStateEnum::Ready).await
104 }
105
106 async fn set_state(&mut self, new_state: EngineStateEnum) -> Result<()> {
108 let mut state = self.state.state.lock().expect("couldn't acquire lock");
110 *state = new_state;
111 Ok(())
112 }
113}
114
115fn spawn_process(exe_path: &str) -> Result<(Child, ChildStdin, ChildStdout)> {
117 let mut cmd = Command::new(exe_path);
118 cmd.stdin(Stdio::piped());
119 cmd.stdout(Stdio::piped());
120 let mut proc = cmd.spawn()?;
121 let stdout = proc.stdout.take().expect("no stdout available");
122 let stdin = proc.stdin.take().expect("no stdin available");
123 Ok((proc, stdin, stdout))
124}
125
126#[async_trait]
127impl ChessEngine for Engine {
128 async fn start_uci(&mut self) -> Result<()> {
129 self.send_command("uci\n".to_string()).await?;
130 self.expect_uciok().await?;
131 self.send_command("isready\n".to_string()).await?;
132 self.expect_readyok().await?;
133 Ok(())
134 }
135
136 async fn new_game(&mut self) -> Result<()> {
137 self.send_command("ucinewgame\n".to_string()).await?;
138 self.set_state(EngineStateEnum::Initialized).await?;
139 self.send_command("isready\n".to_string()).await?;
140 self.expect_readyok().await?;
141 Ok(())
142 }
143
144 async fn set_position(&mut self, fen: &str) -> Result<()> {
145 let cmd = format!("position fen {}\n", fen);
146 self.send_command(cmd.to_string()).await
147 }
148
149 async fn go_infinite(&mut self) -> Result<()> {
150 self.send_command("go infinite\n".to_string()).await?;
151 self.set_state(EngineStateEnum::Thinking).await?;
152 Ok(())
153 }
154
155 async fn go_depth(&mut self, depth: usize) -> Result<()> {
156 self.send_command(format!("go depth {}\n", depth).to_string())
157 .await?;
158 self.set_state(EngineStateEnum::Thinking).await?;
159 Ok(())
160 }
161
162 async fn go_time(&mut self, ms: usize) -> Result<()> {
163 self.send_command(format!("go movetime {}\n", ms).to_string())
164 .await?;
165 self.set_state(EngineStateEnum::Thinking).await?;
166 Ok(())
167 }
168
169 async fn go_mate(&mut self, mate_in: usize) -> Result<()> {
170 self.send_command(format!("go mate {}\n", mate_in).to_string())
171 .await?;
172 self.set_state(EngineStateEnum::Thinking).await?;
173 Ok(())
174 }
175
176 async fn stop(&mut self) -> Result<()> {
177 self.send_command("stop\n".to_string()).await?;
178 self.set_state(EngineStateEnum::Initialized).await?;
179 Ok(())
180 }
181
182 async fn get_evaluation(&mut self) -> Option<Evaluation> {
183 let ev = self.state.evaluation.lock().expect("couldn't acquire lock");
184 return match &*ev {
185 Some(e) => Some(e.clone()),
186 None => None,
187 };
188 }
189
190 async fn get_options(&mut self) -> Result<Vec<EngineOption>> {
191 let options = self.state.options.lock().expect("couldn't acquire lock");
192 Ok(options.clone())
193 }
194
195 async fn set_option(&mut self, option: String, value: String) -> Result<()> {
196 let cmd = format!("setoption name {} value {}\n", option, value);
197 self.send_command(cmd).await
198 }
199}
200
201#[derive(Debug, Clone, PartialEq)]
203pub struct Evaluation {
204 pub score: isize,
205 pub mate: isize,
206 pub depth: isize,
207 pub nodes: isize,
208 pub seldepth: isize,
209 pub multipv: isize,
210 pub pv: Vec<String>,
211 pub time: isize,
212}
213
214impl Default for Evaluation {
215 fn default() -> Self {
217 Evaluation {
218 score: 0,
219 mate: 0,
220 depth: 0,
221 nodes: 0,
222 seldepth: 0,
223 multipv: 0,
224 pv: vec![],
225 time: 0,
226 }
227 }
228}
229
230impl Display for Evaluation {
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 f.write_fmt(format_args!(
234 "score: {} mate: {} depth: {} nodes: {} seldepth: {} multipv: {} time: {}",
235 self.score, self.mate, self.depth, self.nodes, self.seldepth, self.multipv, self.time
236 ))?;
237 if f.alternate() {
238 f.write_fmt(format_args!("\npv: {}", self.pv.join(", ")))?;
239 }
240 Ok(())
241 }
242}
243
244#[derive(PartialEq, Debug)]
246enum EngineStateEnum {
247 Uninitialized,
248 Initialized,
249 Ready,
250 Thinking,
251}
252
253#[derive(PartialEq, Debug, Clone)]
254pub struct EngineOption {
255 pub name: String,
256 pub opt_type: OptionType,
257}
258
259struct EngineState {
261 state: Arc<Mutex<EngineStateEnum>>,
262 evaluation: Arc<Mutex<Option<Evaluation>>>,
263 options: Arc<Mutex<Vec<EngineOption>>>,
264}
265
266impl EngineState {
267 async fn new(stdout: ChildStdout) -> Self {
268 let ev = Arc::new(Mutex::new(None));
269 let state = Arc::new(Mutex::new(EngineStateEnum::Uninitialized));
270 let options = Arc::new(Mutex::new(Vec::new()));
271 let stdout = BufReader::new(stdout);
272 let engstate = EngineState {
273 state: state.clone(),
274 evaluation: ev.clone(),
275 options: options.clone(),
276 };
277 tokio::spawn(async move {
278 Self::process_stdout(stdout, state.clone(), ev.clone(), options.clone()).await
279 });
280 return engstate;
281 }
282
283 async fn process_stdout(
284 mut stdout: BufReader<ChildStdout>,
285 state: Arc<Mutex<EngineStateEnum>>,
286 ev: Arc<Mutex<Option<Evaluation>>>,
287 options: Arc<Mutex<Vec<EngineOption>>>,
288 ) {
289 loop {
290 let mut str = String::new();
291 stdout.read_line(&mut str).await.unwrap();
292 match parse_uci(str) {
293 Ok(UCI::UciOk) => {
294 let mut state = state.lock().expect("couldn't aquire state lock");
295 *state = EngineStateEnum::Initialized;
296 }
297 Ok(UCI::ReadyOk) => {
298 let mut state = state.lock().expect("couldn't aquire state lock");
299 *state = EngineStateEnum::Ready;
300 }
301 Ok(UCI::Info {
302 cp,
303 mate,
304 depth,
305 nodes,
306 seldepth,
307 time,
308 multipv,
309 pv,
310 }) => {
311 let mut ev = ev.lock().expect("couldn't aquire ev lock");
312 let def_ev = Evaluation::default();
313 let prev_ev = match ev.as_ref() {
314 Some(ev) => ev,
315 None => &def_ev,
316 };
317 *ev = Some(Evaluation {
318 score: cp.unwrap_or(prev_ev.score),
319 mate: mate.unwrap_or(prev_ev.mate),
320 depth: depth.unwrap_or(prev_ev.depth),
321 nodes: nodes.unwrap_or(prev_ev.nodes),
322 seldepth: seldepth.unwrap_or(prev_ev.seldepth),
323 multipv: multipv.unwrap_or(prev_ev.multipv),
324 pv: pv.unwrap_or(prev_ev.pv.clone()),
325 time: time.unwrap_or(prev_ev.time),
326 });
327 }
328 Ok(UCI::Option { name, opt_type }) => {
329 let mut options = options.lock().expect("couldn't aquire options lock");
330 options.push(EngineOption { name, opt_type });
331 }
332 _ => continue,
333 }
334 }
335 }
336}
337
338#[cfg(test)]
339mod test {
340 use anyhow::Result;
341
342 use crate::engine::{ChessEngine, Engine};
343
344 macro_rules! test_file {
345 ($fname:expr) => {
346 concat!(env!("CARGO_MANIFEST_DIR"), "/res/test/", $fname)
347 };
348 }
349
350 #[tokio::test]
351 async fn test_sf() -> Result<()> {
352 let mut sf = Engine::new(test_file!("fakefish.sh")).await?;
353 sf.start_uci().await?;
354 Ok(())
355 }
356}