1#[allow(unused_imports)]
9use alloc::string::ToString as _;
10use alloc::{string::String, vec::Vec};
11use core::{default::Default, fmt, str::FromStr, time::Duration};
12
13use shakmaty::{fen::Fen, uci::UciMove};
14
15use crate::parser::parse;
16
17#[derive(Clone, Debug, PartialEq)]
19pub struct ParseUciMessageError;
20
21impl fmt::Display for ParseUciMessageError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "invalid UCI message")
24 }
25}
26
27#[cfg(feature = "std")]
28impl std::error::Error for ParseUciMessageError {}
29
30#[must_use]
36#[derive(Clone, Eq, PartialEq, Debug, Hash)]
37pub enum UciMessage {
38 Uci,
40
41 Debug(bool),
43
44 IsReady,
46
47 Register {
49 later: bool,
51
52 name: Option<String>,
54
55 code: Option<String>,
57 },
58
59 Position {
61 startpos: bool,
64
65 fen: Option<Fen>,
68
69 moves: Vec<UciMove>,
71 },
72
73 SetOption {
75 name: String,
77
78 value: Option<String>,
80 },
81
82 UciNewGame,
84
85 Stop,
87
88 PonderHit,
90
91 Quit,
93
94 Go {
96 time_control: Option<UciTimeControl>,
98
99 search_control: Option<UciSearchControl>,
101 },
102
103 Id {
105 name: Option<String>,
107
108 author: Option<String>,
110 },
111
112 UciOk,
114
115 ReadyOk,
117
118 BestMove {
120 best_move: UciMove,
122
123 ponder: Option<UciMove>,
125 },
126
127 CopyProtection(ProtectionState),
129
130 Registration(ProtectionState),
132
133 Option(UciOptionConfig),
135
136 Info(UciInfo),
138}
139
140impl UciMessage {
141 pub fn register_later() -> UciMessage {
143 UciMessage::Register {
144 later: true,
145 name: None,
146 code: None,
147 }
148 }
149
150 pub fn register_code(name: &str, code: &str) -> UciMessage {
152 UciMessage::Register {
153 later: false,
154 name: Some(String::from(name)),
155 code: Some(String::from(code)),
156 }
157 }
158
159 pub fn go() -> UciMessage {
161 UciMessage::Go {
162 search_control: None,
163 time_control: None,
164 }
165 }
166
167 pub fn go_ponder() -> UciMessage {
169 UciMessage::Go {
170 search_control: None,
171 time_control: Some(UciTimeControl::Ponder),
172 }
173 }
174
175 pub fn go_infinite() -> UciMessage {
177 UciMessage::Go {
178 search_control: None,
179 time_control: Some(UciTimeControl::Infinite),
180 }
181 }
182
183 pub fn go_movetime(milliseconds: Duration) -> UciMessage {
186 UciMessage::Go {
187 search_control: None,
188 time_control: Some(UciTimeControl::MoveTime(milliseconds)),
189 }
190 }
191}
192
193impl fmt::Display for UciMessage {
194 #[allow(clippy::too_many_lines)]
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 let uci: String = match self {
197 UciMessage::Debug(on) => {
198 if *on {
199 String::from("debug on")
200 } else {
201 String::from("debug off")
202 }
203 }
204 UciMessage::Register { later, name, code } => {
205 let mut s = String::from("register");
206
207 if *later {
208 s += " later";
209 } else {
210 if let Some(n) = name {
211 s += format!(" name {}", *n).as_str();
212 }
213 if let Some(c) = code {
214 s += format!(" code {}", *c).as_str();
215 }
216 }
217
218 s
219 }
220 UciMessage::Position {
221 startpos,
222 fen,
223 moves,
224 } => {
225 let mut s = String::from("position ");
226 if *startpos {
227 s += "startpos";
228 } else if let Some(fen) = fen {
229 s += format!("fen {fen}").as_str();
230 }
231
232 if !moves.is_empty() {
233 s += " moves";
234
235 for m in moves {
236 s += format!(" {}", *m).as_str();
237 }
238 }
239
240 s
241 }
242 UciMessage::SetOption { name, value } => {
243 let mut s: String = format!("setoption name {name}");
244
245 if let Some(val) = value {
246 if val.is_empty() {
247 s += " value <empty>";
248 } else {
249 s += format!(" value {}", *val).as_str();
250 }
251 } else {
252 s += " value <empty>";
253 }
254
255 s
256 }
257 UciMessage::Go {
258 time_control,
259 search_control,
260 } => {
261 let mut s = String::from("go");
262
263 if let Some(tc) = time_control {
264 match tc {
265 UciTimeControl::Infinite => {
266 s += " infinite";
267 }
268 UciTimeControl::Ponder => {
269 s += " ponder";
270 }
271 UciTimeControl::MoveTime(duration) => {
272 s += format!(" movetime {}", duration.as_millis()).as_str();
273 }
274 UciTimeControl::TimeLeft {
275 white_time,
276 black_time,
277 white_increment,
278 black_increment,
279 moves_to_go,
280 } => {
281 if let Some(wt) = white_time {
282 s += format!(" wtime {}", wt.as_millis()).as_str();
283 }
284
285 if let Some(bt) = black_time {
286 s += format!(" btime {}", bt.as_millis()).as_str();
287 }
288
289 if let Some(wi) = white_increment {
290 s += format!(" winc {}", wi.as_millis()).as_str();
291 }
292
293 if let Some(bi) = black_increment {
294 s += format!(" binc {}", bi.as_millis()).as_str();
295 }
296
297 if let Some(mtg) = moves_to_go {
298 s += format!(" movestogo {}", *mtg).as_str();
299 }
300 }
301 }
302 }
303
304 if let Some(sc) = search_control {
305 if let Some(depth) = sc.depth {
306 s += format!(" depth {depth}").as_str();
307 }
308
309 if let Some(nodes) = sc.nodes {
310 s += format!(" nodes {nodes}").as_str();
311 }
312
313 if let Some(mate) = sc.mate {
314 s += format!(" mate {mate}").as_str();
315 }
316
317 if !sc.search_moves.is_empty() {
318 s += " searchmoves";
319 for m in &sc.search_moves {
320 s += format!(" {m}").as_str();
321 }
322 }
323 }
324
325 s
326 }
327 UciMessage::Uci => String::from("uci"),
328 UciMessage::IsReady => String::from("isready"),
329 UciMessage::UciNewGame => String::from("ucinewgame"),
330 UciMessage::Stop => String::from("stop"),
331 UciMessage::PonderHit => String::from("ponderhit"),
332 UciMessage::Quit => String::from("quit"),
333
334 UciMessage::Id { name, author } => {
336 let mut s = String::from("id ");
337 if let Some(n) = name {
338 s += format!("name {n}").as_str();
339 } else if let Some(a) = author {
340 s += format!("author {a}").as_str();
341 }
342
343 s
344 }
345 UciMessage::UciOk => String::from("uciok"),
346 UciMessage::ReadyOk => String::from("readyok"),
347 UciMessage::BestMove { best_move, ponder } => {
348 let mut s = format!("bestmove {}", *best_move);
349
350 if let Some(p) = ponder {
351 s += format!(" ponder {}", *p).as_str();
352 }
353
354 s
355 }
356 UciMessage::CopyProtection(cp_state) | UciMessage::Registration(cp_state) => {
357 let mut s = match self {
358 UciMessage::CopyProtection(..) => String::from("copyprotection "),
359 UciMessage::Registration(..) => String::from("registration "),
360 _ => unreachable!(),
361 };
362
363 match cp_state {
364 ProtectionState::Checking => s += "checking",
365 ProtectionState::Ok => s += "ok",
366 ProtectionState::Error => s += "error",
367 }
368
369 s
370 }
371 UciMessage::Option(config) => config.to_string(),
372 UciMessage::Info(info) => info.to_string(),
373 };
374
375 write!(f, "{uci}")
376 }
377}
378
379impl FromStr for UciMessage {
380 type Err = ParseUciMessageError;
381
382 fn from_str(line: &str) -> Result<UciMessage, ParseUciMessageError> {
400 match parse(line) {
401 Ok((_, msg)) => Ok(msg),
402 Err(_) => Err(ParseUciMessageError),
403 }
404 }
405}
406
407#[must_use]
410#[derive(Clone, Eq, PartialEq, Debug, Hash)]
411pub enum UciTimeControl {
412 Ponder,
414
415 Infinite,
417
418 TimeLeft {
420 white_time: Option<Duration>,
422
423 black_time: Option<Duration>,
425
426 white_increment: Option<Duration>,
428
429 black_increment: Option<Duration>,
431
432 moves_to_go: Option<u8>,
434 },
435
436 MoveTime(Duration),
438}
439
440impl UciTimeControl {
441 pub fn time_left() -> UciTimeControl {
443 UciTimeControl::TimeLeft {
444 white_time: None,
445 black_time: None,
446 white_increment: None,
447 black_increment: None,
448 moves_to_go: None,
449 }
450 }
451}
452
453#[must_use]
455#[derive(Clone, Eq, PartialEq, Debug, Hash)]
456pub struct UciSearchControl {
457 pub search_moves: Vec<UciMove>,
459
460 pub mate: Option<u8>,
462
463 pub depth: Option<u8>,
465
466 pub nodes: Option<u64>,
468}
469
470impl UciSearchControl {
471 pub fn depth(depth: u8) -> UciSearchControl {
473 UciSearchControl {
474 search_moves: vec![],
475 mate: None,
476 depth: Some(depth),
477 nodes: None,
478 }
479 }
480
481 pub fn mate(mate: u8) -> UciSearchControl {
483 UciSearchControl {
484 search_moves: vec![],
485 mate: Some(mate),
486 depth: None,
487 nodes: None,
488 }
489 }
490
491 pub fn nodes(nodes: u64) -> UciSearchControl {
493 UciSearchControl {
494 search_moves: vec![],
495 mate: None,
496 depth: None,
497 nodes: Some(nodes),
498 }
499 }
500
501 #[must_use]
503 pub fn is_empty(&self) -> bool {
504 self.search_moves.is_empty()
505 && self.mate.is_none()
506 && self.depth.is_none()
507 && self.nodes.is_none()
508 }
509}
510
511impl Default for UciSearchControl {
512 fn default() -> Self {
514 UciSearchControl {
515 search_moves: vec![],
516 mate: None,
517 depth: None,
518 nodes: None,
519 }
520 }
521}
522
523#[must_use]
525#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
526pub enum ProtectionState {
527 Checking,
529
530 Ok,
532
533 Error,
535}
536
537#[must_use]
539#[derive(Clone, Eq, PartialEq, Debug, Hash)]
540pub enum UciOptionConfig {
541 Check {
543 name: String,
545
546 default: Option<bool>,
548 },
549
550 Spin {
552 name: String,
554
555 default: Option<i64>,
557
558 min: Option<i64>,
560
561 max: Option<i64>,
563 },
564
565 Combo {
567 name: String,
569
570 default: Option<String>,
572
573 var: Vec<String>,
575 },
576
577 Button {
579 name: String,
581 },
582
583 String {
585 name: String,
587
588 default: Option<String>,
590 },
591}
592
593impl UciOptionConfig {
594 #[must_use]
596 pub fn get_name(&self) -> &str {
597 match self {
598 UciOptionConfig::Check { name, .. }
599 | UciOptionConfig::Spin { name, .. }
600 | UciOptionConfig::Combo { name, .. }
601 | UciOptionConfig::Button { name }
602 | UciOptionConfig::String { name, .. } => name.as_str(),
603 }
604 }
605
606 #[must_use]
608 pub fn get_type_str(&self) -> &'static str {
609 match self {
610 UciOptionConfig::Check { .. } => "check",
611 UciOptionConfig::Spin { .. } => "spin",
612 UciOptionConfig::Combo { .. } => "combo",
613 UciOptionConfig::Button { .. } => "button",
614 UciOptionConfig::String { .. } => "string",
615 }
616 }
617}
618
619impl fmt::Display for UciOptionConfig {
620 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
621 let mut s = format!(
622 "option name {} type {}",
623 self.get_name(),
624 self.get_type_str()
625 );
626 match self {
627 UciOptionConfig::Check { default, .. } => {
628 if let Some(def) = default {
629 s += format!(" default {}", *def).as_str();
630 }
631 }
632 UciOptionConfig::Spin {
633 default, min, max, ..
634 } => {
635 if let Some(def) = default {
636 s += format!(" default {}", *def).as_str();
637 }
638
639 if let Some(m) = min {
640 s += format!(" min {}", *m).as_str();
641 }
642
643 if let Some(m) = max {
644 s += format!(" max {}", *m).as_str();
645 }
646 }
647 UciOptionConfig::Combo { default, var, .. } => {
648 if let Some(def) = default {
649 s += format!(" default {}", *def).as_str();
650 }
651
652 for v in var {
653 s += format!(" var {}", *v).as_str();
654 }
655 }
656 UciOptionConfig::String { default, .. } => {
657 if let Some(def) = default {
658 s += format!(" default {}", *def).as_str();
659 }
660 }
661 UciOptionConfig::Button { .. } => {
662 }
664 }
665
666 write!(f, "{s}")
667 }
668}
669
670#[must_use]
671#[derive(Clone, Eq, PartialEq, Debug, Hash, Default)]
672pub struct UciInfo {
673 pub depth: Option<u8>,
675
676 pub sel_depth: Option<u8>,
678
679 pub time: Option<Duration>,
681
682 pub nodes: Option<u64>,
684
685 pub pv: Vec<UciMove>,
687
688 pub multi_pv: Option<u16>,
690
691 pub score: Option<UciInfoScore>,
693
694 pub curr_move: Option<UciMove>,
696
697 pub curr_move_num: Option<u16>,
699
700 pub hash_full: Option<u16>,
702
703 pub nps: Option<u64>,
705
706 pub tb_hits: Option<u64>,
708
709 pub sb_hits: Option<u64>,
712
713 pub cpu_load: Option<u16>,
715
716 pub string: Option<String>,
718
719 pub refutation: Vec<UciMove>,
721
722 pub curr_line: Vec<UciInfoCurrLine>,
724}
725
726impl fmt::Display for UciInfo {
727 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
728 let mut s = String::from("info");
729
730 if let Some(depth) = self.depth {
731 s += format!(" depth {depth}").as_str();
732 }
733
734 if let Some(sel_depth) = self.sel_depth {
735 s += format!(" seldepth {sel_depth}").as_str();
736 }
737
738 if let Some(time) = self.time {
739 s += format!(" time {}", time.as_millis()).as_str();
740 }
741
742 if let Some(nodes) = self.nodes {
743 s += format!(" nodes {nodes}").as_str();
744 }
745
746 if !self.pv.is_empty() {
747 s += " pv";
748
749 for m in &self.pv {
750 s += format!(" {m}").as_str();
751 }
752 }
753
754 if !self.refutation.is_empty() {
755 s += " refutation";
756
757 for m in &self.refutation {
758 s += format!(" {m}").as_str();
759 }
760 }
761
762 if let Some(multi_pv) = self.multi_pv {
763 s += format!(" multipv {multi_pv}").as_str();
764 }
765
766 if let Some(score) = &self.score {
767 s += format!(" score {score}").as_str();
768 }
769
770 if let Some(curr_move) = &self.curr_move {
771 s += format!(" currmove {curr_move}").as_str();
772 }
773
774 if let Some(curr_move_num) = self.curr_move_num {
775 s += format!(" currmovenumber {curr_move_num}").as_str();
776 }
777
778 if let Some(hash_full) = self.hash_full {
779 s += format!(" hashfull {hash_full}").as_str();
780 }
781
782 if let Some(nps) = self.nps {
783 s += format!(" nps {nps}").as_str();
784 }
785
786 if let Some(tb_hits) = self.tb_hits {
787 s += format!(" tbhits {tb_hits}").as_str();
788 }
789
790 if let Some(sb_hits) = self.sb_hits {
791 s += format!(" sbhits {sb_hits}").as_str();
792 }
793
794 if let Some(cpu_load) = self.cpu_load {
795 s += format!(" cpuload {cpu_load}").as_str();
796 }
797
798 if let Some(string) = &self.string {
799 s += format!(" string {string}").as_str();
800 }
801
802 for c in &self.curr_line {
803 s += format!(" currline {c}").as_str();
804 }
805
806 write!(f, "{s}")
813 }
814}
815
816#[derive(Clone, Eq, PartialEq, Debug, Hash)]
817pub struct UciInfoCurrLine {
818 pub cpu_nr: Option<u16>,
820
821 pub moves: Vec<UciMove>,
823}
824
825impl fmt::Display for UciInfoCurrLine {
826 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
827 let mut s = String::new();
828
829 if let Some(c) = self.cpu_nr {
830 s += format!(" cpunr {c}").as_str();
831 }
832
833 if !self.moves.is_empty() {
834 for m in &self.moves {
835 s += format!(" {m}").as_str();
836 }
837 }
838
839 write!(f, "{}", s.trim())
840 }
841}
842
843#[must_use]
844#[derive(Clone, Eq, PartialEq, Debug, Hash, Default)]
845pub struct UciInfoScore {
846 pub cp: Option<i32>,
848
849 pub mate: Option<i8>,
851
852 pub lower_bound: bool,
854
855 pub upper_bound: bool,
857}
858
859impl UciInfoScore {
860 pub fn from_centipawns(cp: i32) -> UciInfoScore {
861 UciInfoScore {
862 cp: Some(cp),
863 ..Default::default()
864 }
865 }
866}
867
868impl fmt::Display for UciInfoScore {
869 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
870 let mut s = String::new();
871
872 if let Some(c) = self.cp {
873 s += format!(" cp {c}").as_str();
874 }
875
876 if let Some(m) = self.mate {
877 s += format!(" mate {m}").as_str();
878 }
879
880 if self.lower_bound {
881 s += " lowerbound";
882 } else if self.upper_bound {
883 s += " upperbound";
884 }
885
886 write!(f, "{}", s.trim())
887 }
888}