blunders_engine/
uci.rs

1//! Universal Chess Interface
2
3use std::collections::{HashMap, HashSet};
4use std::convert::TryFrom;
5use std::convert::TryInto;
6use std::fmt::{self, Display, Write};
7use std::hash::{Hash, Hasher};
8use std::io;
9use std::ops::Deref;
10use std::ops::{Index, IndexMut};
11use std::str::{FromStr, SplitWhitespace};
12
13use crate::coretypes::{Move, PlyKind};
14use crate::error::{self, ErrorKind};
15use crate::fen::Fen;
16use crate::movelist::MoveHistory;
17use crate::position::{Game, Position};
18
19/// UciCommands commands from an external program sent to this chess engine.
20#[derive(Debug, Clone, Eq, PartialEq)]
21pub enum UciCommand {
22    Uci,
23    Debug(bool),
24    IsReady,
25    SetOption(RawOption),
26    UciNewGame,
27    Pos(Game),
28    Go(SearchControls),
29    Stop,
30    PonderHit,
31    Quit,
32}
33
34impl UciCommand {
35    /// Parse a single input line into a UciCommand if possible.
36    pub fn parse_command(input_str: &str) -> error::Result<Self> {
37        let mut input = input_str.split_whitespace();
38        let head = input.next().ok_or(ErrorKind::UciNoCommand)?;
39
40        match head {
41            "uci" => Ok(UciCommand::Uci),
42            "debug" => Self::parse_debug(input),
43            "isready" => Ok(UciCommand::IsReady),
44            "setoption" => Self::parse_setoption(input),
45            "ucinewgame" => Ok(UciCommand::UciNewGame),
46            "position" => Self::parse_pos(input),
47            "go" => Self::parse_go(input),
48            "stop" => Ok(UciCommand::Stop),
49            "ponderhit" => Ok(UciCommand::PonderHit),
50            "quit" => Ok(UciCommand::Quit),
51            _ => Err((ErrorKind::UciUnknownCommand, head).into()),
52        }
53    }
54
55    /// Extract a `debug` command if possible.
56    /// command: `debug [on | off]`
57    fn parse_debug(mut input: SplitWhitespace) -> error::Result<Self> {
58        let debug_mode_str = input.next().ok_or(ErrorKind::UciDebugNoMode)?;
59
60        match debug_mode_str {
61            "on" => Ok(Self::Debug(true)),
62            "off" => Ok(Self::Debug(false)),
63            _ => Err(ErrorKind::UciDebugIllegalMode.into()),
64        }
65    }
66
67    /// Extract a `setoption` command if possible.
68    ///command: `setoption name [id] (value x)`
69    fn parse_setoption(mut input: SplitWhitespace) -> error::Result<Self> {
70        let name = input.next().ok_or(ErrorKind::UciSetOptionNoName)?;
71        (name == "name")
72            .then(|| ())
73            .ok_or(ErrorKind::UciSetOptionNoName)?;
74
75        let mut name = String::new();
76        let mut value = String::new();
77        let mut had_value = false;
78
79        // the id following `name` consists of the input string until the token
80        // `value` or end of input is encountered.
81        while let Some(token) = input.next() {
82            if token == "value" {
83                had_value = true;
84                break;
85            } else {
86                name.push_str(token);
87                name.push(' ');
88            }
89        }
90        name.pop(); // Remove trailing space.
91        (name.len() > 0)
92            .then(|| ())
93            .ok_or(ErrorKind::UciSetOptionNoName)?;
94
95        // input iterator is either empty, or "value" has been parsed and the rest
96        // of input is the contents of value string.
97        if had_value {
98            for token in input {
99                value.push_str(token);
100                value.push(' ');
101            }
102            value.pop(); // Remove trailing space.
103            (value.len() > 0)
104                .then(|| ())
105                .ok_or((ErrorKind::UciNoArgument, "expected argument after value"))?;
106        }
107
108        Ok(UciCommand::SetOption(RawOption {
109            name: name.as_str().into(),
110            value,
111        }))
112    }
113
114    /// Extract a `position` command if possible.
115    /// command: `position [fen fen_str | startpos] (moves move_list ...)`
116    fn parse_pos(mut input: SplitWhitespace) -> error::Result<Self> {
117        let position_input = input.next().ok_or((
118            ErrorKind::UciNoArgument,
119            "position missing description [fen | startpos]",
120        ))?;
121
122        // Parse a valid position from startpos or FEN, or return an Err(_).
123        let base_position = match position_input {
124            "startpos" => Ok(Position::start_position()),
125            "fen" => {
126                let mut fen_str = String::new();
127                for _ in 0..6 {
128                    fen_str.push_str(input.next().ok_or(ErrorKind::UciPositionMalformed)?);
129                    fen_str.push(' ');
130                }
131                Position::parse_fen(&fen_str)
132            }
133            _ => return Err(ErrorKind::UciPositionMalformed.into()),
134        }?;
135
136        let mut moves = MoveHistory::new();
137
138        // Check if there is a sequence of moves to apply to the position.
139        if let Some("moves") = input.next() {
140            for move_str in input {
141                moves.push(Move::from_str(move_str)?);
142            }
143        }
144
145        Game::new(base_position, moves).map(|game| UciCommand::Pos(game))
146    }
147
148    /// Extract a `go` command if possible.
149    /// command: `go [wtime | btime | winc | binc | depth | nodes | mate | movetime | infinite]*`
150    fn parse_go(mut input: SplitWhitespace) -> error::Result<Self> {
151        // The following options have no arguments:
152        // ponder, infinite
153        // The following options must be followed with an integer value:
154        // wtime, btime, winc, binc, depth, nodes, mate, movetime, movestogo
155        const HAS_U32_ARG: [&'static str; 9] = [
156            "wtime",
157            "btime",
158            "winc",
159            "binc",
160            "depth",
161            "movestogo",
162            "mate",
163            "movetime",
164            "nodes",
165        ];
166
167        let mut controls = SearchControls::new();
168
169        while let Some(input_str) = input.next() {
170            // Attempt to parse all options with a u32 argument type.
171            if HAS_U32_ARG.contains(&input_str) {
172                let argument: i64 = input
173                    .next()
174                    .ok_or(ErrorKind::UciNoArgument)?
175                    .parse()
176                    .map_err(|err| (ErrorKind::UciCannotParseInt, err))?;
177
178                match input_str {
179                    "wtime" => {
180                        controls.wtime = Some(
181                            argument
182                                .try_into()
183                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
184                        )
185                    }
186                    "btime" => {
187                        controls.btime = Some(
188                            argument
189                                .try_into()
190                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
191                        )
192                    }
193                    "winc" => {
194                        controls.winc = Some(
195                            argument
196                                .try_into()
197                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
198                        )
199                    }
200                    "binc" => {
201                        controls.binc = Some(
202                            argument
203                                .try_into()
204                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
205                        )
206                    }
207                    "depth" => {
208                        controls.depth = Some(
209                            argument
210                                .try_into()
211                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
212                        )
213                    }
214                    "movestogo" => {
215                        controls.moves_to_go = Some(
216                            argument
217                                .try_into()
218                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
219                        )
220                    }
221                    "mate" => {
222                        controls.mate = Some(
223                            argument
224                                .try_into()
225                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
226                        )
227                    }
228                    "movetime" => {
229                        controls.move_time = Some(
230                            argument
231                                .try_into()
232                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
233                        )
234                    }
235                    "nodes" => {
236                        controls.nodes = Some(
237                            argument
238                                .try_into()
239                                .map_err(|err| (ErrorKind::UciCannotParseInt, err))?,
240                        )
241                    }
242                    _ => {
243                        return Err(ErrorKind::UciInvalidOption.into());
244                    }
245                };
246            } else if input_str == "infinite" {
247                controls.infinite = true;
248            } else {
249                return Err(ErrorKind::UciInvalidOption.into());
250            }
251        }
252
253        Ok(UciCommand::Go(controls))
254    }
255}
256
257impl FromStr for UciCommand {
258    type Err = error::Error;
259    fn from_str(s: &str) -> error::Result<Self> {
260        Self::parse_command(s)
261    }
262}
263
264/// Engine to external program communication.
265#[derive(Debug, Clone)]
266pub enum UciResponse {
267    Id(String, String),
268    UciOk,
269    ReadyOk,
270    Opt(UciOption),
271    BestMove(Move),
272    Info(UciInfo),
273}
274
275impl UciResponse {
276    pub fn new_id(name: &str, author: &str) -> Self {
277        Self::Id(name.into(), author.into())
278    }
279
280    pub fn new_option(uci_opt: UciOption) -> Self {
281        Self::Opt(uci_opt)
282    }
283
284    pub fn new_best_move(move_: Move) -> Self {
285        Self::BestMove(move_)
286    }
287
288    pub fn new_info(uci_info: UciInfo) -> Self {
289        Self::Info(uci_info)
290    }
291
292    /// Send this UciResponse over stdout.
293    /// TODO: Allow for writing to files or stdout.
294    pub fn send(&self) -> io::Result<()> {
295        let stdout = io::stdout();
296        let mut handle = stdout.lock();
297        <io::StdoutLock as io::Write>::write_all(&mut handle, self.to_string().as_ref())?;
298        <io::StdoutLock as io::Write>::flush(&mut handle)
299    }
300}
301
302impl Display for UciResponse {
303    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
304        match self {
305            Self::Id(name, author) => {
306                f.write_str("id name ")?;
307                f.write_str(name)?;
308                f.write_char('\n')?;
309                f.write_str("id author ")?;
310                f.write_str(author)?;
311                f.write_char('\n')
312            }
313            Self::UciOk => f.write_str("uciok\n"),
314            Self::ReadyOk => f.write_str("readyok\n"),
315            Self::BestMove(move_) => {
316                f.write_str("bestmove ")?;
317                move_.fmt(f)?;
318                f.write_char('\n')
319            }
320            Self::Opt(uci_opt) => {
321                write!(f, "{}\n", uci_opt)
322            }
323            Self::Info(_info) => {
324                // TODO
325                f.write_str("info string todo\n")
326            }
327        }
328    }
329}
330
331/// Send a debug info string over UCI.
332/// TODO: This is a temporary function until UciInfo and UciResponse are worked out.
333pub fn debug(can_debug: bool, s: &str) -> io::Result<()> {
334    if can_debug {
335        let mut debug_str = String::from("info string debug ");
336        debug_str.push_str(s);
337        debug_str.push('\n');
338
339        let stdout = io::stdout();
340        let mut handle = stdout.lock();
341        <io::StdoutLock as io::Write>::write_all(&mut handle, debug_str.as_ref())?;
342        <io::StdoutLock as io::Write>::flush(&mut handle)
343    } else {
344        Ok(())
345    }
346}
347
348/// Send an error info string over UCI.
349/// TODO: This is a temporary function until UciInfo and UciResponse are worked out.
350pub fn error(s: &str) -> io::Result<()> {
351    let mut error_str = String::from("info string error ");
352    error_str.push_str(s);
353    error_str.push('\n');
354
355    let stdout = io::stdout();
356    let mut handle = stdout.lock();
357    <io::StdoutLock as io::Write>::write_all(&mut handle, error_str.as_ref())?;
358    <io::StdoutLock as io::Write>::flush(&mut handle)
359}
360
361#[derive(Debug, Clone)]
362pub struct UciInfo {}
363
364/// Type parsed from a Uci `setoption` command.
365/// The value is stringly typed, because it can be a string, bool, integer, or nothing.
366#[derive(Debug, Clone, Eq, PartialEq)]
367pub struct RawOption {
368    name: CaselessString,
369    value: String,
370}
371
372#[derive(Debug, Copy, Clone, Eq, PartialEq)]
373pub struct Check {
374    pub value: bool,
375    pub default: bool,
376}
377
378#[derive(Debug, Copy, Clone, Eq, PartialEq)]
379pub struct Spin {
380    pub value: i64,
381    pub default: i64,
382    pub min: i64,
383    pub max: i64,
384}
385
386#[derive(Debug, Clone, Eq, PartialEq)]
387pub struct Combo {
388    pub value: String,
389    pub default: String,
390    pub choices: HashSet<String>,
391}
392
393#[derive(Debug, Copy, Clone, Eq, PartialEq)]
394pub struct Button {
395    pub pressed: bool,
396}
397
398#[derive(Debug, Clone, Eq, PartialEq)]
399pub struct UciOptionString {
400    pub value: String,
401    pub default: String,
402}
403
404impl Spin {
405    /// Spin uses an i64 as its value type because it must cover any sort of numeric input.
406    /// Spin::value<T> allows the value to be converted automatically to the intended type.
407    /// This panics if the type cannot convert.
408    pub fn value<T: TryFrom<i64>>(&self) -> T {
409        match T::try_from(self.value) {
410            Ok(converted) => converted,
411            _ => panic!("spin value TryFrom<i64> conversion failed"),
412        }
413    }
414}
415
416#[derive(Debug, Clone, Eq, PartialEq)]
417pub enum UciOptionType {
418    Check(Check),
419    Spin(Spin),
420    Combo(Combo),
421    Button(Button),
422    String(UciOptionString),
423}
424
425impl Display for UciOptionType {
426    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
427        match self {
428            UciOptionType::Check(Check { default, .. }) => {
429                write!(f, "type check default {}", default)
430            }
431            UciOptionType::Spin(Spin {
432                default, min, max, ..
433            }) => {
434                write!(f, "type spin default {} min {} max {}", default, min, max)
435            }
436            UciOptionType::Combo(Combo {
437                default, choices, ..
438            }) => {
439                write!(f, "type combo default {}", default)?;
440                for choice in choices {
441                    write!(f, " var {}", choice)?;
442                }
443                Ok(())
444            }
445            UciOptionType::Button(_) => f.write_str("type button"),
446            UciOptionType::String(UciOptionString { default, .. }) => {
447                write!(f, "type string default {}", default)
448            }
449        }
450    }
451}
452
453/// Options to allow:
454/// option name Hash type spin default 1 min 1 max 16000
455/// option name Clear Hash type button
456/// option name Ponder type check default false
457/// option name Threads type spin default 1 min 1 max 32
458#[derive(Debug, Clone, Eq, PartialEq)]
459pub struct UciOption {
460    pub name: CaselessString,
461    pub option_type: UciOptionType,
462}
463
464impl UciOption {
465    /// Create a new UciOption of type check, with a default value.
466    pub fn new_check(name: &str, default: bool) -> Self {
467        Self {
468            name: name.into(),
469            option_type: UciOptionType::Check(Check {
470                value: default,
471                default,
472            }),
473        }
474    }
475
476    /// Create a new UciOption of type spin with a default value, and a min and max.
477    pub fn new_spin(name: &str, default: i64, min: i64, max: i64) -> Self {
478        assert!(min < max, "Illegal spin, min >= max");
479        assert!(default >= min, "Illegal spin, default < min");
480        assert!(default <= max, "Illegal spin, default > max");
481
482        Self {
483            name: name.into(),
484            option_type: UciOptionType::Spin(Spin {
485                value: default,
486                default,
487                min,
488                max,
489            }),
490        }
491    }
492
493    /// Create a new UciOption of type combo with a default value and a list of choices.
494    /// Default value must be a member of choices, including capitalization, but
495    /// ignoring whitespace.
496    pub fn new_combo(name: &str, default: &str, choices: &[&str]) -> Self {
497        let default = default.trim().to_string();
498        let choices: HashSet<String> = choices.iter().map(|s| s.trim().to_string()).collect();
499
500        // Assert that default is a legal choice in a case insensitive comparison.
501        assert!(matches!(
502            choices
503                .iter()
504                .find(|item| item.to_lowercase() == default.to_lowercase()),
505            Some(_)
506        ));
507
508        Self {
509            name: name.into(),
510            option_type: UciOptionType::Combo(Combo {
511                value: default.clone(),
512                default,
513                choices,
514            }),
515        }
516    }
517
518    /// Create a new UciOption of type button with a default state of pressed or not pressed.
519    pub fn new_button(name: &str, pressed: bool) -> Self {
520        Self {
521            name: name.into(),
522            option_type: UciOptionType::Button(Button { pressed }),
523        }
524    }
525
526    /// Create a new UciOption of type string with a default value.
527    pub fn new_string(name: &str, default: &str) -> Self {
528        Self {
529            name: name.into(),
530            option_type: UciOptionType::String(UciOptionString {
531                value: default.trim().to_string(),
532                default: default.trim().to_string(),
533            }),
534        }
535    }
536
537    /// Assume that a UciOption is of type Check, and return reference to inner Check struct.
538    /// Panics if UciOption is not Check.
539    pub fn check(&self) -> &Check {
540        match self.option_type {
541            UciOptionType::Check(ref check) => check,
542            _ => panic!("option type is not check"),
543        }
544    }
545    /// Assume that a UciOption is of type Spin, and return reference to inner Spin struct.
546    /// Panics if UciOption is not Spin.
547    pub fn spin(&self) -> &Spin {
548        match self.option_type {
549            UciOptionType::Spin(ref spin) => spin,
550            _ => panic!("option type is not spin"),
551        }
552    }
553    /// Assume that a UciOption is of type Combo, and return reference to inner Combo struct.
554    /// Panics if UciOption is not Combo.
555    pub fn combo(&self) -> &Combo {
556        match self.option_type {
557            UciOptionType::Combo(ref combo) => combo,
558            _ => panic!("option type is not combo"),
559        }
560    }
561    /// Assume that a UciOption is of type Button, and return reference to inner Button struct.
562    /// Panics if UciOption is not Button.
563    pub fn button(&self) -> &Button {
564        match self.option_type {
565            UciOptionType::Button(ref button) => button,
566            _ => panic!("option type is not button"),
567        }
568    }
569    /// Assume that a UciOption is of type String, and return reference to inner String struct.
570    /// Panics if UciOption is not String.
571    pub fn string(&self) -> &UciOptionString {
572        match self.option_type {
573            UciOptionType::String(ref s) => s,
574            _ => panic!("option type is not String"),
575        }
576    }
577
578    /// Assume that a UciOption is of type Check, and return reference to inner Check struct.
579    /// Panics if UciOption is not Check.
580    pub fn check_mut(&mut self) -> &mut Check {
581        match self.option_type {
582            UciOptionType::Check(ref mut check) => check,
583            _ => panic!("option type is not check"),
584        }
585    }
586    /// Assume that a UciOption is of type Spin, and return reference to inner Spin struct.
587    /// Panics if UciOption is not Spin.
588    pub fn spin_mut(&mut self) -> &mut Spin {
589        match self.option_type {
590            UciOptionType::Spin(ref mut spin) => spin,
591            _ => panic!("option type is not spin"),
592        }
593    }
594    /// Assume that a UciOption is of type Combo, and return reference to inner Combo struct.
595    /// Panics if UciOption is not Combo.
596    pub fn combo_mut(&mut self) -> &mut Combo {
597        match self.option_type {
598            UciOptionType::Combo(ref mut combo) => combo,
599            _ => panic!("option type is not combo"),
600        }
601    }
602    /// Assume that a UciOption is of type Button, and return reference to inner Button struct.
603    /// Panics if UciOption is not Button.
604    pub fn button_mut(&mut self) -> &mut Button {
605        match self.option_type {
606            UciOptionType::Button(ref mut button) => button,
607            _ => panic!("option type is not button"),
608        }
609    }
610    /// Assume that a UciOption is of type String, and return reference to inner String struct.
611    /// Panics if UciOption is not String.
612    pub fn string_mut(&mut self) -> &mut UciOptionString {
613        match self.option_type {
614            UciOptionType::String(ref mut s) => s,
615            _ => panic!("option type is not String"),
616        }
617    }
618
619    /// Given a RawOption, try to extract a typed value from it's stringly-typed value.
620    /// The type of the parsed value must match the value of this UciOptionType value.
621    /// This returns a mutable reference to self on successful update.
622    pub fn try_update(&mut self, raw_opt: &RawOption) -> error::Result<&mut Self> {
623        (self.name == raw_opt.name)
624            .then(|| ())
625            .ok_or((ErrorKind::UciOptionCannotUpdate, "names do not match"))?;
626
627        match self.option_type {
628            UciOptionType::Check(Check { ref mut value, .. }) => {
629                *value = bool::from_str(&raw_opt.value)
630                    .map_err(|err| (ErrorKind::UciOptionCannotUpdate, err))?;
631            }
632            UciOptionType::Spin(Spin {
633                ref mut value,
634                min,
635                max,
636                ..
637            }) => {
638                let new_value = i64::from_str_radix(&raw_opt.value, 10)
639                    .map_err(|err| (ErrorKind::UciOptionCannotUpdate, err))?;
640                (min..=max)
641                    .contains(&new_value)
642                    .then(|| ())
643                    .ok_or((ErrorKind::UciOptionCannotUpdate, "value out of range"))?;
644                *value = new_value;
645            }
646            UciOptionType::Combo(Combo {
647                ref mut value,
648                ref choices,
649                ..
650            }) => {
651                choices
652                    .contains(&raw_opt.value)
653                    .then(|| ())
654                    .ok_or((ErrorKind::UciOptionCannotUpdate, "value not a valid choice"))?;
655                *value = raw_opt.value.clone();
656            }
657            UciOptionType::Button(Button { ref mut pressed }) => *pressed = true,
658            UciOptionType::String(UciOptionString { ref mut value, .. }) => {
659                *value = raw_opt.value.clone()
660            }
661        };
662
663        Ok(self)
664    }
665}
666
667impl Display for UciOption {
668    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
669        write!(f, "option name {} {}", self.name.0, self.option_type)
670    }
671}
672
673/// CaselessString is a String wrapper that compares and hashes a string with
674/// ignored casing and leading/trailing whitespace.
675/// It retains casing for printing, and removes leading/trailing whitespace.
676#[derive(Debug, Clone)]
677pub struct CaselessString(String);
678
679impl PartialEq for CaselessString {
680    fn eq(&self, other: &Self) -> bool {
681        self.0.to_lowercase() == other.0.to_lowercase()
682    }
683}
684impl Eq for CaselessString {}
685
686impl Hash for CaselessString {
687    fn hash<H: Hasher>(&self, state: &mut H) {
688        self.0.to_lowercase().hash(state);
689    }
690}
691
692impl PartialEq<&str> for CaselessString {
693    fn eq(&self, other: &&str) -> bool {
694        *self == Self::from(*other)
695    }
696}
697
698impl Deref for CaselessString {
699    type Target = String;
700    fn deref(&self) -> &Self::Target {
701        &self.0
702    }
703}
704
705impl From<&str> for CaselessString {
706    fn from(s: &str) -> Self {
707        Self(s.trim().to_string())
708    }
709}
710
711/// Underlying type for UciOptions.
712type OptionsMap = HashMap<CaselessString, UciOption>;
713
714/// A HashMap wrapper for UciOption that has extra functionality for UciOption.
715/// An option can only be updated with an option of equivalent type.
716pub struct UciOptions(OptionsMap);
717
718impl UciOptions {
719    /// Create a new UciOptions using underlying HashMap::new().
720    pub fn new() -> Self {
721        Self(OptionsMap::new())
722    }
723
724    /// Insert stores a UciOption using it's name as the key and the full item as the value.
725    /// It always replaces what is located in the container completely.
726    /// If an item existed in the container, the item is removed and returned.
727    pub fn insert(&mut self, uci_opt: UciOption) -> Option<UciOption> {
728        let key = uci_opt.name.clone();
729        // Remove key before inserting ensures Key capitalization is updated.
730        let old_value = self.0.remove(&key);
731        self.0.insert(key, uci_opt);
732        old_value
733    }
734
735    /// UciOptions are uniquely defined by their name. Returns true if a key exists.
736    pub fn contains<K: Into<CaselessString>>(&self, key: K) -> bool {
737        let key: CaselessString = key.into();
738        self.0.contains_key(&key)
739    }
740
741    /// Attempts to update a stored UciOption with the value in a RawOption.
742    /// This will not create a new UciOption entry.
743    /// This returns a mutable reference to the updated value in the table on successful update.
744    pub fn update(&mut self, raw_opt: &RawOption) -> error::Result<&mut UciOption> {
745        self.0
746            .get_mut(&raw_opt.name)
747            .ok_or((
748                ErrorKind::UciOptionCannotUpdate,
749                "RawOption name not a valid UciOption",
750            ))?
751            .try_update(&raw_opt)
752    }
753}
754
755impl<K: Into<CaselessString>> Index<K> for UciOptions {
756    type Output = UciOption;
757    fn index(&self, key: K) -> &Self::Output {
758        let key: CaselessString = key.into();
759        &self.0[&key]
760    }
761}
762
763impl<K: Into<CaselessString>> IndexMut<K> for UciOptions {
764    fn index_mut(&mut self, key: K) -> &mut Self::Output {
765        let key: CaselessString = key.into();
766        self.0.get_mut(&key).expect("key not present")
767    }
768}
769
770impl Deref for UciOptions {
771    type Target = OptionsMap;
772    fn deref(&self) -> &Self::Target {
773        &self.0
774    }
775}
776
777#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
778pub struct SearchControls {
779    pub wtime: Option<i32>,
780    pub btime: Option<i32>,
781    pub winc: Option<u32>,
782    pub binc: Option<u32>,
783    pub moves_to_go: Option<u32>,
784    pub depth: Option<PlyKind>,
785    pub nodes: Option<u64>,
786    pub mate: Option<u32>,
787    pub move_time: Option<u32>,
788    pub infinite: bool,
789}
790
791impl SearchControls {
792    pub fn new() -> Self {
793        Self::default()
794    }
795}
796
797impl Default for SearchControls {
798    fn default() -> Self {
799        Self {
800            wtime: None,
801            btime: None,
802            winc: None,
803            binc: None,
804            moves_to_go: None,
805            depth: None,
806            nodes: None,
807            mate: None,
808            move_time: None,
809            infinite: false,
810        }
811    }
812}
813
814#[cfg(test)]
815mod tests {
816    use super::*;
817    use crate::coretypes::Square::*;
818
819    /// Tests commands: uci, isready, ucinewgame, stop, ponderhit, quit
820    #[test]
821    fn parse_command_singles() {
822        {
823            let input = "uci";
824            let command = UciCommand::parse_command(&input);
825            assert_eq!(UciCommand::Uci, command.unwrap());
826        }
827        {
828            let input = "isready\n";
829            let command = UciCommand::parse_command(&input);
830            assert_eq!(UciCommand::IsReady, command.unwrap());
831        }
832        {
833            let input = "ucinewgame";
834            let command = UciCommand::parse_command(&input);
835            assert_eq!(UciCommand::UciNewGame, command.unwrap());
836        }
837        {
838            let input = "stop";
839            let command = UciCommand::parse_command(&input);
840            assert_eq!(UciCommand::Stop, command.unwrap());
841        }
842        {
843            let input = "ponderhit";
844            let command = UciCommand::parse_command(&input);
845            assert_eq!(UciCommand::PonderHit, command.unwrap());
846        }
847        {
848            let input = "quit";
849            let command = UciCommand::parse_command(&input);
850            assert_eq!(UciCommand::Quit, command.unwrap());
851        }
852    }
853
854    #[test]
855    fn parse_command_debug() {
856        let on = "debug on";
857        let off = "debug off";
858        let command_on = UciCommand::parse_command(on);
859        let command_off = UciCommand::parse_command(off);
860        assert_eq!(UciCommand::Debug(true), command_on.unwrap());
861        assert_eq!(UciCommand::Debug(false), command_off.unwrap());
862    }
863
864    #[test]
865    fn parse_command_setoption() {
866        {
867            let input = "setoption name Hash value 100\n";
868            let command = UciCommand::parse_command(input);
869            let raw_opt = RawOption {
870                name: "hash".into(),
871                value: String::from("100"),
872            };
873            assert_eq!(UciCommand::SetOption(raw_opt), command.unwrap());
874        }
875        {
876            let input = "setoption name Multi Word Name value this is a test string.c";
877            let command = UciCommand::parse_command(input);
878            let raw_opt = RawOption {
879                name: "Multi Word Name".into(),
880                value: String::from("this is a test string.c"),
881            };
882            assert_eq!(UciCommand::SetOption(raw_opt), command.unwrap());
883        }
884        {
885            let input = "setoption name Clear Hash \n";
886            let command = UciCommand::parse_command(input);
887            let raw_opt = RawOption {
888                name: "Clear Hash".into(),
889                value: String::from(""),
890            };
891            assert_eq!(UciCommand::SetOption(raw_opt), command.unwrap());
892        }
893    }
894
895    #[test]
896    fn parse_command_pos() {
897        {
898            // Simple start position.
899            let start_position = Game::new(Position::start_position(), MoveHistory::new()).unwrap();
900            let command_start_str = "position startpos";
901            let command_start1 = UciCommand::parse_command(command_start_str).unwrap();
902            assert_eq!(UciCommand::Pos(start_position), command_start1);
903        }
904
905        {
906            // Derived from applying moves to start position.
907            let mut moves = MoveHistory::new();
908            moves.push(Move::new(D2, D4, None));
909            moves.push(Move::new(D7, D5, None));
910            let base_pos = Position::start_position();
911            let mut final_pos = base_pos.clone();
912
913            moves.iter().for_each(|move_| {
914                final_pos.do_move(*move_);
915            });
916
917            let game = Game::new(base_pos, moves).unwrap();
918            let game_position = game.position.clone();
919
920            let command_start_moves_str = "position startpos moves d2d4 d7d5";
921            let command = UciCommand::parse_command(command_start_moves_str).unwrap();
922            assert_eq!(UciCommand::Pos(game), command);
923            assert_eq!(game_position, final_pos);
924        }
925
926        {
927            // Positions derived from a fen.
928            let pos_fen_str = "rnbqkbnr/pppp1ppp/8/4P3/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 2";
929            let command_str =
930                "position fen rnbqkbnr/pppp1ppp/8/4P3/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 2";
931            let pos = Position::parse_fen(pos_fen_str).unwrap();
932            let game = Game::new(pos, MoveHistory::new()).unwrap();
933            let game_position = game.position;
934            let command = UciCommand::parse_command(command_str).unwrap();
935
936            assert_eq!(UciCommand::Pos(game), command);
937            assert_eq!(game_position, pos);
938        }
939
940        {
941            // Derive from a fen string with moves applied.
942            let base_fen_str = "rnbqkbnr/pppp1ppp/8/4P3/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 2";
943            let post_fen_str = "rnbqkbnr/ppp2ppp/3P4/8/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 3";
944            let command_str = "position fen rnbqkbnr/pppp1ppp/8/4P3/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 2 moves d7d6 e5d6";
945            let pos_base = Position::parse_fen(base_fen_str).unwrap();
946            let pos_post = Position::parse_fen(post_fen_str).unwrap();
947            let mut moves = MoveHistory::new();
948            moves.push(Move::new(D7, D6, None));
949            moves.push(Move::new(E5, D6, None));
950
951            let game = Game::new(pos_base, moves).unwrap();
952
953            let command = UciCommand::parse_command(command_str).unwrap();
954            println!("pos: {}", pos_post);
955
956            if let UciCommand::Pos(ref inner_game) = command {
957                println!("com: {:?}", inner_game);
958            };
959            let game_position = game.position;
960            let game_base_position = game.base_position;
961            assert_eq!(UciCommand::Pos(game), command);
962            assert_eq!(game_position, pos_post);
963            assert_eq!(game_base_position, pos_base);
964        }
965    }
966
967    #[test]
968    fn parse_command_go() {
969        {
970            let input = "go depth 10 wtime 40000 \n";
971            let command = UciCommand::parse_command(input).unwrap();
972            let mut search_ctrl = SearchControls::new();
973            search_ctrl.depth = Some(10);
974            search_ctrl.wtime = Some(40000);
975            assert_eq!(UciCommand::Go(search_ctrl), command);
976        }
977    }
978
979    #[test]
980    fn ucioptions_insert_update_contains() {
981        // option name Hash type spin default 1 min 1 max 16000
982        // option name Clear Hash type button
983        // option name Ponder type check default false
984        // option name Threads type spin default 1 min 1 max 32
985        let option_hash = UciOption::new_spin("Hash", 1, 1, 16000);
986        let option_clear_hash = UciOption::new_button("Clear Hash", false);
987        let option_ponder = UciOption::new_check("Ponder", false);
988        let option_threads = UciOption::new_spin("Threads", 1, 1, 32);
989
990        let mut uci_options = UciOptions::new();
991
992        assert_eq!(uci_options.len(), 0);
993        assert_eq!(uci_options.insert(option_hash.clone()), None);
994        assert_eq!(uci_options.insert(option_clear_hash.clone()), None);
995        assert_eq!(uci_options.insert(option_ponder.clone()), None);
996        assert_eq!(uci_options.insert(option_threads.clone()), None);
997        assert_eq!(uci_options.len(), 4);
998
999        let raw_hash = RawOption {
1000            name: "hash".into(),
1001            value: "14".into(),
1002        };
1003        assert!(matches!(uci_options.update(&raw_hash), Ok(_)));
1004
1005        assert_eq!(
1006            option_clear_hash,
1007            *uci_options.get(&"clear hash".into()).unwrap()
1008        );
1009        assert_eq!(option_ponder, *uci_options.get(&"ponder".into()).unwrap());
1010        assert_eq!(option_threads, *uci_options.get(&"threads".into()).unwrap());
1011        assert_ne!(option_hash, *uci_options.get(&"hash".into()).unwrap());
1012    }
1013}