haitaka_usi/
engine.rs

1//! This module contains the data model for EngineMessage, the commands sent from Engine to GUI.
2//! The main enum is [`EngineMessage`] which encodes all messages sent from a Shogi engine to the GUI.
3//!
4//! For full documenation about the protocol see
5//! - [将棋所USIプロトコル](https://shogidokoro2.stars.ne.jp/usi.html)
6//! - [The Universal Shogi Interface](http://hgm.nubati.net/usi.html)
7use crate::format_vec;
8use haitaka_types::Move;
9use std::fmt;
10use std::time::Duration;
11
12/// Messages sent from the Shogi Engine to the GUI.
13#[derive(Clone, Eq, PartialEq, Debug, Hash)]
14pub enum EngineMessage {
15    /// `id` - the `id` message informs the GUI about the engine name and engine
16    /// developer. This message is sent as initial response to the GUI `usi` message.
17    /// ```text
18    /// id name haitaka-shogi
19    /// id author tofutofu
20    /// ```
21    Id(IdParams),
22
23    /// `usiok` - sent to finalize the initial handshake between GUI and engine. This
24    /// message is sent after the `id` and initial `option` messages.
25    UsiOk,
26
27    /// `readyok` - sent in response to the `isready` message to inform the GUI that
28    /// the engine is ready to start a search.
29    ReadyOk,
30
31    /// `bestmove` - sent in response to a `go` command, to inform the GUI that the engine
32    /// has stopped searching and found a good move. The engine can also use this command
33    /// to resign or claim a win.
34    /// ```text
35    /// bestmove <move>
36    /// bestmove <move> ponder <move>
37    /// bestmove resign
38    /// bestmove win
39    /// ```
40    BestMove(BestMoveParams),
41
42    /// `checkmate` - sent as termination of a `go mate` command.
43    /// ```text
44    /// checkmate <moves> - a forced mate principal sequence of moves (sokuzumi)
45    /// checkmate nomate - the engine was able to prove there is no forced mate
46    /// checkmate timeout - the search timed out inconclusively
47    /// checkmate notimplemented - the engine does not implement tsume shogi search
48    /// ```
49    CheckMate(CheckMateParams),
50
51    /// `copyprotection` - sent by engines that check copy protection:
52    /// ```text
53    /// copyprotection error
54    /// copyprotection checking
55    /// copyprotection ok
56    /// ```
57    CopyProtection(StatusCheck),
58
59    /// `registration` - sent by engines that may require the user (GUI) to register
60    /// by name and registration code.
61    /// ```text
62    /// registration error
63    /// registration checking
64    /// registration ok
65    /// ```
66    Registration(StatusCheck),
67
68    /// `option` - informs the GUI about available engine options. Options are
69    /// distinguished by type and name. Examples:
70    /// ```text
71    /// option name UseBook type check default true
72    /// option name Selectivity type spin default 2 min 0 max 4
73    /// option name Style type combo default Normal var Solid var Normal var Risky
74    /// option name ResetLearning type button
75    /// option name BookFile type string default public.bin
76    /// option name LearningFile type filename default <empty>
77    /// ```
78    /// Available engine options are sent by the engine in response to the `usi` command.
79    /// The GUI can in that case respond by modifying an option with a `setoption` message.
80    /// Option names can never have spaces. Certain names have fixed semantics:
81    /// ```text
82    /// - USI_Hash type spin - MB memory to use for hash tables
83    /// - USI_Ponder type check - when set, the engine is allowed to "ponder" (think during opponent's time)
84    /// - USI_OwnBook type check - the engine has its own opening book
85    /// - USI_Multipv type spin - the engine supports multi bestline mode (default is 1)
86    /// - USI_ShowCurrLine type check - the engine can show the current line while searching (false by default)
87    /// - USI_ShowRefutations type check - the engine can show a move and its refutation (false by default)
88    /// - USI_LimitStrength type check - the engine can adjust its strength (false by default)
89    /// - USI_Strength type spin - the engine plays at the indicated strenght level (negative values
90    /// represent kyu levels, positive values dan levels), requires USI_LimitStrength to be set
91    /// - USI_AnalyseMode type check - the engine may behave differently when analysing or playing a game
92    /// ```
93    /// The `USI_Hash` and especially the `USI_Ponder` options should always be supported. Note that even
94    /// when the ponder option is available and enabled, the engine should still only start pondering when
95    /// it receives a `go ponder` command.
96    ///
97    /// Engines may only support a subset of options. For details, please consult the engine documentation.
98    Option(OptionParam),
99
100    /// `info` - informs the GUI, during the search, about the status of the search. The engine may send
101    /// either selected `info` messages or multiple infos in one message. All infos about the principal
102    /// variation should be sent in one message. Infos about multipv should be sent in successive
103    /// lines. Examples:
104    /// ```text
105    /// info time 1141 depth 3 nodes 135125 score cp -1521 pv 3a3b L*4h 4c4d
106    /// info depth 2 score cp 214 time 1242 nodes 2124 nps 34928 pv 2g2f 8c8d 2f2e
107    /// info nodes 120000 nps 116391 hashfull 104
108    /// info string 7g7f (70%)
109    /// info score cp 156 multipv 1 pv P*5h 4g5g 5h5g 8b8f
110    /// info score cp -99 multipv 2 pv 2d4d 3c4e 8h5e N*7f
111    /// info score cp -157 multipv 3 pv 5g5f 4g4f 4e3c+ 4c3c
112    /// ```
113    Info(Vec<InfoParam>),
114
115    /// This variant is a catch-all for messages that do not conform to the USI protocol.
116    Unknown(String),
117}
118
119/// Represents content of "id" message ("id name..." or "id author ...").
120#[derive(Clone, Eq, PartialEq, Debug, Hash)]
121pub enum IdParams {
122    Name(String),
123    Author(String),
124}
125
126/// Represents payload of "bestmove" message.
127#[derive(Clone, Eq, PartialEq, Debug, Hash)]
128pub enum BestMoveParams {
129    BestMove {
130        bestmove: Move,
131        ponder: Option<Move>,
132    },
133    Win,
134    Resign,
135}
136
137/// Represents payload of "checkmate" message, sent after a "go mate" search terminates.
138#[derive(Clone, Eq, PartialEq, Debug, Hash)]
139pub enum CheckMateParams {
140    /// Main line of checkmate solution
141    Mate(Vec<Move>),
142    /// No forced mate exists
143    NoMate,
144    /// Search for a forced mate timed out and was inconclusive
145    TimeOut,
146    /// Search for forced mates is not implemented
147    NotImplemented,
148}
149
150/// Represents copy protection or registration state.
151#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
152pub enum StatusCheck {
153    /// Signifies the engine is checking the copy protection or registration.
154    Checking,
155
156    /// Signifies copy protection or registration has been validated.
157    Ok,
158
159    /// Signifies error in copy protection or registration validation.
160    Error,
161}
162
163/// Represents contents of the "option" message.
164#[derive(Clone, Eq, PartialEq, Debug, Hash)]
165pub enum OptionParam {
166    Check {
167        name: String,
168        default: Option<bool>,
169    },
170    Spin {
171        name: String,
172        default: Option<i32>,
173        min: Option<i32>,
174        max: Option<i32>,
175    },
176    Combo {
177        name: String,
178        default: Option<String>,
179        vars: Vec<String>,
180    },
181    Button {
182        name: String,
183        // default: Option<String>
184    },
185    String {
186        name: String,
187        default: Option<String>,
188    },
189    Filename {
190        name: String,
191        default: Option<String>,
192    },
193}
194
195/// Represents possible payloads of the "info" message.
196#[derive(Clone, Eq, PartialEq, Debug, Hash)]
197pub enum InfoParam {
198    /// The `info depth` message. Search depth in plies.
199    Depth(u16),
200
201    /// The `info seldepth` message. Selective search depth in plies.
202    /// This also requires `depth` to be sent.
203    SelDepth(u16),
204
205    /// The `info time` message. The time searched. Should be sent with the pv.
206    Time(Duration),
207
208    /// The `info nodes` message. Number of nodes searched.
209    Nodes(u64),
210
211    /// The `info pv` message (principal variation, best line).
212    Pv(Vec<Move>),
213
214    /// The `info pv ... multipv` message (the pv line number in a multi pv sequence).
215    MultiPv(u16),
216
217    /// The 'info score cp ...' message. The score in centipawns (from engine's point of view).
218    ScoreCp(i32, ScoreBound),
219
220    /// The info score mate ...' message. Mate in this many plies. Negative values are
221    /// used to indicate that the engine is being mated.
222    ScoreMate(Option<i32>, ScoreBound),
223
224    /// The `info currmove` message (current move being searched).
225    CurrMove(Move),
226
227    /// The `info currmovenum` message (current move number).
228    CurrMoveNumber(u16),
229
230    /// The `info hashfull` message (occupancy of hash tables in permills, from 0 to 1000).
231    HashFull(u16),
232
233    /// The `info nps` message (nodes per second).
234    Nps(u64),
235
236    /// The `info cpuload` message (CPU load in permills).
237    CpuLoad(u16),
238
239    /// The `info string` message (a string the GUI should display).
240    String(String),
241
242    /// The `info refutation` message (the first move is the move being refuted).
243    Refutation(Vec<Move>),
244
245    /// The `info currline` message (current line being calculated on a CPU).
246    CurrLine {
247        /// The CPU number calculating this line.
248        cpu_nr: Option<u16>,
249
250        /// The line being calculated.
251        line: Vec<Move>,
252    },
253}
254
255#[derive(Clone, Eq, PartialEq, Debug, Hash)]
256pub enum ScoreBound {
257    MatePlus,
258    MateMin,
259    Exact,
260    Lower,
261    Upper,
262}
263
264// Note that the Display for EngineMessage does not add a terminating newline character.
265// When actually sending protocol messages a writer should add the '\n'.
266
267impl fmt::Display for EngineMessage {
268    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
269        match self {
270            EngineMessage::Id(params) => write!(f, "id {}", params),
271            EngineMessage::UsiOk => write!(f, "usiok"),
272            EngineMessage::ReadyOk => write!(f, "readyok"),
273            EngineMessage::BestMove(params) => write!(f, "bestmove {}", params),
274            EngineMessage::CheckMate(params) => write!(f, "checkmate {}", params),
275            EngineMessage::CopyProtection(state) => write!(f, "copyprotection {}", state),
276            EngineMessage::Registration(state) => write!(f, "register {}", state),
277            EngineMessage::Option(option) => write!(f, "option {}", option),
278            EngineMessage::Info(info) => write!(f, "info {}", format_vec!(info)),
279            EngineMessage::Unknown(s) => write!(f, "UNKNOWN \"{}\"", s),
280        }
281    }
282}
283
284impl fmt::Display for IdParams {
285    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
286        match self {
287            IdParams::Name(name) => write!(f, "name {}", name),
288            IdParams::Author(author) => write!(f, "author {}", author),
289        }
290    }
291}
292
293impl fmt::Display for BestMoveParams {
294    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
295        match self {
296            Self::BestMove { bestmove, ponder } => match ponder {
297                Some(ponder) => write!(f, "{} ponder {}", bestmove, ponder),
298                _ => write!(f, "{}", bestmove),
299            },
300            Self::Win => write!(f, "win"),
301            Self::Resign => write!(f, "resign"),
302        }
303    }
304}
305
306impl fmt::Display for CheckMateParams {
307    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
308        match self {
309            Self::Mate(mvs) => write!(f, "{}", format_vec!(mvs)),
310            Self::NoMate => write!(f, "nomate"),
311            Self::TimeOut => write!(f, "timeout"),
312            _ => write!(f, "notimplemented"),
313        }
314    }
315}
316
317impl fmt::Display for StatusCheck {
318    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
319        match self {
320            Self::Checking => write!(f, "checking"),
321            Self::Ok => write!(f, "ok"),
322            Self::Error => write!(f, "error"),
323        }
324    }
325}
326
327impl fmt::Display for OptionParam {
328    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
329        match self {
330            Self::Check { name, default } => {
331                if let Some(default) = default {
332                    write!(f, "name {} type check default {}", name, default)
333                } else {
334                    write!(f, "name {} type check", name)
335                }
336            }
337            Self::String { name, default } => match default {
338                Some(s) if s.is_empty() => write!(f, "name {} type string default <empty>", name),
339                Some(s) => write!(f, "name {} type string default {}", name, s),
340                _ => write!(f, "name {} type string", name), // seems invalid
341            },
342            Self::Filename { name, default } => match default {
343                Some(s) if s.is_empty() => write!(f, "name {} type filename default <empty>", name),
344                Some(s) => write!(f, "name {} type filename default {}", name, s),
345                _ => write!(f, "name {} type filename", name), // seems invalid
346            },
347            Self::Button { name } => write!(f, "name {} type button", name),
348            Self::Spin {
349                name,
350                default,
351                min,
352                max,
353            } => {
354                let mut opt = format!("name {} type spin", name);
355                if let Some(default) = default {
356                    opt += &format!(" default {}", default);
357                }
358                if let Some(min) = min {
359                    opt += &format!(" min {}", min);
360                }
361                if let Some(max) = max {
362                    opt += &format!(" max {}", max);
363                }
364                write!(f, "{}", opt)
365            }
366            Self::Combo {
367                name,
368                default,
369                vars,
370            } => {
371                let mut opt = format!("name {} type combo", name);
372                if let Some(default) = default {
373                    opt += &format!(" default {}", default);
374                }
375                if !vars.is_empty() {
376                    opt += &format!(" var {}", format_vec!(vars, " var "));
377                }
378                write!(f, "{}", opt)
379            }
380        }
381    }
382}
383
384impl fmt::Display for InfoParam {
385    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
386        match self {
387            Self::Depth(n) => write!(f, "depth {}", n),
388            Self::SelDepth(n) => write!(f, "seldepth {}", n),
389            Self::Time(n) => write!(f, "time {}", n.as_millis()),
390            Self::Nodes(n) => write!(f, "nodes {}", n),
391            Self::Pv(mvs) => write!(f, "pv {}", format_vec!(mvs)),
392            Self::MultiPv(n) => write!(f, "multipv {}", n),
393            Self::ScoreCp(cp, bound) => write!(f, "score cp {}{}", cp, bound),
394            Self::ScoreMate(plies, bound) => {
395                if let Some(plies) = plies {
396                    write!(f, "score mate {}{}", plies, bound)
397                } else {
398                    debug_assert!(*bound == ScoreBound::MateMin || *bound == ScoreBound::MatePlus);
399                    write!(f, "score mate{}", bound)
400                }
401            }
402            Self::CurrMove(mv) => write!(f, "currmove {}", mv),
403            Self::CurrMoveNumber(n) => write!(f, "currmovenumber {}", n),
404            Self::HashFull(n) => write!(f, "hashfull {}", n),
405            Self::Nps(n) => write!(f, "nps {}", n),
406            Self::CpuLoad(n) => write!(f, "cpuload {}", n),
407            Self::String(s) => write!(f, "string {}", s),
408            Self::Refutation(mvs) => write!(f, "refutation {}", format_vec!(mvs)),
409            Self::CurrLine { cpu_nr, line } => {
410                if let Some(cpu_nr) = cpu_nr {
411                    write!(f, "currline {} {}", cpu_nr, format_vec!(line))
412                } else {
413                    write!(f, "currline {}", format_vec!(line))
414                }
415            }
416        }
417    }
418}
419
420impl fmt::Display for ScoreBound {
421    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
422        match self {
423            Self::Lower => write!(f, " lowerbound"),
424            Self::Upper => write!(f, " upperbound"),
425            Self::MateMin => write!(f, " -"),
426            Self::MatePlus => write!(f, " +"),
427            _ => write!(f, ""),
428        }
429    }
430}