board_game/interface/
aei.rs

1//! The _Arimaa Engine Interface_ (AEI).
2//!
3//! A derivative of the UCI protocol for the game Arimaa.
4//! Specification available at <https://github.com/Janzert/AEI/blob/master/aei-protocol.txt>.
5
6use std::fmt::{Display, Formatter};
7
8#[derive(Debug, Clone, Eq, PartialEq)]
9pub enum Command {
10    AEI,
11    IsReady,
12    NewGame,
13    SetPosition(String),
14    SetOption { name: OptionName, value: Option<String> },
15    MakeMove(String),
16    Go { ponder: bool },
17    Stop,
18    Quit,
19}
20
21#[derive(Debug, Clone, Eq, PartialEq)]
22pub enum Response {
23    ProtocolV1,
24    AeiOk,
25    ReadyOk,
26    Id { ty: IdType, value: String },
27    BestMove(String),
28    Info { ty: InfoType, value: String },
29    Log(String),
30}
31
32#[derive(Debug, Copy, Clone, Eq, PartialEq)]
33pub enum IdType {
34    Name,
35    Author,
36    Version,
37}
38
39#[derive(Debug, Copy, Clone, Eq, PartialEq)]
40pub enum InfoType {
41    Score,
42    Depth,
43    Nodes,
44    Pv,
45    Time,
46    CurrMoveNumber,
47    String,
48}
49
50#[derive(Debug, Clone, Eq, PartialEq)]
51pub enum OptionName {
52    TC(TCOptionName),
53    Opponent,
54    OpponentRating,
55    Rating,
56    Rated,
57    Event,
58    Other(String),
59}
60
61#[derive(Debug, Copy, Clone, Eq, PartialEq)]
62pub enum TCOptionName {
63    TcMove,
64    TcReserve,
65    TcPercent,
66    TcMax,
67    TcTotal,
68    TcTurns,
69    TcTurnTime,
70    GReserve,
71    SReserve,
72    GUsed,
73    SUsed,
74    LastMoveUsed,
75    MoveUsed,
76}
77
78impl Display for Response {
79    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80        match self {
81            Response::ProtocolV1 => write!(f, "protocol-version 1"),
82            Response::AeiOk => write!(f, "aeiok"),
83            Response::ReadyOk => write!(f, "readyok"),
84            Response::Id { ty, value } => {
85                assert!(!value.contains('\n'));
86                write!(f, "id {} {}", ty, value)
87            }
88            Response::BestMove(mv) => {
89                assert!(!mv.contains('\n'));
90                write!(f, "bestmove {}", mv)
91            }
92            Response::Info { ty, value } => {
93                assert!(!value.contains('\n'));
94                write!(f, "info {} {}", ty, value)
95            }
96            Response::Log(log) => {
97                assert!(!log.contains('\n'));
98                write!(f, "log {}", log)
99            }
100        }
101    }
102}
103
104impl Display for IdType {
105    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
106        match self {
107            IdType::Name => write!(f, "name"),
108            IdType::Author => write!(f, "author"),
109            IdType::Version => write!(f, "version"),
110        }
111    }
112}
113
114impl Display for InfoType {
115    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
116        match self {
117            InfoType::Score => write!(f, "score"),
118            InfoType::Depth => write!(f, "depth"),
119            InfoType::Nodes => write!(f, "nodes"),
120            InfoType::Pv => write!(f, "pv"),
121            InfoType::Time => write!(f, "time"),
122            InfoType::CurrMoveNumber => write!(f, "currmovenumber"),
123            InfoType::String => write!(f, "string"),
124        }
125    }
126}
127
128impl Command {
129    pub fn parse(input: &str) -> Result<Command, nom::Err<nom::error::Error<&str>>> {
130        parse::command()(input).map(|(left, command)| {
131            assert!(left.is_empty());
132            command
133        })
134    }
135}
136
137mod parse {
138    use nom::branch::alt;
139    use nom::bytes::complete::{tag, take_until, take_while};
140    use nom::combinator::{eof, map, opt, value};
141    use nom::sequence::{preceded, terminated, tuple};
142    use nom::IResult;
143
144    use crate::interface::aei::{Command, OptionName, TCOptionName};
145
146    pub fn command<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Command> {
147        let remainder = || take_while(|_| true);
148
149        let set_position = preceded(
150            tag("setposition "),
151            map(remainder(), |s: &str| Command::SetPosition(s.to_owned())),
152        );
153
154        const OPTION_MAP: &[(&str, OptionName)] = &[
155            ("tcmove", OptionName::TC(TCOptionName::TcMove)),
156            ("tcreserve", OptionName::TC(TCOptionName::TcReserve)),
157            ("tcpercent", OptionName::TC(TCOptionName::TcPercent)),
158            ("tcmax", OptionName::TC(TCOptionName::TcMax)),
159            ("tctotal", OptionName::TC(TCOptionName::TcTotal)),
160            ("tcturns", OptionName::TC(TCOptionName::TcTurns)),
161            ("tcturntime", OptionName::TC(TCOptionName::TcTurnTime)),
162            ("greserve", OptionName::TC(TCOptionName::GReserve)),
163            ("sreserve", OptionName::TC(TCOptionName::SReserve)),
164            ("gused", OptionName::TC(TCOptionName::GUsed)),
165            ("sused", OptionName::TC(TCOptionName::SUsed)),
166            ("lastmoveused", OptionName::TC(TCOptionName::LastMoveUsed)),
167            ("moveused", OptionName::TC(TCOptionName::MoveUsed)),
168            ("opponent", OptionName::Opponent),
169            ("opponent_rating", OptionName::OpponentRating),
170            ("rating", OptionName::Rating),
171            ("rated", OptionName::Rated),
172            ("event", OptionName::Event),
173        ];
174
175        let option_name = map(take_until(" "), |s: &str| {
176            OPTION_MAP
177                .iter()
178                .find(|&&(k, _)| k == s)
179                .map_or_else(|| OptionName::Other(s.to_owned()), |(_, v)| v.clone())
180        });
181
182        let set_option = map(
183            tuple((
184                tag("setoption name "),
185                option_name,
186                opt(preceded(tag(" value "), remainder())),
187            )),
188            |(_, name, value)| Command::SetOption {
189                name,
190                value: value.map(ToOwned::to_owned),
191            },
192        );
193
194        let make_move = preceded(tag("makemove "), map(remainder(), |s| Command::MakeMove(s.to_owned())));
195
196        let main = alt((
197            value(Command::AEI, tag("aei")),
198            value(Command::IsReady, tag("isready")),
199            value(Command::NewGame, tag("newgame")),
200            set_position,
201            set_option,
202            make_move,
203            value(Command::Go { ponder: true }, tag("go ponder")),
204            value(Command::Go { ponder: false }, tag("go")),
205            value(Command::Stop, tag("stop")),
206            value(Command::Quit, tag("quit")),
207        ));
208
209        terminated(main, eof)
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use crate::interface::aei::{Command, OptionName};
216
217    #[test]
218    fn set_option() {
219        let parsed = Command::parse("setoption name opponent_rating value 1325");
220        let expected = Command::SetOption {
221            name: OptionName::OpponentRating,
222            value: Some("1325".to_owned()),
223        };
224        assert_eq!(parsed, Ok(expected))
225    }
226}