Skip to main content

manabrew_engine/ability/effects/
tap_all_effect.rs

1use forge_foundation::ZoneType;
2
3use super::{matches_valid_cards_for_sa, EffectContext};
4use crate::ids::CardId;
5
6/// `SP$ TapAll` — tap all matching permanents.
7///
8/// Mirrors Java's `TapAllEffect.java`.
9/// Uses the standard two-pass collect → act pattern to avoid borrow issues.
10///
11/// # Card script examples
12/// ```text
13/// A:SP$ TapAll | ValidCards$ Creature.Blue
14/// A:SP$ TapAll | ValidCards$ Creature.OppCtrl
15/// ```
16/// Struct form of this effect so it can participate in the
17/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
18/// `TapAllEffect` class extending `SpellAbilityEffect`.
19#[manabrew_engine_macros::spell_effect(TapAllEffect)]
20fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
21    let valid_cards = sa.ir.valid_cards_selector.as_ref();
22
23    let player_ids = ctx.game.player_order.clone();
24    let mut to_tap: Vec<CardId> = Vec::new();
25    for &pid in &player_ids {
26        let zone_cards = ctx.game.cards_in_zone(ZoneType::Battlefield, pid).to_vec();
27        for cid in zone_cards {
28            if matches_valid_cards_for_sa(ctx.game, sa, ctx.game.card(cid), valid_cards, "Creature")
29            {
30                to_tap.push(cid);
31            }
32        }
33    }
34
35    for card_id in to_tap {
36        if ctx.game.card(card_id).zone == ZoneType::Battlefield {
37            ctx.game.tap(card_id);
38            // Fire Taps trigger per card
39            ctx.trigger_handler.run_trigger(
40                crate::trigger::TriggerType::Taps,
41                crate::event::RunParams {
42                    card: Some(card_id),
43                    ..Default::default()
44                },
45                false,
46            );
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use crate::ability::spell_ability_effect::SpellAbilityEffect;
54    use forge_foundation::{CardTypeLine, ColorSet, ManaCost, ZoneType};
55    use std::collections::HashMap;
56
57    use crate::ability::effects::EffectContext;
58    use crate::agent::PassAgent;
59    use crate::card::Card;
60    use crate::game::GameState;
61    use crate::ids::{CardId, PlayerId};
62    use crate::mana::ManaPool;
63    use crate::spellability::SpellAbility;
64    use crate::trigger::handler::TriggerHandler;
65
66    fn make_creature(game: &mut GameState, owner: PlayerId) -> CardId {
67        let c = Card::new(
68            CardId(0),
69            "Bear".into(),
70            owner,
71            CardTypeLine::parse("Creature - Bear"),
72            ManaCost::parse("1 G"),
73            ColorSet::GREEN,
74            Some(2),
75            Some(2),
76            vec![],
77            vec![],
78        );
79        game.create_card(c)
80    }
81
82    #[test]
83    fn tap_all_taps_matching_creatures() {
84        let mut game = GameState::new(&["Alice", "Bob"], 20);
85        let p0 = PlayerId(0);
86        let p1 = PlayerId(1);
87        let c1 = make_creature(&mut game, p0);
88        let c2 = make_creature(&mut game, p1);
89        game.move_card(c1, ZoneType::Battlefield, p0);
90        game.move_card(c2, ZoneType::Battlefield, p1);
91        assert!(!game.card(c1).tapped);
92        assert!(!game.card(c2).tapped);
93
94        let sa = SpellAbility::new_simple(None, p0, "A:SP$ TapAll | ValidCards$ Creature");
95        let mut th = TriggerHandler::new();
96        let mut agents: Vec<Box<dyn crate::agent::PlayerAgent>> =
97            vec![Box::new(PassAgent), Box::new(PassAgent)];
98        let mut mp = vec![ManaPool::default(), ManaPool::default()];
99        let templates = HashMap::new();
100        let templates_variants = HashMap::new();
101        let token_fallback = HashMap::new();
102        let edition_dates: HashMap<String, String> = HashMap::new();
103        let mut rng_adapter = crate::game_rng::ThreadRngAdapter;
104        let mut ctx = EffectContext {
105            game: &mut game,
106            combat: None,
107            agents: &mut agents,
108            trigger_handler: &mut th,
109            token_templates: &templates,
110            token_art_variants: &templates_variants,
111            token_fallback: &token_fallback,
112            edition_dates: &edition_dates,
113            mana_pools: &mut mp,
114            parent_target_card: None,
115            rng: &mut rng_adapter,
116        };
117        super::TapAllEffect::resolve(&mut ctx, &sa);
118
119        assert!(ctx.game.card(c1).tapped);
120        assert!(ctx.game.card(c2).tapped);
121    }
122}