mms_rs/
lib.rs

1//! Rust Api for mms (micromouse simulator)
2
3use std::{
4    io::{stdin, stdout, BufRead, StdinLock, Write},
5    num::{NonZeroU32, ParseFloatError, ParseIntError},
6    str::FromStr,
7};
8
9#[cfg(feature = "c_api")]
10mod c_api;
11
12#[derive(thiserror::Error, Debug)]
13pub enum MmsError {
14    #[error("ParseIntError: {0}")]
15    ParseIntError(#[from] ParseIntError),
16    #[error("ParseFloatError: {0}")]
17    ParseFloatError(#[from] ParseFloatError),
18    #[error("ParseStatQueryError: {0}")]
19    ParseStatQueryError(String),
20    #[error("IoError: {0}")]
21    IoError(#[from] std::io::Error),
22    #[error("InvalidAck: {0}")]
23    InvalidAck(String),
24    #[error("InvalidColorString: {0}")]
25    InvalidColorString(String),
26    #[error("InvalidDirectionString: {0}")]
27    InvalidDirectionString(String),
28}
29
30/// Which stat to query
31pub enum StatQuery {
32    TotalDistance,
33    TotalTurns,
34    BestRunDistance,
35    BestRunTurns,
36    CurrentRunDistance,
37    CurrentRunTurns,
38    TotalEffectiveDistance,
39    BestRunEffectiveDistance,
40    CurrentRunEffectiveDistance,
41    Score,
42}
43
44impl FromStr for StatQuery {
45    type Err = MmsError;
46
47    fn from_str(s: &str) -> Result<Self, Self::Err> {
48        match s {
49            "total-distance" => Ok(StatQuery::TotalDistance),
50            "total-turns" => Ok(StatQuery::TotalTurns),
51            "best-run-distance" => Ok(StatQuery::BestRunDistance),
52            "best-run-turns" => Ok(StatQuery::BestRunTurns),
53            "current-run-distance" => Ok(StatQuery::CurrentRunDistance),
54            "current-run-turns" => Ok(StatQuery::CurrentRunTurns),
55            "total-effective-distance" => Ok(StatQuery::TotalEffectiveDistance),
56            "best-run-effective-distance" => Ok(StatQuery::BestRunEffectiveDistance),
57            "current-run-effective-distance" => Ok(StatQuery::CurrentRunEffectiveDistance),
58            "score" => Ok(StatQuery::Score),
59            _ => Err(MmsError::ParseStatQueryError(s.to_string())),
60        }
61    }
62}
63
64impl StatQuery {
65    fn get_string(&self) -> &'static str {
66        match self {
67            StatQuery::TotalDistance => "total-distance",
68            StatQuery::TotalTurns => "total-turns",
69            StatQuery::BestRunDistance => "best-run-distance",
70            StatQuery::BestRunTurns => "best-run-turns",
71            StatQuery::CurrentRunDistance => "current-run-distance",
72            StatQuery::CurrentRunTurns => "current-run-turns",
73            StatQuery::TotalEffectiveDistance => "total-effective-distance",
74            StatQuery::BestRunEffectiveDistance => "best-run-effective-distance",
75            StatQuery::CurrentRunEffectiveDistance => "current-run-effective-distance",
76            StatQuery::Score => "score",
77        }
78    }
79}
80
81/// The stat that was requested
82pub enum Stat {
83    TotalDistance(i32),
84    TotalTurns(i32),
85    BestRunDistance(i32),
86    BestRunTurns(i32),
87    CurrentRunDistance(i32),
88    CurrentRunTurns(i32),
89    TotalEffectiveDistance(f32),
90    BestRunEffectiveDistance(f32),
91    CurrentRunEffectiveDistance(f32),
92    Score(f32),
93}
94
95/// The direction for the wall
96pub enum Direction {
97    North,
98    East,
99    South,
100    West,
101}
102
103impl FromStr for Direction {
104    type Err = MmsError;
105
106    fn from_str(s: &str) -> Result<Self, Self::Err> {
107        use Direction::{East, North, South, West};
108        match s {
109            "n" => Ok(North),
110            "e" => Ok(East),
111            "s" => Ok(South),
112            "w" => Ok(West),
113            _ => Err(MmsError::InvalidDirectionString(s.to_string())),
114        }
115    }
116}
117
118impl Direction {
119    fn get_string(&self) -> char {
120        use Direction::{East, North, South, West};
121        match self {
122            North => 'n',
123            East => 'e',
124            South => 's',
125            West => 'w',
126        }
127    }
128}
129
130/// The cell color
131pub enum CellColor {
132    Black,
133    Blue,
134    Gray,
135    Cyan,
136    Green,
137    Orange,
138    Red,
139    White,
140    Yellow,
141    DarkBlue,
142    DarkCyan,
143    DarkGray,
144    DarkGreen,
145    DarkRed,
146    DarkYellow,
147}
148
149impl FromStr for CellColor {
150    type Err = MmsError;
151
152    fn from_str(s: &str) -> Result<Self, Self::Err> {
153        use CellColor::{
154            Black, Blue, Cyan, DarkBlue, DarkCyan, DarkGray, DarkGreen, DarkRed, DarkYellow, Gray,
155            Green, Orange, Red, White, Yellow,
156        };
157        match s {
158            "k" => Ok(Black),
159            "b" => Ok(Blue),
160            "a" => Ok(Gray),
161            "c" => Ok(Cyan),
162            "g" => Ok(Green),
163            "o" => Ok(Orange),
164            "r" => Ok(Red),
165            "w" => Ok(White),
166            "y" => Ok(Yellow),
167            "B" => Ok(DarkBlue),
168            "C" => Ok(DarkCyan),
169            "A" => Ok(DarkGray),
170            "G" => Ok(DarkGreen),
171            "R" => Ok(DarkRed),
172            "Y" => Ok(DarkYellow),
173            _ => Err(MmsError::InvalidColorString(s.to_string())),
174        }
175    }
176}
177
178impl CellColor {
179    fn get_char(&self) -> char {
180        use CellColor::{
181            Black, Blue, Cyan, DarkBlue, DarkCyan, DarkGray, DarkGreen, DarkRed, DarkYellow, Gray,
182            Green, Orange, Red, White, Yellow,
183        };
184        match self {
185            Black => 'k',
186            Blue => 'b',
187            Gray => 'a',
188            Cyan => 'c',
189            Green => 'g',
190            Orange => 'o',
191            Red => 'r',
192            White => 'w',
193            Yellow => 'y',
194            DarkBlue => 'B',
195            DarkCyan => 'C',
196            DarkGray => 'A',
197            DarkGreen => 'G',
198            DarkRed => 'R',
199            DarkYellow => 'Y',
200        }
201    }
202}
203
204/// The main wrapper around the mms api. Holds locks to `stdin` and `stdout` to allow for fast and
205/// exclusive access for the api.
206pub struct MmsApi;
207
208#[cfg(not(feature = "use_panics"))]
209type ResultType<T> = Result<T, MmsError>;
210
211#[cfg(feature = "use_panics")]
212type ResultType<T> = T;
213
214#[cfg(not(feature = "use_panics"))]
215macro_rules! writeln_and_flush {
216    ($dst:expr, $($arg:tt)*) => {
217        writeln!($dst, $($arg)*)?;
218        $dst.flush()?;
219    };
220}
221
222#[cfg(feature = "use_panics")]
223macro_rules! writeln_and_flush {
224    ($dst:expr, $($arg:tt)*) => {
225        writeln!($dst, $($arg)*).unwrap();
226        $dst.flush().unwrap();
227    };
228}
229
230#[cfg(not(feature = "use_panics"))]
231macro_rules! handle_result {
232    ($e: expr) => {
233        $e?
234    };
235}
236#[cfg(feature = "use_panics")]
237macro_rules! handle_result {
238    ($e: expr) => {
239        $e.unwrap()
240    };
241}
242
243#[cfg(not(feature = "use_panics"))]
244macro_rules! return_result {
245    ($e: expr) => {
246        return Ok($e);
247    };
248}
249#[cfg(feature = "use_panics")]
250macro_rules! return_result {
251    (()) => {
252        ()
253    };
254    ($e: expr) => {
255        return $e;
256    };
257}
258
259macro_rules! ack {
260    ($cin: expr) => {
261        return MmsApi::read_ack(&mut $cin);
262    };
263}
264
265impl MmsApi {
266    /// Returns the width of the maze
267    ///
268    /// # Errors
269    /// `IoError`
270    /// `ParseIntError`
271    /// `ParseFloatError`
272    /// # Panics
273    /// this panics when `use_panics` is disabled
274    #[cfg_attr(feature = "use_panics", must_use)]
275    pub fn maze_width() -> ResultType<i32> {
276        let mut cout = stdout().lock();
277        let mut cin = stdin().lock();
278        writeln_and_flush!(cout, "mazeWidth");
279        let mut response = String::new();
280        handle_result!(cin.read_line(&mut response));
281        return_result!(handle_result!(response.trim().parse()));
282    }
283
284    /// Returns the height of the maze
285    ///
286    /// # Errors
287    /// `IoError`
288    /// `ParseIntError`
289    /// `ParseFloatError`
290    /// # Panics
291    /// this panics when `use_panics` is disabled
292    #[cfg_attr(feature = "use_panics", must_use)]
293    pub fn maze_height() -> ResultType<i32> {
294        let mut cout = stdout().lock();
295        let mut cin = stdin().lock();
296        writeln_and_flush!(cout, "mazeHeight");
297        let mut response = String::new();
298        handle_result!(cin.read_line(&mut response));
299        return_result!(handle_result!(response.trim().parse()));
300    }
301
302    /// Returns `true` if there is a wall in front of the robot, else `false`
303    ///
304    /// # Errors
305    /// `IoError`
306    /// `ParseIntError`
307    /// `ParseFloatError`
308    /// # Panics
309    /// this panics when `use_panics` is disabled
310    #[cfg_attr(feature = "use_panics", must_use)]
311    pub fn wall_front() -> ResultType<bool> {
312        let mut cout = stdout().lock();
313        let mut cin = stdin().lock();
314        writeln_and_flush!(cout, "wallFront");
315        let mut response = String::new();
316        handle_result!(cin.read_line(&mut response));
317        return_result!(response.trim() == "true");
318    }
319
320    /// Returns `true` if there is a wall to the right of the robot, else `false`
321    ///
322    /// # Errors
323    /// `IoError`
324    /// `ParseIntError`
325    /// `ParseFloatError`
326    /// # Panics
327    /// this panics when `use_panics` is disabled
328    #[cfg_attr(feature = "use_panics", must_use)]
329    pub fn wall_right() -> ResultType<bool> {
330        let mut cout = stdout().lock();
331        let mut cin = stdin().lock();
332        writeln_and_flush!(cout, "wallRight");
333        let mut response = String::new();
334        handle_result!(cin.read_line(&mut response));
335        return_result!(response.trim() == "true");
336    }
337
338    /// Returns `true` if there is a wall to the left of the robot, else `false`
339    ///
340    /// # Errors
341    /// `IoError`
342    /// `ParseIntError`
343    /// `ParseFloatError`
344    /// # Panics
345    /// this panics when `use_panics` is disabled
346    #[cfg_attr(feature = "use_panics", must_use)]
347    pub fn wall_left() -> ResultType<bool> {
348        let mut cout = stdout().lock();
349        let mut cin = stdin().lock();
350        writeln_and_flush!(cout, "wallLeft");
351        let mut response = String::new();
352        handle_result!(cin.read_line(&mut response));
353        return_result!(response.trim() == "true");
354    }
355
356    /// Move the robot forward the specified number of cells
357    ///
358    /// Args:
359    /// - `distance`: The optional non-zero number of cells to move forward. Default = 1
360    ///
361    /// # Errors
362    /// `IoError`
363    /// `ParseIntError`
364    /// `ParseFloatError`
365    /// `InvalidAck`
366    /// # Panics
367    /// this panics when `use_panics` is disabled
368    #[cfg_attr(feature = "use_panics", must_use)]
369    pub fn move_forward(distance: Option<NonZeroU32>) -> ResultType<()> {
370        let mut cout = stdout().lock();
371        let mut cin = stdin().lock();
372        writeln_and_flush!(
373            cout,
374            "moveForward {}",
375            distance.map_or_else(String::new, |d| d.to_string())
376        );
377        ack!(cin);
378    }
379
380    /// Turn the robot ninety degrees to the right
381    ///
382    /// # Errors
383    /// `IoError`
384    /// `ParseIntError`
385    /// `ParseFloatError`
386    /// `InvalidAck`
387    /// # Panics
388    /// this panics when `use_panics` is disabled
389    #[cfg_attr(feature = "use_panics", must_use)]
390    pub fn turn_right() -> ResultType<()> {
391        let mut cout = stdout().lock();
392        let mut cin = stdin().lock();
393        writeln_and_flush!(cout, "turnRight");
394        ack!(cin);
395    }
396
397    /// Turn the robot ninety degrees to the left
398    ///
399    /// # Errors
400    /// `IoError`
401    /// `ParseIntError`
402    /// `ParseFloatError`
403    /// `InvalidAck`
404    /// # Panics
405    /// this panics when `use_panics` is disabled
406    #[cfg_attr(feature = "use_panics", must_use)]
407    pub fn turn_left() -> ResultType<()> {
408        let mut cout = stdout().lock();
409        let mut cin = stdin().lock();
410        writeln_and_flush!(cout, "turnLeft");
411        ack!(cin);
412    }
413
414    /// Display a wall at the given position
415    ///
416    /// Args:
417    /// - `x`: The X coordinate of the cell
418    /// - `y`: The Y coordinate of the cell
419    /// - `direction`: The direction of the wall
420    ///
421    /// # Errors
422    /// `IoError`
423    /// `ParseIntError`
424    /// `ParseFloatError`
425    /// # Panics
426    /// this panics when `use_panics` is disabled
427    #[cfg_attr(feature = "use_panics", must_use)]
428    pub fn set_wall(x: u32, y: u32, direction: &Direction) -> ResultType<()> {
429        let mut cout = stdout().lock();
430        writeln_and_flush!(cout, "setWall {x} {y} {}", direction.get_string());
431        return_result!(());
432    }
433
434    /// Clear the wall at the given position
435    ///
436    /// Args:
437    /// - `x`: The X coordinate of the cell
438    /// - `y`: The Y coordinate of the cell
439    /// - `direction`: The direction of the wall
440    ///
441    /// # Errors
442    /// `IoError`
443    /// `ParseIntError`
444    /// `ParseFloatError`
445    /// # Panics
446    /// this panics when `use_panics` is disabled
447    #[cfg_attr(feature = "use_panics", must_use)]
448    pub fn clear_wall(x: u32, y: u32, direction: &Direction) -> ResultType<()> {
449        let mut cout = stdout().lock();
450        writeln_and_flush!(cout, "clearWall {x} {y} {}", direction.get_string());
451        return_result!(());
452    }
453
454    /// Set the color of the cell at the given position
455    ///
456    /// Args:
457    /// - `x`: The X coordinate of the cell
458    /// - `y`: The Y coordinate of the cell
459    /// - `color`: The color of the cell
460    ///
461    /// # Errors
462    /// `IoError`
463    /// `ParseIntError`
464    /// `ParseFloatError`
465    /// # Panics
466    /// this panics when `use_panics` is disabled
467    #[cfg_attr(feature = "use_panics", must_use)]
468    pub fn set_color(x: u32, y: u32, color: &CellColor) -> ResultType<()> {
469        let mut cout = stdout().lock();
470        writeln_and_flush!(cout, "setColor {x} {y} {}", color.get_char());
471        return_result!(());
472    }
473
474    /// Clear the color of the cell at the given position
475    ///
476    /// Args:
477    /// - `x`: The X coordinate of the cell
478    /// - `y`: The Y coordinate of the cell
479    ///
480    /// # Errors
481    /// `IoError`
482    /// `ParseIntError`
483    /// `ParseFloatError`
484    /// # Panics
485    /// this panics when `use_panics` is disabled
486    #[cfg_attr(feature = "use_panics", must_use)]
487    pub fn clear_color(x: u32, y: u32) -> ResultType<()> {
488        let mut cout = stdout().lock();
489        writeln_and_flush!(cout, "clearColor {x} {y}");
490        return_result!(());
491    }
492
493    /// Clear the color of all cells
494    ///
495    /// # Errors
496    /// `IoError`
497    /// `ParseIntError`
498    /// `ParseFloatError`
499    /// # Panics
500    /// this panics when `use_panics` is disabled
501    #[cfg_attr(feature = "use_panics", must_use)]
502    pub fn clear_all_color() -> ResultType<()> {
503        let mut cout = stdout().lock();
504        writeln_and_flush!(cout, "clearAllColor");
505        return_result!(());
506    }
507
508    /// Set the text of the cell at the given position
509    ///
510    /// Args:
511    /// - `x`: The X coordinate of the cell
512    /// - `y`: The Y coordinate of the cell
513    /// - `text`: The desired text, max length 10
514    ///
515    /// # Errors
516    /// `IoError`
517    /// `ParseIntError`
518    /// `ParseFloatError`
519    /// # Panics
520    /// this panics when `use_panics` is disabled
521    #[cfg_attr(feature = "use_panics", must_use)]
522    pub fn set_text(x: u32, y: u32, text: &str) -> ResultType<()> {
523        let mut cout = stdout().lock();
524        writeln_and_flush!(cout, "setText {x} {y} {text}");
525        return_result!(());
526    }
527
528    /// Clear the text of the cell at the given position
529    ///
530    /// Args:
531    /// - `x`: The X coordinate of the cell
532    /// - `y`: The Y coordinate of the cell
533    ///
534    /// # Errors
535    /// `IoError`
536    /// `ParseIntError`
537    /// `ParseFloatError`
538    /// # Panics
539    /// this panics when `use_panics` is disabled
540    #[cfg_attr(feature = "use_panics", must_use)]
541    pub fn clear_text(x: u32, y: u32) -> ResultType<()> {
542        let mut cout = stdout().lock();
543        writeln_and_flush!(cout, "clearText {x} {y}");
544        return_result!(());
545    }
546
547    /// Clear the text of all cells
548    ///
549    /// # Errors
550    /// `IoError`
551    /// `ParseIntError`
552    /// `ParseFloatError`
553    /// # Panics
554    /// this panics when `use_panics` is disabled
555    #[cfg_attr(feature = "use_panics", must_use)]
556    pub fn clear_all_text() -> ResultType<()> {
557        let mut cout = stdout().lock();
558        writeln_and_flush!(cout, "clearAllText");
559        return_result!(());
560    }
561
562    /// Returns `true` if the reset button was pressed, else `false`
563    ///
564    /// # Errors
565    /// `IoError`
566    /// `ParseIntError`
567    /// `ParseFloatError`
568    /// # Panics
569    /// this panics when `use_panics` is disabled
570    #[cfg_attr(feature = "use_panics", must_use)]
571    pub fn was_reset() -> ResultType<bool> {
572        let mut cout = stdout().lock();
573        let mut cin = stdin().lock();
574        writeln_and_flush!(cout, "wasReset");
575        let mut response = String::new();
576        handle_result!(cin.read_line(&mut response));
577        return_result!(response.trim() == "true");
578    }
579
580    /// Allow the mouse to be moved back to the start of the maze
581    ///
582    /// # Errors
583    /// `IoError`
584    /// `ParseIntError`
585    /// `ParseFloatError`
586    /// `InvalidAck`
587    /// # Panics
588    /// this panics when `use_panics` is disabled
589    #[cfg_attr(feature = "use_panics", must_use)]
590    pub fn ack_reset() -> ResultType<()> {
591        let mut cout = stdout().lock();
592        let mut cin = stdin().lock();
593        writeln_and_flush!(cout, "ackReset");
594        ack!(cin);
595    }
596
597    /// The value of the stat, or `-1` if no value exists yet.
598    ///
599    /// # Errors
600    /// `IoError`
601    /// `ParseIntError`
602    /// `ParseFloatError`
603    /// # Panics
604    /// this panics when `use_panics` is disabled
605    #[cfg_attr(feature = "use_panics", must_use)]
606    pub fn get_stat(query: &StatQuery) -> ResultType<Stat> {
607        let mut cout = stdout().lock();
608        let mut cin = stdin().lock();
609        writeln_and_flush!(cout, "{}", query.get_string());
610        let mut response = String::new();
611        handle_result!(cin.read_line(&mut response));
612        let response = response.trim();
613        let result = match query {
614            StatQuery::TotalDistance => Stat::TotalDistance(handle_result!(response.parse())),
615            StatQuery::TotalTurns => Stat::TotalTurns(handle_result!(response.parse())),
616            StatQuery::BestRunDistance => Stat::BestRunDistance(handle_result!(response.parse())),
617            StatQuery::BestRunTurns => Stat::BestRunTurns(handle_result!(response.parse())),
618            StatQuery::CurrentRunDistance => {
619                Stat::CurrentRunDistance(handle_result!(response.parse()))
620            }
621            StatQuery::CurrentRunTurns => Stat::CurrentRunTurns(handle_result!(response.parse())),
622            StatQuery::TotalEffectiveDistance => {
623                Stat::TotalEffectiveDistance(handle_result!(response.parse()))
624            }
625            StatQuery::BestRunEffectiveDistance => {
626                Stat::BestRunEffectiveDistance(handle_result!(response.parse()))
627            }
628            StatQuery::CurrentRunEffectiveDistance => {
629                Stat::CurrentRunEffectiveDistance(handle_result!(response.parse()))
630            }
631            StatQuery::Score => Stat::Score(handle_result!(response.parse())),
632        };
633        return_result!(result);
634    }
635
636    fn read_ack(cin: &mut StdinLock) -> ResultType<()> {
637        let mut response = String::new();
638        handle_result!(cin.read_line(&mut response));
639        let ack = response.trim();
640        #[cfg(not(feature = "use_panics"))]
641        if ack == "ack" {
642            Ok(())
643        } else {
644            Err(MmsError::InvalidAck(response))
645        }
646        #[cfg(feature = "use_panics")]
647        assert!(ack == "ack", "{response}");
648    }
649}