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}