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 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 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 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 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}