manabrew_engine/player/actions/
player_action.rs1use serde::{Deserialize, Serialize};
2
3use crate::agent::types::{MainPhaseAction, PlayOption, TargetChoice};
4use crate::agent::PlayerAgent;
5use crate::ids::{CardId, PlayerId};
6use crate::player::PlayerController;
7
8pub const STATIC_ALTERNATIVE_ABILITY_INDEX: usize = usize::MAX;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum PlayerAction {
12 PassPriority,
13 Concede,
14 FinishTargeting,
15 CastSpell(PlayOption),
16 ActivateMana(CardId, Option<usize>, Option<u16>),
17 UndoMana(CardId),
18 ActivateAbility(AbilityRef),
19 PayCost(CardId),
20 PayManaFromPool(ManaChoice),
21 SelectCard(CardId),
22 SelectPlayer(PlayerId),
23 TargetEntity(TargetEntity),
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub struct AbilityRef {
28 pub card_id: CardId,
29 pub ability_index: usize,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33pub struct ManaChoice {
34 pub color_code: u8,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum TargetEntity {
39 Card(CardId),
40 Player(PlayerId),
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum PlayerActionOutcome {
45 Priority(MainPhaseAction),
46 Target(TargetChoice),
47 Pending,
48}
49
50impl PlayerAction {
51 pub fn to_priority_action(
52 self,
53 playable: &[PlayOption],
54 tappable_lands: &[CardId],
55 untappable_lands: &[CardId],
56 activatable: &[(CardId, usize)],
57 ) -> Option<MainPhaseAction> {
58 match self {
59 PlayerAction::PassPriority => Some(MainPhaseAction::Pass),
60 PlayerAction::CastSpell(play) => playable
61 .contains(&play)
62 .then_some(MainPhaseAction::Play(play)),
63 PlayerAction::ActivateMana(card_id, ability_index, express_choice) => tappable_lands
64 .contains(&card_id)
65 .then_some(MainPhaseAction::ActivateMana(
66 card_id,
67 ability_index,
68 express_choice,
69 )),
70 PlayerAction::UndoMana(card_id) => untappable_lands
71 .contains(&card_id)
72 .then_some(MainPhaseAction::UntapMana(card_id)),
73 PlayerAction::ActivateAbility(ability) => activatable
74 .iter()
75 .copied()
76 .find(|(card_id, index)| {
77 *card_id == ability.card_id && *index == ability.ability_index
78 })
79 .map(|(card_id, index)| MainPhaseAction::ActivateAbility(card_id, index)),
80 _ => None,
81 }
82 }
83
84 pub fn to_target_choice(self) -> Option<TargetChoice> {
85 match self {
86 PlayerAction::SelectCard(card_id) => Some(TargetChoice::Card(card_id)),
87 PlayerAction::SelectPlayer(player_id) => Some(TargetChoice::Player(player_id)),
88 PlayerAction::TargetEntity(TargetEntity::Card(card_id)) => {
89 Some(TargetChoice::Card(card_id))
90 }
91 PlayerAction::TargetEntity(TargetEntity::Player(player_id)) => {
92 Some(TargetChoice::Player(player_id))
93 }
94 PlayerAction::FinishTargeting => Some(TargetChoice::None),
95 _ => None,
96 }
97 }
98
99 pub fn run<A: PlayerAgent + ?Sized>(
100 self,
101 _controller: &mut PlayerController<'_, A>,
102 playable: &[PlayOption],
103 tappable_lands: &[CardId],
104 untappable_lands: &[CardId],
105 activatable: &[(CardId, usize)],
106 ) -> PlayerActionOutcome {
107 if let Some(priority) =
108 self.to_priority_action(playable, tappable_lands, untappable_lands, activatable)
109 {
110 return PlayerActionOutcome::Priority(priority);
111 }
112 if let Some(target) = self.to_target_choice() {
113 return PlayerActionOutcome::Target(target);
114 }
115 PlayerActionOutcome::Pending
116 }
117}