Skip to main content

manabrew_engine/ability/effects/
skip_phase_effect.rs

1use super::{resolve_defined_players, EffectContext};
2
3/// Create a skip-phase effect for use by static or replacement abilities.
4/// Mirrors Java's `SkipPhaseEffect.createSkipPhaseEffect(...)`.
5///
6/// Creates an effect that will cause the specified player to skip the
7/// specified phase on their next turn. Used by cards like Stasis,
8/// Teferi's Protection, etc.
9pub fn create_skip_phase_effect(
10    game: &mut crate::game::GameState,
11    player: crate::ids::PlayerId,
12    phase: &str,
13) {
14    match phase {
15        "Draw" => game.player_mut(player).skip_next_draw = true,
16        "Combat" | "BeginCombat" => game.player_mut(player).skip_next_combat = true,
17        "Untap" => game.player_mut(player).skip_next_untap = true,
18        _ => {
19            eprintln!("SkipPhaseEffect: Unknown phase to skip: {:?}", phase);
20        }
21    }
22}
23
24/// Run the skip-phase effect (entry point for non-resolve contexts).
25/// Mirrors Java's `SkipPhaseEffect.run(SpellAbility)`.
26///
27/// Parses the Phase$ and Defined$ parameters and applies the skip.
28pub fn run(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
29    use crate::ability::spell_ability_effect::SpellAbilityEffect;
30    SkipPhaseEffect::resolve(ctx, sa);
31}
32
33/// Resolve `SP$ SkipPhase` — make a player skip their next occurrence of a phase.
34///
35/// Mirrors Java `SkipPhaseEffect.java`.
36/// Sets per-player phase skip flags. The game loop checks these before
37/// entering each phase and skips accordingly.
38///
39/// # Card script examples
40/// ```text
41/// A:SP$ SkipPhase | Defined$ Opponent | Phase$ Draw
42/// A:SP$ SkipPhase | Defined$ You | Phase$ Combat
43/// A:SP$ SkipPhase | Defined$ Opponent | Phase$ Untap
44/// ```
45/// Struct form of this effect so it can participate in the
46/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
47/// `SkipPhaseEffect` class extending `SpellAbilityEffect`.
48#[manabrew_engine_macros::spell_effect(SkipPhaseEffect)]
49fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
50    let controller = sa.activating_player;
51
52    let phase = sa
53        .ir
54        .phase_text
55        .as_deref()
56        .or(sa.ir.step_text.as_deref())
57        .unwrap_or("");
58
59    let defined = sa.defined().unwrap_or("You");
60
61    let targets = resolve_defined_players(defined, controller, ctx.game);
62    for target in targets {
63        if !ctx.game.player(target).is_alive() {
64            continue;
65        }
66        match phase {
67            "Draw" => ctx.game.player_set_skip_draw(target),
68            "Combat" | "BeginCombat" => ctx.game.player_set_skip_combat(target),
69            "Untap" => ctx.game.player_set_skip_untap(target),
70            _ => {
71                let err = crate::ability::IllegalAbilityException::new(format!(
72                    "Unknown phase to skip: {:?}",
73                    phase
74                ));
75                eprintln!("{}", err);
76            }
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use std::collections::HashMap;
84
85    use super::SkipPhaseEffect;
86    use crate::ability::effects::EffectContext;
87    use crate::ability::spell_ability_effect::SpellAbilityEffect;
88    use crate::agent::PassAgent;
89    use crate::game::GameState;
90    use crate::ids::PlayerId;
91    use crate::mana::ManaPool;
92    use crate::spellability::SpellAbility;
93    use crate::trigger::handler::TriggerHandler;
94
95    #[test]
96    fn skip_draw_phase() {
97        let mut game = GameState::new(&["Alice", "Bob"], 20);
98        let p0 = PlayerId(0);
99        let p1 = PlayerId(1);
100
101        let sa =
102            SpellAbility::new_simple(None, p0, "SP$ SkipPhase | Defined$ Opponent | Phase$ Draw");
103
104        let mut th = TriggerHandler::new();
105        let mut agents: Vec<Box<dyn crate::agent::PlayerAgent>> =
106            vec![Box::new(PassAgent), Box::new(PassAgent)];
107        let mut mp = vec![ManaPool::default(), ManaPool::default()];
108        let templates = HashMap::new();
109        let templates_variants = HashMap::new();
110        let token_fallback = HashMap::new();
111        let edition_dates: HashMap<String, String> = HashMap::new();
112        let mut rng_adapter = crate::game_rng::ThreadRngAdapter;
113        let mut ctx = EffectContext {
114            game: &mut game,
115            combat: None,
116            agents: &mut agents,
117            trigger_handler: &mut th,
118            token_templates: &templates,
119            token_art_variants: &templates_variants,
120            token_fallback: &token_fallback,
121            edition_dates: &edition_dates,
122            mana_pools: &mut mp,
123            parent_target_card: None,
124            rng: &mut rng_adapter,
125        };
126        super::SkipPhaseEffect::resolve(&mut ctx, &sa);
127
128        assert!(ctx.game.player(p1).skip_next_draw);
129        assert!(!ctx.game.player(p1).skip_next_combat);
130    }
131
132    #[test]
133    fn skip_combat_phase() {
134        let mut game = GameState::new(&["Alice", "Bob"], 20);
135        let p0 = PlayerId(0);
136
137        let sa = SpellAbility::new_simple(None, p0, "SP$ SkipPhase | Defined$ You | Phase$ Combat");
138
139        let mut th = TriggerHandler::new();
140        let mut agents: Vec<Box<dyn crate::agent::PlayerAgent>> =
141            vec![Box::new(PassAgent), Box::new(PassAgent)];
142        let mut mp = vec![ManaPool::default(), ManaPool::default()];
143        let templates = HashMap::new();
144        let templates_variants = HashMap::new();
145        let token_fallback = HashMap::new();
146        let edition_dates: HashMap<String, String> = HashMap::new();
147        let mut rng_adapter = crate::game_rng::ThreadRngAdapter;
148        let mut ctx = EffectContext {
149            game: &mut game,
150            combat: None,
151            agents: &mut agents,
152            trigger_handler: &mut th,
153            token_templates: &templates,
154            token_art_variants: &templates_variants,
155            token_fallback: &token_fallback,
156            edition_dates: &edition_dates,
157            mana_pools: &mut mp,
158            parent_target_card: None,
159            rng: &mut rng_adapter,
160        };
161        super::SkipPhaseEffect::resolve(&mut ctx, &sa);
162
163        assert!(ctx.game.player(p0).skip_next_combat);
164    }
165}