board_game/interface/gtp/
command.rs1use std::collections::VecDeque;
2use std::fmt::{Display, Formatter};
3use std::str::FromStr;
4
5use itertools::Itertools;
6
7#[derive(Debug, Clone)]
8pub struct Command {
9 pub id: Option<u64>,
10 pub name: String,
11 pub args: Vec<String>,
12}
13
14macro_rules! command_kinds {
15 ($($id:ident ($name:literal),)*) => {
16 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
17 pub enum CommandKind {
18 $($id),*
19 }
20
21 impl CommandKind {
22 pub const ALL: &'static [CommandKind] = &[$(CommandKind::$id),*];
23
24 pub fn name(self) -> &'static str {
25 match self {
26 $(CommandKind::$id => $name,)*
27 }
28 }
29 }
30
31 impl Display for CommandKind {
32 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
33 write!(f, "{}", self.name())
34 }
35 }
36 }
37}
38
39command_kinds!(
40 Name("name"),
42 ProtocolVersion("protocol_version"),
43 Version("version"),
44 KnownCommand("known_command"),
45 ListCommands("list_commands"),
46 Quit("quit"),
47 BoardSize("boardsize"),
49 ClearBoard("clear_board"),
50 Komi("komi"),
51 Play("play"),
57 GenMove("genmove"),
58 Undo("undo"),
59 TimeSettings("time_settings"),
61 TimeLeft("time_left"),
62 FinalScore("final_score"),
63 FinalStatusList("final_status_list"),
64 ShowBoard("showboard"),
69);
70
71#[derive(Debug, Clone, Eq, PartialEq)]
72pub enum FinalStatusKind {
73 Alive,
74 Dead,
75 Seki,
76 }
80
81pub type ResponseInner = Result<Option<String>, String>;
82
83#[derive(Debug, Clone, Eq, PartialEq)]
84pub struct Response {
85 id: Option<u64>,
86 inner: ResponseInner,
87}
88
89impl Response {
90 pub fn new(id: Option<u64>, inner: ResponseInner) -> Self {
91 let msg = match &inner {
93 Ok(msg) => msg.as_ref(),
94 Err(msg) => Some(msg),
95 };
96 if let Some(msg) = msg {
97 assert!(!msg.contains("\n\n"));
98 }
99
100 Self { id, inner }
101 }
102}
103
104#[derive(Debug, Clone, Eq, PartialEq)]
105pub struct InvalidCommand;
106
107impl FromStr for Command {
108 type Err = InvalidCommand;
109
110 fn from_str(s: &str) -> Result<Self, Self::Err> {
111 let mut tokens: VecDeque<_> = s.split(' ').collect();
112 if tokens.is_empty() {
113 return Err(InvalidCommand);
114 }
115
116 let id = if let Ok(id) = u64::from_str(tokens[0]) {
117 tokens.pop_front();
118 Some(id)
119 } else {
120 None
121 };
122
123 let name = tokens.pop_front().ok_or(InvalidCommand)?.to_owned();
124 let args = tokens.into_iter().map(|s| s.to_owned()).collect_vec();
125
126 Ok(Command { id, name, args })
127 }
128}
129
130#[derive(Debug, Clone, Eq, PartialEq)]
131pub struct UnknownCommand;
132
133impl FromStr for CommandKind {
134 type Err = UnknownCommand;
135
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
137 Self::ALL.iter().find(|c| c.name() == s).ok_or(UnknownCommand).copied()
138 }
139}
140
141impl Display for Response {
142 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
143 match (&self.inner, self.id) {
144 (Ok(Some(c)), Some(id)) => write!(f, "={id} {c}\n\n"),
145 (Ok(None), Some(id)) => write!(f, "={id}\n\n"),
146 (Ok(Some(c)), None) => write!(f, "= {c}\n\n"),
147 (Ok(None), None) => write!(f, "=\n\n"),
148 (Err(c), Some(id)) => write!(f, "?{id} {c}\n\n"),
149 (Err(c), None) => write!(f, "? {c}\n\n"),
150 }
151 }
152}
153
154#[derive(Debug, Clone, Eq, PartialEq)]
155pub struct UnknownStatus;
156
157impl FromStr for FinalStatusKind {
158 type Err = UnknownStatus;
159
160 fn from_str(s: &str) -> Result<Self, Self::Err> {
161 match s {
162 "alive" => Ok(FinalStatusKind::Alive),
163 "dead" => Ok(FinalStatusKind::Dead),
164 "seki" => Ok(FinalStatusKind::Seki),
165 _ => Err(UnknownStatus),
166 }
167 }
168}
169
170#[cfg(test)]
171mod test {
172 use crate::interface::gtp::command::CommandKind;
173 use std::str::FromStr;
174
175 #[test]
176 fn parse_all() {
177 for &kind in CommandKind::ALL {
178 assert_eq!(Ok(kind), CommandKind::from_str(&kind.to_string()))
179 }
180 }
181}