shakmaty_uci/
message.rs

1/*
2 * This file is based on vampirc-uci, licenced under the Apache License 2.0
3 * by the vampirc-uci authors.
4 *
5 * Special thanks to Matija Kejžar for their work.
6 */
7
8#[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/// Error when parsing an invalid UCI message.
18#[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/// A representation of a message going to and coming from an UCI engine.
31///
32/// See [Description of the Universal Chess Interface (UCI)] for details about the UCI protocol.
33///
34/// [Description of the Universal Chess Interface (UCI)]: https://gist.github.com/DOBRO/2592c6dad754ba67e6dcaec8c90165bf
35#[must_use]
36#[derive(Clone, Eq, PartialEq, Debug, Hash)]
37pub enum UciMessage {
38    /// The `uci` engine-bound message.
39    Uci,
40
41    /// The `debug` engine-bound message.
42    Debug(bool),
43
44    /// The `isready` engine-bound message.
45    IsReady,
46
47    /// The `register` engine-bound message.
48    Register {
49        /// The `register later` engine-bound message.
50        later: bool,
51
52        /// The name part of the `register name <name> code <code>` engine-bound message.
53        name: Option<String>,
54
55        /// The code part of the `register name <name> code <code>` engine-bound message.
56        code: Option<String>,
57    },
58
59    /// The `position` engine-bound message.
60    Position {
61        /// If `true`, it denotes the starting chess position. Generally, if this property is `true`, then the value of
62        /// the `fen` property will be `None`.
63        startpos: bool,
64
65        /// The [FEN format](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) representation of a chess
66        /// position.
67        fen: Option<Fen>,
68
69        /// A list of moves to apply to the position.
70        moves: Vec<UciMove>,
71    },
72
73    /// The `setoption` engine-bound message.
74    SetOption {
75        /// The name of the option to set.
76        name: String,
77
78        /// The value of the option to set. If the option has no value, this should be `None`.
79        value: Option<String>,
80    },
81
82    /// The `ucinewgame` engine-bound message.
83    UciNewGame,
84
85    /// The `stop` engine-bound message.
86    Stop,
87
88    /// The `ponderhit` engine-bound message.
89    PonderHit,
90
91    /// The `quit` engine-bound message.
92    Quit,
93
94    /// The `go` engine-bound message.
95    Go {
96        /// Time-control-related `go` parameters (sub-commands).
97        time_control: Option<UciTimeControl>,
98
99        /// Search-related `go` parameters (sub-commands).
100        search_control: Option<UciSearchControl>,
101    },
102
103    /// The `id` GUI-bound message.
104    Id {
105        /// The name of the engine, possibly including the version.
106        name: Option<String>,
107
108        /// The name of the author of the engine.
109        author: Option<String>,
110    },
111
112    /// The `uciok` GUI-bound message.
113    UciOk,
114
115    /// The `readyok` GUI-bound message.
116    ReadyOk,
117
118    /// The `bestmove` GUI-bound message.
119    BestMove {
120        /// The move the engine thinks is the best one in the position.
121        best_move: UciMove,
122
123        /// The move the engine would like to ponder on.
124        ponder: Option<UciMove>,
125    },
126
127    /// The `copyprotection` GUI-bound message.
128    CopyProtection(ProtectionState),
129
130    /// The `registration` GUI-bound message.
131    Registration(ProtectionState),
132
133    /// The `option` GUI-bound message.
134    Option(UciOptionConfig),
135
136    /// The `info` GUI-bound message.
137    Info(UciInfo),
138}
139
140impl UciMessage {
141    /// Constructs a [`UciMessage::Register`] message (`register later`)
142    pub fn register_later() -> UciMessage {
143        UciMessage::Register {
144            later: true,
145            name: None,
146            code: None,
147        }
148    }
149
150    /// Constructs a [`UciMessage::Register`] message (`register <code> <name>`)
151    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    /// Constructs an empty [`UciMessage::Go`] message.
160    pub fn go() -> UciMessage {
161        UciMessage::Go {
162            search_control: None,
163            time_control: None,
164        }
165    }
166
167    /// Constructs a [`UciMessage::Go`] message to start calculation in ponder mode (`go ponder`).
168    pub fn go_ponder() -> UciMessage {
169        UciMessage::Go {
170            search_control: None,
171            time_control: Some(UciTimeControl::Ponder),
172        }
173    }
174
175    /// Constructs a [`UciMessage::Go`] message to start infinite calculation (`go infinite`).
176    pub fn go_infinite() -> UciMessage {
177        UciMessage::Go {
178            search_control: None,
179            time_control: Some(UciTimeControl::Infinite),
180        }
181    }
182
183    /// Constructs a [`UciMessage::Go`] message to start calculation for up to `milliseconds` (`go
184    /// movetime <milliseconds>`).
185    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            // GUI-bound from this point on
335            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    /// Parses a UCI message from a line (with or without a terminating newline) and returns a
383    /// [`UciMessage`] or [`ParseUciMessageError`] if message is unknown or invalid.
384    ///
385    /// Usually used in a loop that reads a single line from an input stream, such as the stdin.
386    ///
387    /// # Errors
388    ///
389    /// Returns a [`ParseUciMessageError`] when message cannot be parsed
390    ///
391    /// # Examples
392    ///
393    /// ```
394    /// use shakmaty_uci::{UciMessage};
395    ///
396    /// let msg: UciMessage = "uci\n".parse().unwrap();
397    /// println!("Received message: {}", msg);
398    /// ```
399    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/// This enum represents the possible variants of the `go` UCI message that deal with the chess game's time controls
408/// and the engine's thinking time.
409#[must_use]
410#[derive(Clone, Eq, PartialEq, Debug, Hash)]
411pub enum UciTimeControl {
412    /// The `go ponder` message.
413    Ponder,
414
415    /// The `go infinite` message.
416    Infinite,
417
418    /// The information about the game's time controls.
419    TimeLeft {
420        /// White's time on the clock, in milliseconds.
421        white_time: Option<Duration>,
422
423        /// Black's time on the clock, in milliseconds.
424        black_time: Option<Duration>,
425
426        /// White's increment per move, in milliseconds.
427        white_increment: Option<Duration>,
428
429        /// Black's increment per move, in milliseconds.
430        black_increment: Option<Duration>,
431
432        /// The number of moves to go to the next time control.
433        moves_to_go: Option<u8>,
434    },
435
436    /// Specifies how much time the engine should think about the move, in milliseconds.
437    MoveTime(Duration),
438}
439
440impl UciTimeControl {
441    /// Returns a [`UciTimeControl::TimeLeft`] with all members set to `None`.
442    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/// A struct that controls the engine's (non-time-related) search settings.
454#[must_use]
455#[derive(Clone, Eq, PartialEq, Debug, Hash)]
456pub struct UciSearchControl {
457    /// Limits the search to these moves.
458    pub search_moves: Vec<UciMove>,
459
460    /// Search for mate in this many moves.
461    pub mate: Option<u8>,
462
463    /// Search to this ply depth.
464    pub depth: Option<u8>,
465
466    /// Search no more than this many nodes (positions).
467    pub nodes: Option<u64>,
468}
469
470impl UciSearchControl {
471    /// Creates an `UciSearchControl` with `depth` set to the parameter and everything else set to empty or `None`.
472    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    /// Creates an `UciSearchControl` with `mate` set to the parameter and everything else set to empty or `None`.
482    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    /// Creates an `UciSearchControl` with `nodes` set to the parameter and everything else set to empty or `None`.
492    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    /// Returns `true` if all of the struct's settings are either `None` or empty.
502    #[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    /// Creates an empty `UciSearchControl`.
513    fn default() -> Self {
514        UciSearchControl {
515            search_moves: vec![],
516            mate: None,
517            depth: None,
518            nodes: None,
519        }
520    }
521}
522
523/// Represents the copy protection or registration state.
524#[must_use]
525#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
526pub enum ProtectionState {
527    /// Signifies the engine is checking the copy protection or registration.
528    Checking,
529
530    /// Signifies the copy protection or registration has been validated.
531    Ok,
532
533    /// Signifies error in copy protection or registratin validation.
534    Error,
535}
536
537/// Represents a UCI option definition.
538#[must_use]
539#[derive(Clone, Eq, PartialEq, Debug, Hash)]
540pub enum UciOptionConfig {
541    /// The option of type `check` (a boolean).
542    Check {
543        /// The name of the option.
544        name: String,
545
546        /// The default value of this `bool` property.
547        default: Option<bool>,
548    },
549
550    /// The option of type `spin` (a signed integer).
551    Spin {
552        /// The name of the option.
553        name: String,
554
555        /// The default value of this integer property.
556        default: Option<i64>,
557
558        /// The minimal value of this integer property.
559        min: Option<i64>,
560
561        /// The maximal value of this integer property.
562        max: Option<i64>,
563    },
564
565    /// The option of type `combo` (a list of strings).
566    Combo {
567        /// The name of the option.
568        name: String,
569
570        /// The default value for this list of strings.
571        default: Option<String>,
572
573        /// The list of acceptable strings.
574        var: Vec<String>,
575    },
576
577    /// The option of type `button` (an action).
578    Button {
579        /// The name of the option.
580        name: String,
581    },
582
583    /// The option of type `string` (a string, unsurprisingly).
584    String {
585        /// The name of the option.
586        name: String,
587
588        /// The default value of this string option.
589        default: Option<String>,
590    },
591}
592
593impl UciOptionConfig {
594    /// Returns the name of the option.
595    #[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    /// Returns the type string of the option (ie. `"check"`, `"spin"` ...)
607    #[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                // Do nothing, we're already good
663            }
664        }
665
666        write!(f, "{s}")
667    }
668}
669
670#[must_use]
671#[derive(Clone, Eq, PartialEq, Debug, Hash, Default)]
672pub struct UciInfo {
673    /// The `info depth` message.
674    pub depth: Option<u8>,
675
676    /// The `info seldepth` message.
677    pub sel_depth: Option<u8>,
678
679    /// The `info time` message.
680    pub time: Option<Duration>,
681
682    /// The `info nodes` message.
683    pub nodes: Option<u64>,
684
685    /// The `info pv` message (best line move sequence).
686    pub pv: Vec<UciMove>,
687
688    /// The `info pv ... multipv` message (the pv line number in a multi pv sequence).
689    pub multi_pv: Option<u16>,
690
691    /// The `info score ...` message.
692    pub score: Option<UciInfoScore>,
693
694    /// The `info currmove` message (current move).
695    pub curr_move: Option<UciMove>,
696
697    /// The `info currmovenumber` message (current move number).
698    pub curr_move_num: Option<u16>,
699
700    /// The `info hashfull` message (the occupancy of hashing tables in permills).
701    pub hash_full: Option<u16>,
702
703    /// The `info nps` message (nodes per second).
704    pub nps: Option<u64>,
705
706    /// The `info tbhits` message (end-game table-base hits).
707    pub tb_hits: Option<u64>,
708
709    /// The `info sbhits` message (I guess some Shredder-specific end-game table-base stuff. I dunno, probably best to
710    /// ignore).
711    pub sb_hits: Option<u64>,
712
713    /// The `info cpuload` message (CPU load in permills).
714    pub cpu_load: Option<u16>,
715
716    /// The `info string` message (a string the GUI should display).
717    pub string: Option<String>,
718
719    /// The `info refutation` message (the first move is the move being refuted).
720    pub refutation: Vec<UciMove>,
721
722    /// The `info currline` message (current line being calculated on a CPU).
723    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        /*
807        UciInfoAttribute::Any(_, value) => {
808            s += &format!(" {value}");
809        }
810        */
811
812        write!(f, "{s}")
813    }
814}
815
816#[derive(Clone, Eq, PartialEq, Debug, Hash)]
817pub struct UciInfoCurrLine {
818    /// The CPU number calculating this line.
819    pub cpu_nr: Option<u16>,
820
821    /// The line being calculated.
822    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    /// The score in centipawns.
847    pub cp: Option<i32>,
848
849    /// Mate coming up in this many moves. Negative value means the engine is getting mated.
850    pub mate: Option<i8>,
851
852    /// The value sent is the lower bound.
853    pub lower_bound: bool,
854
855    /// The value sent is the upper bound.
856    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}