Skip to main content

manabrew_engine/agent/
types.rs

1use serde::{Deserialize, Serialize};
2
3use crate::ids::{CardId, PlayerId};
4use crate::spellability::AlternativeCost;
5
6/// A game entity that can be a player or a card (permanent).
7/// Used by effects like Proliferate that operate on mixed entity lists.
8/// Mirrors Java's `GameEntity` hierarchy used in `chooseEntitiesForEffect`.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum GameEntity {
11    Player(PlayerId),
12    Card(CardId),
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub struct PlayOption {
17    pub card_id: CardId,
18    pub mode: PlayCardMode,
19    /// Disambiguates between multiple instances of the same alternative cost
20    /// keyword on the same card (e.g. intrinsic `Evoke {2}{U}` at index 0
21    /// versus granted `Evoke {4}` at index 1 when Ashling's static ability
22    /// adds a second Evoke cost). Zero for all other modes.
23    #[serde(default)]
24    pub alt_cost_index: u8,
25}
26
27impl PlayOption {
28    pub fn normal(card_id: CardId) -> Self {
29        Self {
30            card_id,
31            mode: PlayCardMode::Normal,
32            alt_cost_index: 0,
33        }
34    }
35
36    pub fn with_mode(card_id: CardId, mode: PlayCardMode) -> Self {
37        Self {
38            card_id,
39            mode,
40            alt_cost_index: 0,
41        }
42    }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46pub enum PlayCardMode {
47    Normal,
48    BackFaceLand,
49    /// Cast the right split face of a Room card from hand.
50    RoomRightSplit,
51    Alternative(AlternativeCost),
52    /// Alternative cost granted by `Mode$ AlternativeCost` static abilities.
53    StaticAlternative,
54    ForetellExile,
55    /// Unlock a Room door on a permanent already on the battlefield.
56    /// Mirrors Java's `StaticAbilityApiBased` for `ST$ UnlockDoor` which falls
57    /// through to the `CastSpell` branch in the harness (not `ActivateAbility`).
58    UnlockDoor,
59}
60
61/// A target choice that can be a player, a card, or nothing.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63pub enum TargetChoice {
64    Player(PlayerId),
65    Card(CardId),
66    None,
67}
68
69/// The action a player takes during a main phase.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum MainPhaseAction {
72    /// Pass priority / end main phase.
73    Pass,
74    /// Play a card from hand / graveyard / exile / command with a specific cast mode.
75    Play(PlayOption),
76    /// Tap an untapped land on the battlefield to add its mana to the pool.
77    /// Optional ability index selects a specific mana ability (dual lands).
78    ActivateMana(CardId, Option<usize>, Option<u16>),
79    /// Untap a tapped land and remove its mana from the pool (undo tap).
80    UntapMana(CardId),
81    /// Activate an ability on a permanent. (source card, ability index)
82    ActivateAbility(CardId, usize),
83}
84
85#[derive(Debug, Clone)]
86pub struct ActivatableAction {
87    pub card_id: CardId,
88    pub ability_index: usize,
89    pub description: String,
90    pub cost: Option<String>,
91    pub is_mana_ability: bool,
92    pub produced_mana: Option<String>,
93    pub produced_mana_amount: Option<i32>,
94}
95
96#[derive(Debug, Clone, Default)]
97pub struct PriorityActionSpace {
98    pub playable: Vec<PlayOption>,
99    /// Card ids tappable for mana (engine validation of `ActivateMana`).
100    pub tappable_lands: Vec<CardId>,
101    pub untappable_lands: Vec<CardId>,
102    pub activatable: Vec<ActivatableAction>,
103    pub mana_abilities: Vec<ActivatableAction>,
104}
105
106impl PriorityActionSpace {
107    pub fn is_empty(&self) -> bool {
108        self.playable.is_empty()
109            && self.tappable_lands.is_empty()
110            && self.untappable_lands.is_empty()
111            && self.activatable.is_empty()
112    }
113}
114
115/// The action a player takes when asked to pay an attack cost (Propaganda, Ghostly Prison).
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum CombatCostAction {
118    /// Tap an untapped land to add mana to the pool.
119    TapLand {
120        card_id: CardId,
121        mana_ability_index: Option<usize>,
122        express_choice: Option<u16>,
123    },
124    /// Untap a tapped land and remove its mana from the pool (undo).
125    UntapLand(CardId),
126    /// Pay the cost from the mana pool.
127    Pay,
128    /// Decline to pay — remove this attacker.
129    Decline,
130}
131
132/// The action a player takes when interactively paying a mana cost for a spell.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum ManaCostAction {
135    TapForMana {
136        card_id: CardId,
137        mana_ability_index: Option<usize>,
138        express_choice: Option<u16>,
139    },
140    Untap(CardId),
141    /// Confirm payment from the mana pool. When `auto` is true, the engine
142    /// should complete the payment session using engine auto-pay.
143    Pay {
144        auto: bool,
145    },
146    /// Payment was attempted but could not be completed.
147    AttemptedAndFailed,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct ManaAbilityOption {
152    pub card_id: CardId,
153    pub ability_index: usize,
154    pub description: String,
155    pub cost: Option<String>,
156    pub produced_mana: Option<String>,
157    pub produced_mana_amount: Option<i32>,
158}
159
160/// Java-parity binary choice kinds (`PlayerController.BinaryChoiceType`).
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum BinaryChoiceKind {
163    HeadsOrTails,
164    TapOrUntap,
165    PlayOrDraw,
166    OddsOrEvens,
167    UntapOrLeaveTapped,
168    LeftOrRight,
169    AddOrRemove,
170    IncreaseOrDecrease,
171}
172
173impl BinaryChoiceKind {
174    /// Canonical button labels for each binary choice kind.
175    pub fn labels(self) -> (&'static str, &'static str) {
176        match self {
177            BinaryChoiceKind::HeadsOrTails => ("Heads", "Tails"),
178            BinaryChoiceKind::TapOrUntap => ("Tap", "Untap"),
179            BinaryChoiceKind::PlayOrDraw => ("Play", "Draw"),
180            BinaryChoiceKind::OddsOrEvens => ("Odds", "Evens"),
181            BinaryChoiceKind::UntapOrLeaveTapped => ("Untap", "Leave tapped"),
182            BinaryChoiceKind::LeftOrRight => ("Left", "Right"),
183            BinaryChoiceKind::AddOrRemove => ("Add", "Remove"),
184            BinaryChoiceKind::IncreaseOrDecrease => ("Increase", "Decrease"),
185        }
186    }
187
188    pub fn as_str(self) -> &'static str {
189        match self {
190            BinaryChoiceKind::HeadsOrTails => "HeadsOrTails",
191            BinaryChoiceKind::TapOrUntap => "TapOrUntap",
192            BinaryChoiceKind::PlayOrDraw => "PlayOrDraw",
193            BinaryChoiceKind::OddsOrEvens => "OddsOrEvens",
194            BinaryChoiceKind::UntapOrLeaveTapped => "UntapOrLeaveTapped",
195            BinaryChoiceKind::LeftOrRight => "LeftOrRight",
196            BinaryChoiceKind::AddOrRemove => "AddOrRemove",
197            BinaryChoiceKind::IncreaseOrDecrease => "IncreaseOrDecrease",
198        }
199    }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum RollSwapChoice {
204    Power,
205    Toughness,
206}