board_game/interface/gtp/
command.rs

1use 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    // admin
41    Name("name"),
42    ProtocolVersion("protocol_version"),
43    Version("version"),
44    KnownCommand("known_command"),
45    ListCommands("list_commands"),
46    Quit("quit"),
47    // setup
48    BoardSize("boardsize"),
49    ClearBoard("clear_board"),
50    Komi("komi"),
51    // TODO handicap?
52    // FixedHandicap("fixed_handicap"),
53    // PlaceFreeHandicap("place_free_handicap"),
54    // SetFreeHandicap("set_free_handicap"),
55    // core play
56    Play("play"),
57    GenMove("genmove"),
58    Undo("undo"),
59    // tournament
60    TimeSettings("time_settings"),
61    TimeLeft("time_left"),
62    FinalScore("final_score"),
63    FinalStatusList("final_status_list"),
64    // TODO regression?
65    // LoadSgf("loadsgf"),
66    // RegGenMove("reg_genmove"),
67    //debug
68    ShowBoard("showboard"),
69);
70
71#[derive(Debug, Clone, Eq, PartialEq)]
72pub enum FinalStatusKind {
73    Alive,
74    Dead,
75    Seki,
76    // WhiteTerritory,
77    // BlackTerritory,
78    // Dame,
79}
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        // check that the response doesn't contain two consecutive newlines
92        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}