mons_rust/models/
mons_game.rs

1use crate::*;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone)]
5pub struct MonsGame {
6    pub board: Board,
7    pub white_score: i32,
8    pub black_score: i32,
9    pub active_color: Color,
10    pub actions_used_count: i32,
11    pub mana_moves_count: i32,
12    pub mons_moves_count: i32,
13    pub white_potions_count: i32,
14    pub black_potions_count: i32,
15    pub turn_number: i32,
16}
17
18impl MonsGame {
19    pub fn new() -> Self {
20        Self {
21            board: Board::new(),
22            white_score: 0,
23            black_score: 0,
24            active_color: Color::White,
25            actions_used_count: 0,
26            mana_moves_count: 0,
27            mons_moves_count: 0,
28            white_potions_count: 0,
29            black_potions_count: 0,
30            turn_number: 1,
31        }
32    }
33
34    pub fn with_params(
35        board: Board,
36        white_score: i32,
37        black_score: i32,
38        active_color: Color,
39        actions_used_count: i32,
40        mana_moves_count: i32,
41        mons_moves_count: i32,
42        white_potions_count: i32,
43        black_potions_count: i32,
44        turn_number: i32,
45    ) -> Self {
46        Self {
47            board,
48            white_score,
49            black_score,
50            active_color,
51            actions_used_count,
52            mana_moves_count,
53            mons_moves_count,
54            white_potions_count,
55            black_potions_count,
56            turn_number,
57        }
58    }
59
60    pub fn update_with(&mut self, other_game: &MonsGame) {
61        self.board = Board::new_with_items(other_game.board.items.clone());
62        self.white_score = other_game.white_score;
63        self.black_score = other_game.black_score;
64        self.active_color = other_game.active_color;
65        self.actions_used_count = other_game.actions_used_count;
66        self.mana_moves_count = other_game.mana_moves_count;
67        self.mons_moves_count = other_game.mons_moves_count;
68        self.white_potions_count = other_game.white_potions_count;
69        self.black_potions_count = other_game.black_potions_count;
70        self.turn_number = other_game.turn_number;
71    }
72
73    // MARK: - process input
74
75    pub fn process_input(&mut self, input: Vec<Input>, do_not_apply_events: bool, one_option_enough: bool) -> Output {
76        if self.winner_color().is_some() {
77            return Output::InvalidInput;
78        }
79        if input.is_empty() {
80            return self.suggested_input_to_start_with();
81        }
82        let start_location = match input.get(0) {
83            Some(Input::Location(location)) => *location,
84            _ => return Output::InvalidInput,
85        };
86        let start_item = match self.board.item(start_location) {
87            Some(item) => item.clone(),
88            None => return Output::InvalidInput,
89        };
90        let specific_second_input = input.get(1).cloned();
91        let second_input_options = self.second_input_options(start_location, &start_item, one_option_enough, specific_second_input);
92    
93        let second_input = if specific_second_input.is_none() {
94            if second_input_options.is_empty() {
95                return Output::InvalidInput;
96            } else {
97                return Output::NextInputOptions(second_input_options);
98            }
99        } else {
100            specific_second_input.unwrap()
101        };
102    
103        let target_location = match second_input {
104            Input::Location(location) => location,
105            _ => return Output::InvalidInput,
106        };
107        let second_input_kind = match second_input_options.iter().find(|option| &option.input == &second_input) {
108            Some(option) => option.kind,
109            None => return Output::InvalidInput,
110        };
111    
112        let specific_third_input = input.get(2).cloned();
113        let (mut events, third_input_options) = match self.process_second_input(second_input_kind, start_item.clone(), start_location, target_location, specific_third_input) {
114            Some((events, options)) => (events, options),
115            None => (vec![], vec![]),
116        };
117    
118        if specific_third_input.is_none() {
119            if !third_input_options.is_empty() {
120                return Output::NextInputOptions(third_input_options);
121            } else if !events.is_empty() {
122                return Output::Events(if do_not_apply_events { events.clone() } else { self.apply_and_add_resulting_events(events) });
123            } else {
124                return Output::InvalidInput;
125            }
126        }
127    
128        let specific_third_input = specific_third_input.unwrap();
129    
130        let third_input = match third_input_options.iter().find(|option| &option.input == &specific_third_input) {
131            Some(option) => option,
132            None => return Output::InvalidInput,
133        };
134    
135        let specific_forth_input = input.get(3).cloned();
136        let (forth_events, forth_input_options) = match self.process_third_input(third_input.clone(), start_item, start_location, target_location) {
137            Some((events, options)) => (events, options),
138            None => (vec![], vec![]),
139        };
140        events.extend(forth_events);
141    
142        if specific_forth_input.is_none() {
143            if !forth_input_options.is_empty() {
144                return Output::NextInputOptions(forth_input_options);
145            } else if !events.is_empty() {
146                return Output::Events(if do_not_apply_events { events } else { self.apply_and_add_resulting_events(events) });
147            } else {
148                return Output::InvalidInput;
149            }
150        }
151    
152        let specific_forth_input = specific_forth_input.unwrap();
153    
154        match specific_forth_input {
155            Input::Modifier(modifier) => {
156                let destination_location = match third_input.input {
157                    Input::Location(location) => location,
158                    _ => return Output::InvalidInput,
159                };
160                let forth_input = match forth_input_options.iter().find(|option| &option.input == &specific_forth_input) {
161                    Some(option) => option,
162                    None => return Output::InvalidInput,
163                };
164                if let Some(actor_mon_item) = forth_input.actor_mon_item.clone() {
165                    if let Some(actor_mon) = actor_mon_item.mon() {
166                        match modifier {
167                            Modifier::SelectBomb => events.push(Event::PickupBomb { by: *actor_mon, at: destination_location }),
168                            Modifier::SelectPotion => events.push(Event::PickupPotion { by: actor_mon_item, at: destination_location }),
169                            Modifier::Cancel => return Output::InvalidInput,
170                        }
171                        return Output::Events(if do_not_apply_events { events } else { self.apply_and_add_resulting_events(events) });
172                    }
173                }
174                Output::InvalidInput
175            }
176            _ => Output::InvalidInput,
177        }
178    }
179    
180    
181    // MARK: - process step by step
182
183    fn suggested_input_to_start_with(&mut self) -> Output {
184        let mut suggested_locations: Vec<Location> = Vec::new();
185    
186        for location in self.board.all_mons_locations(self.active_color) {
187            let output = self.process_input(vec![Input::Location(location.clone())], true, true);
188            if matches!(output, Output::NextInputOptions(options) if !options.is_empty()) {
189                suggested_locations.push(location);
190            }
191        }
192        
193        if (!self.player_can_move_mon() && !self.player_can_use_action() || suggested_locations.is_empty()) && self.player_can_move_mana() {
194            for location in self.board.all_free_regular_mana_locations(self.active_color) {
195                let output = self.process_input(vec![Input::Location(location.clone())], true, true);
196                if matches!(output, Output::NextInputOptions(options) if !options.is_empty()) {
197                    suggested_locations.push(location);
198                }
199            }
200        }
201    
202        if suggested_locations.is_empty() {
203            Output::InvalidInput
204        } else {
205            Output::LocationsToStartFrom(suggested_locations)
206        }
207    }
208
209    fn second_input_options(&self, start_location: Location, start_item: &Item, only_one: bool, specific_next: Option<Input>) -> Vec<NextInput> {
210        let specific_location = match specific_next {
211            Some(Input::Location(location)) => Some(location),
212            _ => None,
213        };
214        let start_square = self.board.square(start_location);
215        let mut second_input_options = Vec::new();
216        match start_item {
217            Item::Mon { mon } if mon.color == self.active_color && !mon.is_fainted() => {
218                if self.player_can_move_mon() {
219                    second_input_options.extend(
220                        self.next_inputs(start_location.nearby_locations(), NextInputKind::MonMove, only_one, specific_next.map(|input| match input {
221                            Input::Location(loc) => loc,
222                            _ => start_location,
223                        }), |location| {
224                            let item = self.board.item(location);
225                            let square = self.board.square(location);
226                    
227                            let item_allows = match item {
228                                Some(Item::Mon { .. }) | Some(Item::MonWithMana { .. }) | Some(Item::MonWithConsumable { .. }) => false,
229                                Some(Item::Mana { .. }) => mon.kind == MonKind::Drainer,
230                                Some(Item::Consumable { .. }) => true,
231                                None => true,
232                            };
233                    
234                            item_allows && match square {
235                                Square::Regular | Square::ConsumableBase | Square::ManaBase { .. } | Square::ManaPool { .. } => true,
236                                Square::SupermanaBase => matches!(item, Some(Item::Mana { mana: Mana::Supermana })) && mon.kind == MonKind::Drainer,
237                                Square::MonBase { kind, color } => kind == mon.kind && color == mon.color,
238                            }
239                        }),
240                    );   
241                }
242            
243                if !matches!(start_square, Square::MonBase { .. }) && self.player_can_use_action() {
244                    match mon.kind {
245                        MonKind::Angel | MonKind::Drainer => (),
246                        MonKind::Mystic => {
247                            second_input_options.extend(
248                                self.next_inputs(start_location.reachable_by_mystic_action(), NextInputKind::MysticAction, only_one, specific_location, |location| {
249                                    if let Some(item) = self.board.item(location) {
250                                        if self.protected_by_opponents_angel().contains(&location) {
251                                            return false;
252                                        }
253            
254                                        match item {
255                                            Item::Mon { mon: target_mon } | Item::MonWithMana { mon: target_mon, .. } | Item::MonWithConsumable { mon: target_mon, .. } => {
256                                                mon.color != target_mon.color && !target_mon.is_fainted()
257                                            }
258                                            _ => false,
259                                        }
260                                    } else {
261                                        false
262                                    }
263                                }),
264                            );
265                        }
266                        MonKind::Demon => {
267                            second_input_options.extend(
268                                self.next_inputs(start_location.reachable_by_demon_action(), NextInputKind::DemonAction, only_one, specific_location, |location| {
269                                    if let Some(item) = self.board.item(location) {
270                                        if self.protected_by_opponents_angel().contains(&location) || self.board.item(start_location.location_between(&location)).is_some() {
271                                            return false;
272                                        }
273            
274                                        match item {
275                                            Item::Mon { mon: target_mon } | Item::MonWithMana { mon: target_mon, .. } | Item::MonWithConsumable { mon: target_mon, .. } => {
276                                                mon.color != target_mon.color && !target_mon.is_fainted()
277                                            }
278                                            _ => false,
279                                        }
280                                    } else {
281                                        false
282                                    }
283                                }),
284                            );
285                        }
286                        MonKind::Spirit => {
287                            second_input_options.extend(
288                                self.next_inputs(start_location.reachable_by_spirit_action(), NextInputKind::SpiritTargetCapture, only_one, specific_location, |location| {
289                                    if let Some(item) = self.board.item(location) {
290                                        match item {
291                                            Item::Mon { mon: target_mon } | Item::MonWithMana { mon: target_mon, .. } | Item::MonWithConsumable { mon: target_mon, .. } => {
292                                                !target_mon.is_fainted()
293                                            }
294                                            _ => true,
295                                        }
296                                    } else {
297                                        false
298                                    }
299                                }),
300                            );
301                        },
302                    }
303                }
304            }
305            
306            Item::Mana { mana } if matches!(mana, Mana::Regular(color) if color == &self.active_color) && self.player_can_move_mana() => {
307                second_input_options.extend(
308                    self.next_inputs(start_location.nearby_locations(), NextInputKind::ManaMove, only_one, specific_location, |location| {
309                        let item = self.board.item(location);
310                        let square = self.board.square(location);
311                        match item {
312                            Some(Item::Mon { mon }) => match square {
313                                Square::Regular | Square::ConsumableBase | Square::ManaBase { .. } | Square::ManaPool { .. } => mon.kind == MonKind::Drainer,
314                                Square::SupermanaBase | Square::MonBase { .. } => false,
315                            },
316                            Some(Item::MonWithConsumable { .. }) | Some(Item::Consumable { .. }) | Some(Item::MonWithMana { .. }) | Some(Item::Mana { .. }) => false,
317                            None => matches!(square, Square::Regular | Square::ConsumableBase | Square::ManaBase { .. } | Square::ManaPool { .. }),
318                        }
319                    }),
320                );
321            }
322            Item::MonWithMana { mon, mana } if mon.color == self.active_color && self.player_can_move_mon() => {
323                second_input_options.extend(
324                    self.next_inputs(start_location.nearby_locations(), NextInputKind::MonMove, only_one, specific_location, |location| {
325                        let item = self.board.item(location);
326                        let square = self.board.square(location);
327    
328                        match item {
329                            Some(Item::Mon { .. }) | Some(Item::MonWithMana { .. }) | Some(Item::MonWithConsumable { .. }) => false,
330                            Some(Item::Consumable { .. }) | Some(Item::Mana { .. }) => true,
331                            None => match square {
332                                Square::Regular | Square::ConsumableBase | Square::ManaBase { .. } | Square::ManaPool { .. } => true,
333                                Square::SupermanaBase => *mana == Mana::Supermana,
334                                Square::MonBase { .. } => false,
335                            },
336                        }
337                    }),
338                );
339            }
340            Item::MonWithConsumable { mon, consumable } if mon.color == self.active_color => {
341                if self.player_can_move_mon() {
342                    second_input_options.extend(
343                        self.next_inputs(start_location.nearby_locations(), NextInputKind::MonMove, only_one, specific_location, |location| {
344                            let item = self.board.item(location);
345                            let square = self.board.square(location);
346    
347                            match item {
348                                Some(Item::Mon { .. }) | Some(Item::Mana { .. }) | Some(Item::MonWithMana { .. }) | Some(Item::MonWithConsumable { .. }) => false,
349                                Some(Item::Consumable { .. }) => true,
350                                None => matches!(square, Square::Regular | Square::ConsumableBase | Square::ManaBase { .. } | Square::ManaPool { .. }),
351                            }
352                        }),
353                    );
354                }
355    
356                if matches!(consumable, Consumable::Bomb) {
357                    second_input_options.extend(
358                        self.next_inputs(start_location.reachable_by_bomb(), NextInputKind::BombAttack, only_one, specific_location, |location| {
359                            self.board.item(location).map_or(false, |item| {
360                                match item {
361                                    Item::Mon { mon: target_mon } | Item::MonWithMana { mon: target_mon, .. } | Item::MonWithConsumable { mon: target_mon, .. } => {
362                                        mon.color != target_mon.color && !target_mon.is_fainted()
363                                    }
364                                    _ => false,
365                                }
366                            })
367                        }),
368                    );
369                }
370            }
371            _ => (),
372        }
373    
374        second_input_options
375    }
376    
377    fn process_second_input(&mut self, kind: NextInputKind, start_item: Item, start_location: Location, target_location: Location, specific_next: Option<Input>) -> Option<(Vec<Event>, Vec<NextInput>)> {
378        let _specific_location = match specific_next {
379            Some(Input::Location(location)) => Some(location),
380            _ => None,
381        };
382    
383        let mut third_input_options = Vec::new();
384        let mut events = Vec::new();
385        let target_square = self.board.square(target_location);
386        let target_item = self.board.item(target_location);
387    
388        match kind {
389            NextInputKind::MonMove => {
390                if start_item.mon().is_none() { return None; }
391                events.push(Event::MonMove {
392                    item: start_item.clone(),
393                    from: start_location,
394                    to: target_location,
395                });
396                
397                if let Some(target_item) = self.board.item(target_location).cloned() {
398                    match target_item {
399                        Item::Mon { .. } | Item::MonWithMana { .. } | Item::MonWithConsumable { .. } => return None,
400                        Item::Mana { mana } => {
401                            if let Some(start_mana) = start_item.mana() {
402                                match start_mana {
403                                    Mana::Supermana => {
404                                        events.push(Event::SupermanaBackToBase {
405                                            from: start_location,
406                                            to: self.board.supermana_base(),
407                                        });
408                                    }
409                                    _ => {
410                                        events.push(Event::ManaDropped {
411                                            mana: start_mana.clone(),
412                                            at: start_location,
413                                        });
414                                    }
415                                }
416                            }
417                            if let Some(mon) = start_item.mon() {
418                                events.push(Event::PickupMana {
419                                    mana,
420                                    by: *mon,
421                                    at: target_location,
422                                });
423                            }
424                        },
425                        Item::Consumable { consumable } => match consumable {
426                            Consumable::Bomb | Consumable::Potion => return None,
427                            Consumable::BombOrPotion => {
428                                if start_item.consumable().is_some() || start_item.mana().is_some() {
429                                    events.push(Event::PickupPotion {
430                                        by: start_item,
431                                        at: target_location,
432                                    });
433                                } else {
434                                    third_input_options.push(NextInput::new(
435                                        Input::Modifier(Modifier::SelectBomb),
436                                        NextInputKind::SelectConsumable,
437                                        Some(start_item.clone()),
438                                    ));
439                                    third_input_options.push(NextInput::new(
440                                        Input::Modifier(Modifier::SelectPotion),
441                                        NextInputKind::SelectConsumable,
442                                        Some(start_item),
443                                    ));
444                                }
445                            },
446                        },
447                    }
448                }
449        
450                match target_square {
451                    Square::ManaPool { .. } => {
452                        if let Some(mana_in_hand) = start_item.mana() {
453                            events.push(Event::ManaScored { mana: *mana_in_hand, at: target_location });
454                        }
455                    }
456                    _ => (),
457                }
458            },
459            NextInputKind::ManaMove => {
460                let mana = match start_item {
461                    Item::Mana { mana } => mana,
462                    _ => return None,
463                };
464                events.push(Event::ManaMove {
465                    mana,
466                    from: start_location,
467                    to: target_location,
468                });
469        
470                if let Some(target_item) = self.board.item(target_location) {
471                    match target_item {
472                        Item::Mon { mon } => {
473                            events.push(Event::PickupMana {
474                                mana,
475                                by: *mon,
476                                at: target_location,
477                            });
478                        },
479                        Item::Mana { .. } | Item::Consumable { .. } | Item::MonWithMana { .. } | Item::MonWithConsumable { .. } => return None,
480                    }
481                }
482        
483                match target_square {
484                    Square::ManaBase { .. } | Square::ConsumableBase | Square::Regular => (),
485                    Square::ManaPool { color: _ } => {
486                        events.push(Event::ManaScored {
487                            mana,
488                            at: target_location,
489                        });
490                    },
491                    Square::MonBase { .. } | Square::SupermanaBase => return None,
492                }
493            },
494            NextInputKind::MysticAction => {
495                let start_mon = match start_item {
496                    Item::Mon { mon } => mon,
497                    _ => return None,
498                };
499                events.push(Event::MysticAction {
500                    mystic: start_mon,
501                    from: start_location,
502                    to: target_location,
503                });
504            
505                if let Some(target_item) = self.board.item(target_location) {
506                    match target_item {
507                        Item::Mon { mon: target_mon } | Item::MonWithMana { mon: target_mon, .. } | Item::MonWithConsumable { mon: target_mon, .. } => {
508                            events.push(Event::MonFainted {
509                                mon: *target_mon,
510                                from: target_location,
511                                to: self.board.base(Mon { kind: target_mon.kind, color: target_mon.color, cooldown: target_mon.cooldown }),
512                            });
513            
514                            if let Item::MonWithMana { mana, .. } = target_item {
515                                match mana {
516                                    Mana::Regular(_) => events.push(Event::ManaDropped { mana: *mana, at: target_location }),
517                                    Mana::Supermana => events.push(Event::SupermanaBackToBase {
518                                        from: target_location,
519                                        to: self.board.supermana_base(),
520                                    }),
521                                }
522                            }
523            
524                            if let Item::MonWithConsumable { consumable, .. } = target_item {
525                                match consumable {
526                                    Consumable::Bomb => {
527                                        events.push(Event::BombExplosion { at: target_location });
528                                    },
529                                    Consumable::Potion | Consumable::BombOrPotion => return None,
530                                }
531                            }
532                        },
533                        Item::Consumable { .. } | Item::Mana { .. } => return None,
534                    }
535                }
536            },
537            NextInputKind::DemonAction => {
538                let start_mon = match start_item {
539                    Item::Mon { mon } => mon,
540                    _ => return None,
541                };
542                events.push(Event::DemonAction {
543                    demon: start_mon,
544                    from: start_location,
545                    to: target_location,
546                });
547                let mut requires_additional_step = false;
548            
549                if let Some(target_item) = self.board.item(target_location) {
550                    match target_item {
551                        Item::Mana { .. } | Item::Consumable { .. } => return None,
552                        Item::Mon { mon: target_mon } | Item::MonWithMana { mon: target_mon, .. } | Item::MonWithConsumable { mon: target_mon, .. } => {
553                            events.push(Event::MonFainted {
554                                mon: *target_mon,
555                                from: target_location,
556                                to: self.board.base(Mon { kind: target_mon.kind, color: target_mon.color, cooldown: target_mon.cooldown }),
557                            });
558            
559                            if let Item::MonWithMana { mana, .. } = target_item {
560                                match mana {
561                                    Mana::Regular(_) => {
562                                        requires_additional_step = true;
563                                        events.push(Event::ManaDropped { mana: *mana, at: target_location });
564                                    },
565                                    Mana::Supermana => events.push(Event::SupermanaBackToBase {
566                                        from: target_location,
567                                        to: self.board.supermana_base(),
568                                    }),
569                                }
570                            }
571            
572                            if let Item::MonWithConsumable { consumable, .. } = target_item {
573                                match consumable {
574                                    Consumable::Bomb => {
575                                        events.push(Event::BombExplosion { at: target_location });
576                                        events.push(Event::MonFainted {
577                                            mon: start_mon,
578                                            from: target_location,
579                                            to: self.board.base(Mon { kind: start_mon.kind, color: start_mon.color, cooldown: start_mon.cooldown }),
580                                        });
581                                    },
582                                    Consumable::Potion | Consumable::BombOrPotion => return None,
583                                }
584                            }
585                        },
586                    }
587                }
588            
589                match target_square {
590                    Square::Regular | Square::ConsumableBase | Square::ManaBase { .. } | Square::ManaPool { .. } => (),
591                    Square::SupermanaBase | Square::MonBase { .. } => requires_additional_step = true,
592                }
593            
594                if requires_additional_step {
595                    let nearby_locations = target_location.nearby_locations();
596                    for location in nearby_locations.iter() {
597                        let item = self.board.item(*location);
598                        let square = self.board.square(*location);
599            
600                        let is_valid_location = item.is_none() || matches!(item, Some(Item::Consumable { .. }));
601            
602                        if is_valid_location {
603                            match square {
604                                Square::Regular | Square::ConsumableBase | Square::ManaBase { .. } | Square::ManaPool { .. } => {
605                                    third_input_options.push(NextInput {
606                                        input: Input::Location(*location),
607                                        kind: NextInputKind::DemonAdditionalStep,
608                                        actor_mon_item: None,
609                                    });
610                                },
611                                Square::MonBase { kind, color } => {
612                                    if start_mon.kind == kind && start_mon.color == color {
613                                        third_input_options.push(NextInput {
614                                            input: Input::Location(*location),
615                                            kind: NextInputKind::DemonAdditionalStep,
616                                            actor_mon_item: None,
617                                        });
618                                    }
619                                },
620                                Square::SupermanaBase => (),
621                            }
622                        }
623                    }
624                }
625            },
626            
627            NextInputKind::SpiritTargetCapture => {
628                if target_item.is_none() { return None; }
629                let target_mon = target_item.as_ref().and_then(|item| item.mon());
630                let target_mana = target_item.as_ref().and_then(|item| item.mana());
631                third_input_options.append(&mut self.next_inputs(target_location.nearby_locations(), NextInputKind::SpiritTargetMove, false, None, |location| {
632                    let destination_item = self.board.item(location);
633                    let destination_square = self.board.square(location);
634            
635                    let valid_destination = match destination_item {
636                        Some(Item::Mon { mon: destination_mon }) => match target_item {
637                            Some(Item::Mon { .. }) | Some(Item::MonWithMana { .. }) | Some(Item::MonWithConsumable { .. }) => false,
638                            Some(Item::Mana { .. }) => destination_mon.kind == MonKind::Drainer && !destination_mon.is_fainted(),
639                            Some(Item::Consumable { consumable: target_consumable }) => *target_consumable == Consumable::BombOrPotion,
640                            None => false,
641                        },
642                        Some(Item::Mana { .. }) => matches!(target_item, Some(Item::Mon { mon: target_mon }) if target_mon.kind == MonKind::Drainer && !target_mon.is_fainted()),
643                        Some(Item::MonWithMana { .. }) | Some(Item::MonWithConsumable { .. }) => match target_item {
644                            Some(Item::Mon { .. }) | Some(Item::MonWithMana { .. }) | Some(Item::MonWithConsumable { .. }) => false,
645                            Some(Item::Mana { .. }) => false,
646                            Some(Item::Consumable { consumable: target_consumable }) => *target_consumable == Consumable::BombOrPotion,
647                            None => false,
648                        },
649                        Some(Item::Consumable { consumable: destination_consumable }) => matches!(target_item, Some(Item::Mon { .. }) | Some(Item::MonWithMana { .. }) | Some(Item::MonWithConsumable { .. }) if *destination_consumable == Consumable::BombOrPotion),
650                        None => true,
651                    };
652            
653                    if valid_destination {
654                        match destination_square {
655                            Square::Regular | Square::ConsumableBase | Square::ManaBase { .. } | Square::ManaPool { .. } => true,
656                            Square::SupermanaBase => {
657                                target_mana == Some(&Mana::Supermana) || (matches!(target_mon.map(|mon| mon.kind), Some(MonKind::Drainer)) && matches!(destination_item, Some(Item::Mana { mana: Mana::Supermana })))
658                            },
659                            Square::MonBase { kind, color } => {
660                                if let Some(mon) = target_mon {
661                                    mon.kind == kind && mon.color == color && target_mana.is_none() && target_item.as_ref().and_then(|item| item.consumable()).is_none()
662                                } else {
663                                    false
664                                }
665                            },
666                        }
667                    } else {
668                        false
669                    }
670                }));
671            },
672            
673            NextInputKind::BombAttack => {
674                let start_mon = start_item.mon().unwrap();
675
676                events.push(Event::BombAttack {
677                    by: start_mon.clone(),
678                    from: start_location,
679                    to: target_location,
680                });
681            
682                if let Some(target_item) = target_item {
683                    match target_item {
684                        Item::Mon { mon } | Item::MonWithMana { mon, .. } | Item::MonWithConsumable { mon, .. } => {
685                            events.push(Event::MonFainted {
686                                mon: *mon,
687                                from: target_location,
688                                to: self.board.base(*mon),
689                            });
690            
691                            if let Item::MonWithMana { mana, .. } = target_item {
692                                match mana {
693                                    Mana::Regular(_) => events.push(Event::ManaDropped {
694                                        mana: *mana,
695                                        at: target_location,
696                                    }),
697                                    Mana::Supermana => events.push(Event::SupermanaBackToBase {
698                                        from: target_location,
699                                        to: self.board.supermana_base(),
700                                    }),
701                                }
702                            }
703            
704                            if let Item::MonWithConsumable { consumable, .. } = target_item {
705                                match consumable {
706                                    Consumable::Bomb => {
707                                        events.push(Event::BombExplosion {
708                                            at: target_location,
709                                        });
710                                    },
711                                    Consumable::Potion | Consumable::BombOrPotion => return None,
712                                }
713                            }
714                        },
715                        Item::Mana { .. } | Item::Consumable { .. } => return None,
716
717                    }
718                }
719            },            
720            _ => (),
721        }
722    
723        Some((events, third_input_options))
724    }
725    
726    fn process_third_input(&mut self, third_input: NextInput, start_item: Item, _start_location: Location, target_location: Location) -> Option<(Vec<Event>, Vec<NextInput>)> {
727        let target_item = self.board.item(target_location);
728        let mut forth_input_options = Vec::new();
729        let mut events = Vec::new();
730    
731        match third_input.kind {
732            NextInputKind::SpiritTargetMove => {
733                if let Input::Location(destination_location) = third_input.input {
734                    if let Some(target_item) = target_item {
735                        let destination_item = self.board.item(destination_location);
736                        let destination_square = self.board.square(destination_location);
737    
738                        events.push(Event::SpiritTargetMove { item: target_item.clone(), from: target_location, to: destination_location });
739    
740                        if let Some(destination_item) = destination_item {
741                            match target_item {
742                                Item::Mon { mon: travelling_mon } => match destination_item {
743                                    Item::Mon { .. } | Item::MonWithMana { .. } | Item::MonWithConsumable { .. } => return None,
744                                    Item::Mana { mana: destination_mana } => {
745                                        events.push(Event::PickupMana { mana: *destination_mana, by: *travelling_mon, at: destination_location });
746                                    },
747                                    Item::Consumable { consumable: destination_consumable } => match destination_consumable {
748                                        Consumable::Potion | Consumable::Bomb => return None,
749                                        Consumable::BombOrPotion => {
750                                            forth_input_options.push(NextInput::new(Input::Modifier(Modifier::SelectBomb), NextInputKind::SelectConsumable, Some(target_item.clone())));
751                                            forth_input_options.push(NextInput::new(Input::Modifier(Modifier::SelectPotion), NextInputKind::SelectConsumable, Some(target_item.clone())));
752                                        },
753                                    },
754                                },
755                                Item::Mana { mana: travelling_mana } => match destination_item {
756                                    Item::Mana { .. } | Item::MonWithMana { .. } | Item::MonWithConsumable { .. } | Item::Consumable { .. } => return None,
757                                    Item::Mon { mon: destination_mon } => {
758                                        events.push(Event::PickupMana { mana: *travelling_mana, by: *destination_mon, at: destination_location });
759                                    },
760                                },
761                                Item::MonWithMana { .. } | Item::MonWithConsumable { .. } => match destination_item {
762                                    Item::Mon { .. } | Item::Mana { .. } | Item::MonWithMana { .. } | Item::MonWithConsumable { .. } => return None,
763                                    Item::Consumable { consumable: destination_consumable } => match destination_consumable {
764                                        Consumable::Potion | Consumable::Bomb => return None,
765                                        Consumable::BombOrPotion => {
766                                            events.push(Event::PickupPotion { by: target_item.clone(), at: destination_location });
767                                        },
768                                    },
769                                },
770                                Item::Consumable { consumable: travelling_consumable } => match destination_item {
771                                    Item::Mana { .. } | Item::Consumable { .. } => return None,
772                                    Item::Mon { .. } => {
773                                        forth_input_options.push(NextInput::new(Input::Modifier(Modifier::SelectBomb), NextInputKind::SelectConsumable, Some(destination_item.clone())));
774                                        forth_input_options.push(NextInput::new(Input::Modifier(Modifier::SelectPotion), NextInputKind::SelectConsumable, Some(destination_item.clone())));
775                                    },
776                                    Item::MonWithMana { .. } | Item::MonWithConsumable { .. } => match travelling_consumable {
777                                        Consumable::Potion | Consumable::Bomb => return None,
778                                        Consumable::BombOrPotion => {
779                                            events.push(Event::PickupPotion { by: destination_item.clone(), at: destination_location });
780                                        },
781                                    },
782                                },
783                            }
784                        }
785    
786                        if matches!(destination_square, Square::ManaPool { .. }) {
787                            if let Some(mana) = target_item.mana() {
788                                events.push(Event::ManaScored { mana: *mana, at: destination_location });
789                            }
790                        }
791                    } else {
792                        return None;
793                    }
794                } else {
795                    return None;
796                }
797            },
798            NextInputKind::DemonAdditionalStep => {
799                if let Input::Location(destination_location) = third_input.input {
800                    if let Some(demon) = start_item.mon() {
801                        events.push(Event::DemonAdditionalStep { demon: *demon, from: target_location, to: destination_location });
802    
803                        if let Some(item) = self.board.item(destination_location) {
804                            if let Item::Consumable { consumable } = item {
805                                match consumable {
806                                    Consumable::Potion | Consumable::Bomb => return None,
807                                    Consumable::BombOrPotion => {
808                                        forth_input_options.push(NextInput::new(Input::Modifier(Modifier::SelectBomb), NextInputKind::SelectConsumable, Some(start_item.clone())));
809                                        forth_input_options.push(NextInput::new(Input::Modifier(Modifier::SelectPotion), NextInputKind::SelectConsumable, Some(start_item.clone())));
810                                    },
811                                }
812                            }
813                        }
814                    } else {
815                        return None;
816                    }
817                } else {
818                    return None;
819                }
820            },
821            NextInputKind::SelectConsumable => {
822                if let Input::Modifier(modifier) = third_input.input {
823                    if let Some(mon) = start_item.mon() {
824                        match modifier {
825                            Modifier::SelectBomb => {
826                                events.push(Event::PickupBomb { by: *mon, at: target_location });
827                            },
828                            Modifier::SelectPotion => {
829                                events.push(Event::PickupPotion { by: start_item.clone(), at: target_location });
830                            },
831                            Modifier::Cancel => return None,
832                        }
833                    } else {
834                        return None;
835                    }
836                } else {
837                    return None;
838                }
839            },
840            _ => return None,
841        }
842    
843        Some((events, forth_input_options))
844    }    
845
846    // MARK: - apply events
847
848    pub fn apply_and_add_resulting_events(&mut self, events: Vec<Event>) -> Vec<Event> {
849        let mut extra_events = Vec::new();
850        for event in &events {
851            match event {
852                Event::MonMove { item, from, to } => {
853                    self.mons_moves_count += 1;
854                    self.board.remove_item(*from);
855                    self.board.put(item.clone(), *to);
856                }
857                Event::ManaMove { mana, from, to } => {
858                    self.mana_moves_count += 1;
859                    self.board.remove_item(*from);
860                    self.board.put(Item::Mana { mana: *mana }, *to);
861                }
862                Event::ManaScored { mana, at } => {
863                    let score = mana.score(self.active_color);
864                    if self.active_color == Color::White {
865                        self.white_score += score;
866                    } else {
867                        self.black_score += score;
868                    }
869                    if let Some(item) = self.board.item(*at) {
870                        if let Some(mon) = item.mon() {
871                            self.board.put(Item::Mon { mon: mon.clone() }, *at);
872                        } else {
873                            self.board.remove_item(*at);
874                        }
875                    }
876                }
877                Event::MysticAction { mystic: _, from: _, to } => {
878                    if self.actions_used_count >= Config::ACTIONS_PER_TURN {
879                        if self.active_color == Color::White {
880                            self.white_potions_count -= 1;
881                        } else {
882                            self.black_potions_count -= 1;
883                        }
884                    } else {
885                        self.actions_used_count += 1;
886                    }
887                    self.board.remove_item(*to);
888                }
889                Event::DemonAction { demon, from, to } => {
890                    if self.actions_used_count >= Config::ACTIONS_PER_TURN {
891                        if self.active_color == Color::White {
892                            self.white_potions_count -= 1;
893                        } else {
894                            self.black_potions_count -= 1;
895                        }
896                    } else {
897                        self.actions_used_count += 1;
898                    }
899                    self.board.remove_item(*from);
900                    self.board.put(Item::Mon { mon: demon.clone() }, *to);
901                }
902                Event::DemonAdditionalStep { demon, from: _, to } => {
903                    self.board.put(Item::Mon { mon: demon.clone() }, *to);
904                }
905                Event::SpiritTargetMove { item, from, to } => {
906                    if self.actions_used_count >= Config::ACTIONS_PER_TURN {
907                        if self.active_color == Color::White {
908                            self.white_potions_count -= 1;
909                        } else {
910                            self.black_potions_count -= 1;
911                        }
912                    } else {
913                        self.actions_used_count += 1;
914                    }
915                    self.board.remove_item(*from);
916                    self.board.put(item.clone(), *to);
917                }
918                Event::PickupBomb { by, at } => {
919                    self.board.put(Item::MonWithConsumable { mon: by.clone(), consumable: Consumable::Bomb }, *at);
920                }
921                Event::PickupPotion { by, at } => {
922                    let mon_color = if let Some(mon) = by.mon() {
923                        mon.color
924                    } else {
925                        continue;
926                    };
927                    if mon_color == Color::White {
928                        self.white_potions_count += 1;
929                    } else {
930                        self.black_potions_count += 1;
931                    }
932                    self.board.put(by.clone(), *at);
933                }
934                Event::PickupMana { mana, by, at } => {
935                    self.board.put(Item::MonWithMana { mon: by.clone(), mana: *mana }, *at);
936                }
937                Event::MonFainted { mon, from: _, to } => {
938                    let mut fainted_mon = mon.clone();
939                    fainted_mon.faint();
940                    self.board.put(Item::Mon { mon: fainted_mon }, *to);
941                }
942                Event::ManaDropped { mana, at } => {
943                    self.board.put(Item::Mana { mana: *mana }, *at);
944                }
945                Event::SupermanaBackToBase { from: _, to } => {
946                    self.board.put(Item::Mana { mana: Mana::Supermana }, *to);
947                }
948                Event::BombAttack { by, from, to } => {
949                    self.board.remove_item(*to);
950                    self.board.put(Item::Mon { mon: by.clone() }, *from);
951                }
952                Event::BombExplosion { at } => {
953                    self.board.remove_item(*at);
954                }
955                Event::MonAwake { .. } | Event::GameOver { .. } | Event::NextTurn { .. } => {}
956            }
957        }
958    
959        if let Some(winner) = self.winner_color() {
960            extra_events.push(Event::GameOver { winner });
961        } else if self.is_first_turn() && !self.player_can_move_mon() ||
962                  !self.is_first_turn() && !self.player_can_move_mana() ||
963                  !self.is_first_turn() && !self.player_can_move_mon() && self.board.find_mana(self.active_color).is_none() {
964            self.active_color = self.active_color.other();
965            self.turn_number += 1;
966            self.reset_turn_state();
967            extra_events.push(Event::NextTurn { color: self.active_color });
968        
969            for mon_location in self.board.fainted_mons_locations(self.active_color) {
970                if let Some(item) = self.board.item(mon_location) {
971                    if let Some(mut mon) = item.mon().cloned() {
972                        mon.decrease_cooldown();
973                        if !mon.is_fainted() {
974                            extra_events.push(Event::MonAwake { mon: mon.clone(), at: mon_location });
975                        }
976                        self.board.put(Item::Mon { mon: mon.clone() }, mon_location);
977                    }                    
978                }
979            }
980        }
981        
982        events.into_iter().chain(extra_events.into_iter()).collect()
983    }
984    
985    fn reset_turn_state(&mut self) {
986        self.actions_used_count = 0;
987        self.mana_moves_count = 0;
988        self.mons_moves_count = 0;
989    }
990
991    // MARK: - helpers
992    pub fn next_inputs<F>(&self, locations: Vec<Location>, kind: NextInputKind, only_one: bool, specific: Option<Location>, filter: F) -> Vec<NextInput> where F: Fn(Location) -> bool {
993        if let Some(specific_location) = specific {
994            if locations.contains(&specific_location) && filter(specific_location) {
995                return vec![NextInput { input: Input::Location(specific_location), kind, actor_mon_item: None }];
996            } else {
997                return vec![];
998            }
999        } else if only_one {
1000            if let Some(one) = locations.into_iter().find(|&loc| filter(loc)) {
1001                return vec![NextInput { input: Input::Location(one), kind, actor_mon_item: None }];
1002            } else {
1003                return vec![];
1004            }
1005        } else {
1006            return locations.into_iter().filter_map(|loc| {
1007                if filter(loc) {
1008                    Some(NextInput { input: Input::Location(loc), kind, actor_mon_item: None })
1009                } else {
1010                    None
1011                }
1012            }).collect();
1013        }
1014    }
1015
1016    pub fn available_move_kinds(&self) -> HashMap<AvailableMoveKind, i32> {
1017        let mut moves = HashMap::new();
1018        moves.insert(AvailableMoveKind::MonMove, Config::MONS_MOVES_PER_TURN - self.mons_moves_count);
1019        moves.insert(AvailableMoveKind::Action, 0);
1020        moves.insert(AvailableMoveKind::Potion, 0);
1021        moves.insert(AvailableMoveKind::ManaMove, 0);
1022
1023        if self.turn_number == 1 {
1024            return moves;
1025        }
1026
1027        moves.insert(AvailableMoveKind::Action, Config::ACTIONS_PER_TURN - self.actions_used_count);
1028        moves.insert(AvailableMoveKind::Potion, self.player_potions_count());
1029        moves.insert(AvailableMoveKind::ManaMove, Config::MANA_MOVES_PER_TURN - self.mana_moves_count);
1030
1031        moves
1032    }
1033
1034    pub fn winner_color(&self) -> Option<Color> {
1035        if self.white_score >= Config::TARGET_SCORE {
1036            Some(Color::White)
1037        } else if self.black_score >= Config::TARGET_SCORE {
1038            Some(Color::Black)
1039        } else {
1040            None
1041        }
1042    }
1043
1044    pub fn is_later_than(&self, game: &MonsGame) -> bool {
1045        if self.turn_number > game.turn_number {
1046            true
1047        } else if self.turn_number == game.turn_number {
1048            self.player_potions_count() < game.player_potions_count() ||
1049            self.actions_used_count > game.actions_used_count ||
1050            self.mana_moves_count > game.mana_moves_count ||
1051            self.mons_moves_count > game.mons_moves_count ||
1052            self.board.fainted_mons_locations(self.active_color.other()).len() > game.board.fainted_mons_locations(game.active_color.other()).len()
1053        } else {
1054            false
1055        }
1056    }
1057
1058    pub fn is_first_turn(&self) -> bool {
1059        self.turn_number == 1 
1060    }
1061
1062    pub fn player_potions_count(&self) -> i32 {
1063        match self.active_color {
1064            Color::White => self.white_potions_count,
1065            Color::Black => self.black_potions_count,
1066        }
1067    }
1068
1069    pub fn player_can_move_mon(&self) -> bool {
1070        self.mons_moves_count < Config::MONS_MOVES_PER_TURN 
1071    }
1072
1073    pub fn player_can_move_mana(&self) -> bool {
1074        !self.is_first_turn() && self.mana_moves_count < Config::MANA_MOVES_PER_TURN 
1075    }
1076
1077    pub fn player_can_use_action(&self) -> bool {
1078        !self.is_first_turn() && (self.player_potions_count() > 0 || self.actions_used_count < Config::ACTIONS_PER_TURN) 
1079    }
1080
1081    pub fn protected_by_opponents_angel(&self) -> std::collections::HashSet<Location> {
1082        if let Some(location) = self.board.find_awake_angel(self.active_color.other()) {
1083            let protected: Vec<Location> = location.nearby_locations();
1084            protected.into_iter().collect()
1085        } else {
1086            std::collections::HashSet::new()
1087        }
1088    }
1089}