1use 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
30pub 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
81pub 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
95pub 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
130pub 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
204pub 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}