1use crate::*;
2
3pub trait FenRepresentable {
4 fn fen(&self) -> String;
5}
6
7impl FenRepresentable for MonsGame {
8 fn fen(&self) -> String {
9 let fields = vec![
10 self.white_score.to_string(),
11 self.black_score.to_string(),
12 self.active_color.fen(),
13 self.actions_used_count.to_string(),
14 self.mana_moves_count.to_string(),
15 self.mons_moves_count.to_string(),
16 self.white_potions_count.to_string(),
17 self.black_potions_count.to_string(),
18 self.turn_number.to_string(),
19 self.board.fen(),
20 ];
21 fields.join(" ")
22 }
23}
24
25impl MonsGame {
26 pub fn from_fen(fen: &str) -> Option<Self> {
27 let fields: Vec<&str> = fen.split_whitespace().collect();
28 if fields.len() != 10 {
29 return None;
30 }
31 Some(Self {
32 board: Board::from_fen(fields[9])?,
33 white_score: fields[0].parse().ok()?,
34 black_score: fields[1].parse().ok()?,
35 active_color: Color::from_fen(fields[2])?,
36 actions_used_count: fields[3].parse().ok()?,
37 mana_moves_count: fields[4].parse().ok()?,
38 mons_moves_count: fields[5].parse().ok()?,
39 white_potions_count: fields[6].parse().ok()?,
40 black_potions_count: fields[7].parse().ok()?,
41 turn_number: fields[8].parse().ok()?,
42 })
43 }
44}
45
46impl FenRepresentable for Item {
47 fn fen(&self) -> String {
48 match self {
49 Item::Mon { mon } => format!("{}x", mon.fen()),
50 Item::Mana { mana } => format!("xx{}", mana.fen()),
51 Item::MonWithMana { mon, mana } => format!("{}{}", mon.fen(), mana.fen()),
52 Item::MonWithConsumable { mon, consumable } => format!("{}{}", mon.fen(), consumable.fen()),
53 Item::Consumable { consumable } => format!("xx{}", consumable.fen()),
54 }
55 }
56}
57
58impl Item {
59 fn from_fen(fen: &str) -> Option<Self> {
60 if fen.len() != 3 {
61 return None;
62 }
63
64 let mon_fen = &fen[0..2];
65 let item_fen = &fen[2..];
66
67 match mon_fen {
68 "xx" => match Mana::from_fen(item_fen) {
69 Some(mana) => Some(Item::Mana { mana }),
70 None => Consumable::from_fen(item_fen).map(|consumable| Item::Consumable { consumable }),
71 },
72 _ => {
73 let mon = Mon::from_fen(mon_fen)?;
74 if let Some(mana) = Mana::from_fen(item_fen) {
75 Some(Item::MonWithMana { mon, mana })
76 } else if let Some(consumable) = Consumable::from_fen(item_fen) {
77 Some(Item::MonWithConsumable { mon, consumable })
78 } else {
79 Some(Item::Mon { mon })
80 }
81 }
82 }
83 }
84}
85
86impl FenRepresentable for Board {
87 fn fen(&self) -> String {
88 let mut lines: Vec<String> = Vec::new();
89 for i in 0..Config::BOARD_SIZE {
90 let mut line = String::new();
91 let mut empty_space_count = 0;
92 for j in 0..Config::BOARD_SIZE {
93 match self.items.get(&Location { i, j }) {
94 Some(item) => {
95 if empty_space_count > 0 {
96 line += &format!("n{:02}", empty_space_count);
97 empty_space_count = 0;
98 }
99 line += &item.fen();
100 }
101 None => {
102 empty_space_count += 1;
103 }
104 }
105 }
106 if empty_space_count > 0 {
107 line += &format!("n{:02}", empty_space_count);
108 }
109 lines.push(line);
110 }
111 lines.join("/")
112 }
113}
114
115impl Board {
116 pub fn from_fen(fen: &str) -> Option<Self> {
117 let lines: Vec<&str> = fen.split('/').collect();
118 if lines.len() != Config::BOARD_SIZE as usize {
119 return None;
120 }
121 let mut items = std::collections::HashMap::new();
122 for (i, line) in lines.iter().enumerate() {
123 let mut j = 0;
124 let mut chars = line.chars().peekable();
125
126 while let Some(ch) = chars.peek() {
127 match ch {
128 'n' => {
129 chars.next();
130 let num_chars: String = chars.by_ref().take(2).collect();
131 if let Ok(num) = num_chars.parse::<usize>() {
132 j += num;
133 }
134 },
135 _ => {
136 let item_fen: String = chars.by_ref().take(3).collect();
137 if let Some(item) = Item::from_fen(&item_fen) {
138 items.insert(Location { i: i as i32, j: j as i32 }, item);
139 }
140 j += 1;
141 }
142 }
143 }
144 }
145 Some(Self { items })
146 }
147}
148
149
150impl FenRepresentable for Mon {
151 fn fen(&self) -> String {
152 let kind_char = match self.kind {
153 MonKind::Demon => 'e',
154 MonKind::Drainer => 'd',
155 MonKind::Angel => 'a',
156 MonKind::Spirit => 's',
157 MonKind::Mystic => 'y',
158 };
159 let kind_char = if self.color == Color::White { kind_char.to_uppercase().to_string() } else { kind_char.to_string() };
160 format!("{}{}", kind_char, self.cooldown % 10)
161 }
162}
163
164impl Mon {
165 fn from_fen(fen: &str) -> Option<Self> {
166 if fen.len() != 2 {
167 return None;
168 }
169 let chars: Vec<char> = fen.chars().collect();
170 let kind = match chars[0].to_ascii_lowercase() {
171 'e' => MonKind::Demon,
172 'd' => MonKind::Drainer,
173 'a' => MonKind::Angel,
174 's' => MonKind::Spirit,
175 'y' => MonKind::Mystic,
176 _ => return None,
177 };
178 let color = if chars[0].is_uppercase() { Color::White } else { Color::Black };
179 let cooldown = chars[1].to_digit(10)?;
180 Some(Mon { kind, color, cooldown: cooldown as i32 })
181 }
182}
183
184impl FenRepresentable for Mana {
185 fn fen(&self) -> String {
186 match *self {
187 Mana::Regular(Color::White) => "M".to_string(),
188 Mana::Regular(Color::Black) => "m".to_string(),
189 Mana::Supermana => "U".to_string(),
190 }
191 }
192}
193
194impl Mana {
195 fn from_fen(fen: &str) -> Option<Self> {
196 match fen {
197 "U" => Some(Mana::Supermana),
198 "M" => Some(Mana::Regular(Color::White)),
199 "m" => Some(Mana::Regular(Color::Black)),
200 _ => None,
201 }
202 }
203}
204
205impl FenRepresentable for Color {
206 fn fen(&self) -> String {
207 match self {
208 Color::White => "w".to_string(),
209 Color::Black => "b".to_string(),
210 }
211 }
212}
213
214impl Color {
215 fn from_fen(fen: &str) -> Option<Self> {
216 match fen {
217 "w" => Some(Color::White),
218 "b" => Some(Color::Black),
219 _ => None,
220 }
221 }
222}
223
224impl FenRepresentable for Consumable {
225 fn fen(&self) -> String {
226 match self {
227 Consumable::Potion => "P".to_string(),
228 Consumable::Bomb => "B".to_string(),
229 Consumable::BombOrPotion => "Q".to_string(),
230 }
231 }
232}
233
234impl Consumable {
235 fn from_fen(fen: &str) -> Option<Self> {
236 match fen {
237 "P" => Some(Consumable::Potion),
238 "B" => Some(Consumable::Bomb),
239 "Q" => Some(Consumable::BombOrPotion),
240 _ => None,
241 }
242 }
243}
244
245impl FenRepresentable for Event {
246 fn fen(&self) -> String {
247 match self {
248 Event::MonMove { item, from, to } => format!("mm {} {} {}", item.fen(), from.fen(), to.fen()),
249 Event::ManaMove { mana, from, to } => format!("mma {} {} {}", mana.fen(), from.fen(), to.fen()),
250 Event::ManaScored { mana, at } => format!("ms {} {}", mana.fen(), at.fen()),
251 Event::MysticAction { mystic, from, to } => format!("ma {} {} {}", mystic.fen(), from.fen(), to.fen()),
252 Event::DemonAction { demon, from, to } => format!("da {} {} {}", demon.fen(), from.fen(), to.fen()),
253 Event::DemonAdditionalStep { demon, from, to } => format!("das {} {} {}", demon.fen(), from.fen(), to.fen()),
254 Event::SpiritTargetMove { item, from, to } => format!("stm {} {} {}", item.fen(), from.fen(), to.fen()),
255 Event::PickupBomb { by, at } => format!("pb {} {}", by.fen(), at.fen()),
256 Event::PickupPotion { by, at } => format!("pp {} {}", by.fen(), at.fen()),
257 Event::PickupMana { mana, by, at } => format!("pm {} {} {}", mana.fen(), by.fen(), at.fen()),
258 Event::MonFainted { mon, from, to } => format!("mf {} {} {}", mon.fen(), from.fen(), to.fen()),
259 Event::ManaDropped { mana, at } => format!("md {} {}", mana.fen(), at.fen()),
260 Event::SupermanaBackToBase { from, to } => format!("sb {} {}", from.fen(), to.fen()),
261 Event::BombAttack { by, from, to } => format!("ba {} {} {}", by.fen(), from.fen(), to.fen()),
262 Event::MonAwake { mon, at } => format!("maw {} {}", mon.fen(), at.fen()),
263 Event::BombExplosion { at } => format!("be {}", at.fen()),
264 Event::NextTurn { color } => format!("nt {}", color.fen()),
265 Event::GameOver { winner } => format!("go {}", winner.fen()),
266 }
267 }
268}
269
270impl Event {
271 fn from_fen(fen: &str) -> Option<Self> {
272 let parts: Vec<&str> = fen.split(' ').collect();
273 match parts.as_slice() {
274 ["mm", item_fen, from_fen, to_fen] => {
275 Some(Event::MonMove {
276 item: Item::from_fen(item_fen)?,
277 from: Location::from_fen(from_fen)?,
278 to: Location::from_fen(to_fen)?,
279 })
280 }
281 ["mma", mana_fen, from_fen, to_fen] => {
282 Some(Event::ManaMove {
283 mana: Mana::from_fen(mana_fen)?,
284 from: Location::from_fen(from_fen)?,
285 to: Location::from_fen(to_fen)?,
286 })
287 }
288 ["ms", mana_fen, at_fen] => {
289 Some(Event::ManaScored {
290 mana: Mana::from_fen(mana_fen)?,
291 at: Location::from_fen(at_fen)?,
292 })
293 }
294 ["ma", mystic_fen, from_fen, to_fen] => {
295 Some(Event::MysticAction {
296 mystic: Mon::from_fen(mystic_fen)?,
297 from: Location::from_fen(from_fen)?,
298 to: Location::from_fen(to_fen)?,
299 })
300 }
301 ["da", demon_fen, from_fen, to_fen] => {
302 Some(Event::DemonAction {
303 demon: Mon::from_fen(demon_fen)?,
304 from: Location::from_fen(from_fen)?,
305 to: Location::from_fen(to_fen)?,
306 })
307 }
308 ["das", demon_fen, from_fen, to_fen] => {
309 Some(Event::DemonAdditionalStep {
310 demon: Mon::from_fen(demon_fen)?,
311 from: Location::from_fen(from_fen)?,
312 to: Location::from_fen(to_fen)?,
313 })
314 }
315 ["stm", item_fen, from_fen, to_fen] => {
316 Some(Event::SpiritTargetMove {
317 item: Item::from_fen(item_fen)?,
318 from: Location::from_fen(from_fen)?,
319 to: Location::from_fen(to_fen)?,
320 })
321 }
322 ["pb", by_fen, at_fen] => {
323 Some(Event::PickupBomb {
324 by: Mon::from_fen(by_fen)?,
325 at: Location::from_fen(at_fen)?,
326 })
327 }
328 ["pp", by_fen, at_fen] => {
329 Some(Event::PickupPotion {
330 by: Item::from_fen(by_fen)?,
331 at: Location::from_fen(at_fen)?,
332 })
333 }
334 ["pm", mana_fen, by_fen, at_fen] => {
335 Some(Event::PickupMana {
336 mana: Mana::from_fen(mana_fen)?,
337 by: Mon::from_fen(by_fen)?,
338 at: Location::from_fen(at_fen)?,
339 })
340 }
341 ["mf", mon_fen, from_fen, to_fen] => {
342 Some(Event::MonFainted {
343 mon: Mon::from_fen(mon_fen)?,
344 from: Location::from_fen(from_fen)?,
345 to: Location::from_fen(to_fen)?,
346 })
347 }
348 ["md", mana_fen, at_fen] => {
349 Some(Event::ManaDropped {
350 mana: Mana::from_fen(mana_fen)?,
351 at: Location::from_fen(at_fen)?,
352 })
353 }
354 ["sb", from_fen, to_fen] => {
355 Some(Event::SupermanaBackToBase {
356 from: Location::from_fen(from_fen)?,
357 to: Location::from_fen(to_fen)?,
358 })
359 }
360 ["ba", by_fen, from_fen, to_fen] => {
361 Some(Event::BombAttack {
362 by: Mon::from_fen(by_fen)?,
363 from: Location::from_fen(from_fen)?,
364 to: Location::from_fen(to_fen)?,
365 })
366 }
367 ["maw", mon_fen, at_fen] => {
368 Some(Event::MonAwake {
369 mon: Mon::from_fen(mon_fen)?,
370 at: Location::from_fen(at_fen)?,
371 })
372 }
373 ["be", at_fen] => {
374 Some(Event::BombExplosion {
375 at: Location::from_fen(at_fen)?,
376 })
377 }
378 ["nt", color_fen] => {
379 Some(Event::NextTurn {
380 color: Color::from_fen(color_fen)?,
381 })
382 }
383 ["go", winner_fen] => {
384 Some(Event::GameOver {
385 winner: Color::from_fen(winner_fen)?,
386 })
387 }
388 _ => None,
389 }
390 }
391}
392
393impl FenRepresentable for NextInput {
394 fn fen(&self) -> String {
395 format!(
396 "{} {} {}",
397 self.input.fen(),
398 self.kind.fen(),
399 self.actor_mon_item.as_ref().map_or("o".to_string(), |item| item.fen())
400 )
401 }
402}
403
404impl NextInput {
405 fn from_fen(fen: &str) -> Option<Self> {
406 let components: Vec<&str> = fen.split_whitespace().collect();
407 if components.len() != 3 {
408 return None;
409 }
410 let input = Input::from_fen(components[0])?;
411 let kind = NextInputKind::from_fen(components[1])?;
412 let actor_mon_item = if components[2] != "o" {
413 Some(Item::from_fen(components[2])?)
414 } else {
415 None
416 };
417
418 Some(Self::new(input, kind, actor_mon_item))
419 }
420}
421
422impl FenRepresentable for NextInputKind {
423 fn fen(&self) -> String {
424 match self {
425 NextInputKind::MonMove => "mm".to_string(),
426 NextInputKind::ManaMove => "mma".to_string(),
427 NextInputKind::MysticAction => "ma".to_string(),
428 NextInputKind::DemonAction => "da".to_string(),
429 NextInputKind::DemonAdditionalStep => "das".to_string(),
430 NextInputKind::SpiritTargetCapture => "stc".to_string(),
431 NextInputKind::SpiritTargetMove => "stm".to_string(),
432 NextInputKind::SelectConsumable => "sc".to_string(),
433 NextInputKind::BombAttack => "ba".to_string(),
434 }
435 }
436}
437
438impl NextInputKind {
439 fn from_fen(fen: &str) -> Option<Self> {
440 match fen {
441 "mm" => Some(NextInputKind::MonMove),
442 "mma" => Some(NextInputKind::ManaMove),
443 "ma" => Some(NextInputKind::MysticAction),
444 "da" => Some(NextInputKind::DemonAction),
445 "das" => Some(NextInputKind::DemonAdditionalStep),
446 "stc" => Some(NextInputKind::SpiritTargetCapture),
447 "stm" => Some(NextInputKind::SpiritTargetMove),
448 "sc" => Some(NextInputKind::SelectConsumable),
449 "ba" => Some(NextInputKind::BombAttack),
450 _ => None,
451 }
452 }
453}
454
455impl FenRepresentable for Location {
456 fn fen(&self) -> String {
457 format!("{},{}", self.i, self.j)
458 }
459}
460
461impl Location {
462 fn from_fen(fen: &str) -> Option<Self> {
463 let parts: Vec<&str> = fen.split(',').collect();
464 if parts.len() != 2 {
465 return None;
466 }
467 let i = parts[0].parse().ok()?;
468 let j = parts[1].parse().ok()?;
469 Some(Self { i, j })
470 }
471}
472
473impl FenRepresentable for Modifier {
474 fn fen(&self) -> String {
475 match self {
476 Modifier::SelectPotion => "p",
477 Modifier::SelectBomb => "b",
478 Modifier::Cancel => "c",
479 }.to_string()
480 }
481}
482
483impl Modifier {
484 fn from_fen(fen: &str) -> Option<Self> {
485 match fen {
486 "p" => Some(Modifier::SelectPotion),
487 "b" => Some(Modifier::SelectBomb),
488 "c" => Some(Modifier::Cancel),
489 _ => None,
490 }
491 }
492}
493
494impl FenRepresentable for Input {
495 fn fen(&self) -> String {
496 match self {
497 Input::Location(location) => format!("l{}", location.fen()),
498 Input::Modifier(modifier) => format!("m{}", modifier.fen()),
499 }
500 }
501}
502
503impl Input {
504 pub fn fen_from_array(inputs: &[Input]) -> String {
505 inputs.iter()
506 .map(|input| input.fen())
507 .collect::<Vec<_>>()
508 .join(";")
509 }
510
511 pub fn from_fen(fen: &str) -> Option<Self> {
512 fen.chars().next().and_then(|prefix| match prefix {
513 'l' => Location::from_fen(&fen[1..]).map(Input::Location),
514 'm' => Modifier::from_fen(&fen[1..]).map(Input::Modifier),
515 _ => None,
516 })
517 }
518
519 pub fn array_from_fen(fen: &str) -> Vec<Self> {
520 if fen.is_empty() {
521 vec![]
522 } else {
523 fen.split(';')
524 .filter_map(|f| Input::from_fen(f))
525 .collect()
526 }
527 }
528}
529
530impl FenRepresentable for Output {
531 fn fen(&self) -> String {
532 match self {
533 Output::InvalidInput => "i".to_string(),
534 Output::LocationsToStartFrom(locations) => {
535 let mut sorted_locations: Vec<_> = locations.iter().map(|location| location.fen()).collect();
536 sorted_locations.sort();
537 "l".to_owned() + &sorted_locations.join("/")
538 },
539 Output::NextInputOptions(next_inputs) => {
540 let mut sorted_next_inputs: Vec<_> = next_inputs.iter().map(|next_input| next_input.fen()).collect();
541 sorted_next_inputs.sort();
542 "n".to_owned() + &sorted_next_inputs.join("/")
543 },
544 Output::Events(events) => {
545 let mut sorted_events: Vec<_> = events.iter().map(|event| event.fen()).collect();
546 sorted_events.sort();
547 "e".to_owned() + &sorted_events.join("/")
548 },
549 }
550 }
551}
552
553impl Output {
554 pub fn from_fen(fen: &str) -> Option<Self> {
555 let (prefix, data) = fen.split_at(1);
556 match prefix {
557 "i" => Some(Output::InvalidInput),
558 "l" => {
559 let locations = data.split('/').filter_map(|f| Location::from_fen(f)).collect::<Vec<_>>();
560 if locations.len() > 0 {
561 Some(Output::LocationsToStartFrom(locations))
562 } else {
563 None
564 }
565 },
566 "n" => {
567 let next_inputs = data.split('/').filter_map(|f| NextInput::from_fen(f)).collect::<Vec<_>>();
568 if next_inputs.len() > 0 {
569 Some(Output::NextInputOptions(next_inputs))
570 } else {
571 None
572 }
573 },
574 "e" => {
575 let events = data.split('/').filter_map(|f| Event::from_fen(f)).collect::<Vec<_>>();
576 if events.len() > 0 {
577 Some(Output::Events(events))
578 } else {
579 None
580 }
581 },
582 _ => None,
583 }
584 }
585}