mons_rust/models/
fen_representable.rs

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}