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, PartialOrd, Ord, Hash, 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 is_class_level_up: bool,
93    pub produced_mana: Option<String>,
94    pub produced_mana_amount: Option<i32>,
95}
96
97#[derive(Debug, Clone, Default)]
98pub struct PriorityActionSpace {
99    pub playable: Vec<PlayOption>,
100    /// Card ids tappable for mana (engine validation of `ActivateMana`).
101    pub tappable_lands: Vec<CardId>,
102    pub untappable_lands: Vec<CardId>,
103    pub activatable: Vec<ActivatableAction>,
104    pub mana_abilities: Vec<ActivatableAction>,
105}
106
107impl PriorityActionSpace {
108    pub fn is_empty(&self) -> bool {
109        self.playable.is_empty()
110            && self.tappable_lands.is_empty()
111            && self.untappable_lands.is_empty()
112            && self.activatable.is_empty()
113    }
114}
115
116/// The action a player takes when asked to pay an attack cost (Propaganda, Ghostly Prison).
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum CombatCostAction {
119    /// Tap an untapped land to add mana to the pool.
120    TapLand {
121        card_id: CardId,
122        mana_ability_index: Option<usize>,
123        express_choice: Option<u16>,
124    },
125    /// Untap a tapped land and remove its mana from the pool (undo).
126    UntapLand(CardId),
127    /// Pay the cost from the mana pool.
128    Pay,
129    /// Decline to pay — remove this attacker.
130    Decline,
131}
132
133/// The action a player takes when interactively paying a mana cost for a spell.
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub enum ManaCostAction {
136    TapForMana {
137        card_id: CardId,
138        mana_ability_index: Option<usize>,
139        express_choice: Option<u16>,
140    },
141    Untap(CardId),
142    /// Confirm payment from the mana pool. When `auto` is true, the engine
143    /// should complete the payment session using engine auto-pay.
144    Pay {
145        auto: bool,
146    },
147    /// Payment was attempted but could not be completed.
148    AttemptedAndFailed,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct ManaAbilityOption {
153    pub card_id: CardId,
154    pub ability_index: usize,
155    pub description: String,
156    pub cost: Option<String>,
157    pub produced_mana: Option<String>,
158    pub produced_mana_amount: Option<i32>,
159}
160
161/// Java-parity binary choice kinds (`PlayerController.BinaryChoiceType`).
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum BinaryChoiceKind {
164    HeadsOrTails,
165    TapOrUntap,
166    PlayOrDraw,
167    OddsOrEvens,
168    UntapOrLeaveTapped,
169    LeftOrRight,
170    AddOrRemove,
171    IncreaseOrDecrease,
172}
173
174impl BinaryChoiceKind {
175    /// Canonical button labels for each binary choice kind.
176    pub fn labels(self) -> (&'static str, &'static str) {
177        match self {
178            BinaryChoiceKind::HeadsOrTails => ("Heads", "Tails"),
179            BinaryChoiceKind::TapOrUntap => ("Tap", "Untap"),
180            BinaryChoiceKind::PlayOrDraw => ("Play", "Draw"),
181            BinaryChoiceKind::OddsOrEvens => ("Odds", "Evens"),
182            BinaryChoiceKind::UntapOrLeaveTapped => ("Untap", "Leave tapped"),
183            BinaryChoiceKind::LeftOrRight => ("Left", "Right"),
184            BinaryChoiceKind::AddOrRemove => ("Add", "Remove"),
185            BinaryChoiceKind::IncreaseOrDecrease => ("Increase", "Decrease"),
186        }
187    }
188
189    pub fn as_str(self) -> &'static str {
190        match self {
191            BinaryChoiceKind::HeadsOrTails => "HeadsOrTails",
192            BinaryChoiceKind::TapOrUntap => "TapOrUntap",
193            BinaryChoiceKind::PlayOrDraw => "PlayOrDraw",
194            BinaryChoiceKind::OddsOrEvens => "OddsOrEvens",
195            BinaryChoiceKind::UntapOrLeaveTapped => "UntapOrLeaveTapped",
196            BinaryChoiceKind::LeftOrRight => "LeftOrRight",
197            BinaryChoiceKind::AddOrRemove => "AddOrRemove",
198            BinaryChoiceKind::IncreaseOrDecrease => "IncreaseOrDecrease",
199        }
200    }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub enum RollSwapChoice {
205    Power,
206    Toughness,
207}