Skip to main content

manabrew_engine/card/
card_lists.rs

1use rand::seq::SliceRandom;
2
3use crate::ability::ability_utils;
4use crate::card::valid_filter;
5use crate::game::GameState;
6use crate::ids::{CardId, PlayerId};
7use crate::parsing::cached_compiled_selector;
8use crate::spellability::SpellAbility;
9
10/// Port of common `CardLists` utilities used by card filtering/counting code.
11pub struct CardLists;
12
13impl CardLists {
14    pub fn filter_toughness(
15        game: &GameState,
16        cards: &[CardId],
17        at_least_toughness: i32,
18    ) -> Vec<CardId> {
19        cards
20            .iter()
21            .copied()
22            .filter(|&cid| game.card(cid).toughness() <= at_least_toughness)
23            .collect()
24    }
25
26    pub fn filter_power(game: &GameState, cards: &[CardId], at_least_power: i32) -> Vec<CardId> {
27        cards
28            .iter()
29            .copied()
30            .filter(|&cid| game.card(cid).power() >= at_least_power)
31            .collect()
32    }
33
34    pub fn filter_le_power(
35        game: &GameState,
36        cards: &[CardId],
37        less_than_power: i32,
38    ) -> Vec<CardId> {
39        cards
40            .iter()
41            .copied()
42            .filter(|&cid| game.card(cid).power() <= less_than_power)
43            .collect()
44    }
45
46    pub fn filter_any_counters(
47        game: &GameState,
48        cards: &[CardId],
49        at_least_counters: i32,
50    ) -> Vec<CardId> {
51        cards
52            .iter()
53            .copied()
54            .filter(|&cid| {
55                game.card(cid).counters.values().copied().sum::<i32>() >= at_least_counters
56            })
57            .collect()
58    }
59
60    pub fn sort_by_cmc_desc(game: &GameState, list: &mut [CardId]) {
61        list.sort_by_key(|&cid| -game.card(cid).mana_value());
62    }
63
64    pub fn sort_by_toughness_asc(game: &GameState, list: &mut [CardId]) {
65        list.sort_by_key(|&cid| game.card(cid).toughness());
66    }
67
68    pub fn sort_by_toughness_desc(game: &GameState, list: &mut [CardId]) {
69        list.sort_by_key(|&cid| -game.card(cid).toughness());
70    }
71
72    pub fn sort_by_power_asc(game: &GameState, list: &mut [CardId]) {
73        list.sort_by_key(|&cid| game.card(cid).power());
74    }
75
76    pub fn sort_by_power_desc(game: &GameState, list: &mut [CardId]) {
77        list.sort_by_key(|&cid| -game.card(cid).power());
78    }
79
80    pub fn shuffle(list: &mut [CardId]) {
81        let mut rng = rand::thread_rng();
82        list.shuffle(&mut rng);
83    }
84
85    pub fn filter_controlled_by(
86        game: &GameState,
87        cards: &[CardId],
88        player: PlayerId,
89    ) -> Vec<CardId> {
90        cards
91            .iter()
92            .copied()
93            .filter(|&cid| game.card(cid).controller == player)
94            .collect()
95    }
96
97    pub fn filter_controlled_by_as_list(
98        game: &GameState,
99        cards: &[CardId],
100        players: &[PlayerId],
101    ) -> Vec<CardId> {
102        cards
103            .iter()
104            .copied()
105            .filter(|&cid| players.contains(&game.card(cid).controller))
106            .collect()
107    }
108
109    pub fn can_subsequently_target(
110        game: &GameState,
111        cards: &[CardId],
112        source: &SpellAbility,
113    ) -> Vec<CardId> {
114        let targets = source.get_targets();
115        if targets.target_card.is_none()
116            && targets.target_player.is_none()
117            && targets.target_stack_entry.is_none()
118        {
119            return cards.to_vec();
120        }
121        cards
122            .iter()
123            .copied()
124            .filter(|&cid| {
125                let player = source.targeting_player.unwrap_or(source.activating_player);
126                crate::spellability::target_restrictions::can_be_targeted_by_sa(
127                    game, cid, player, source,
128                )
129            })
130            .collect()
131    }
132
133    pub fn test(game: &GameState, card: CardId, source: &SpellAbility) -> bool {
134        let player = source.targeting_player.unwrap_or(source.activating_player);
135        crate::spellability::target_restrictions::can_be_targeted_by_sa(game, card, player, source)
136    }
137
138    pub fn filter(
139        _game: &GameState,
140        cards: &[CardId],
141        filt: impl Fn(CardId) -> bool,
142    ) -> Vec<CardId> {
143        cards.iter().copied().filter(|&cid| filt(cid)).collect()
144    }
145
146    pub fn filter_as_list(
147        game: &GameState,
148        cards: &[CardId],
149        restriction: &str,
150        source_controller: PlayerId,
151    ) -> Vec<CardId> {
152        cards
153            .iter()
154            .copied()
155            .filter(|&cid| {
156                ability_utils::matches_valid_cards(game.card(cid), restriction, source_controller)
157            })
158            .collect()
159    }
160
161    pub fn count(
162        game: &GameState,
163        cards: &[CardId],
164        restriction: &str,
165        source_controller: PlayerId,
166    ) -> usize {
167        Self::filter_as_list(game, cards, restriction, source_controller).len()
168    }
169
170    pub fn filter_as_list_with_source(
171        game: &GameState,
172        cards: &[CardId],
173        restriction: &str,
174        source: CardId,
175    ) -> Vec<CardId> {
176        let selector = cached_compiled_selector(restriction);
177        cards
178            .iter()
179            .copied()
180            .filter(|&cid| {
181                valid_filter::matches_valid_card_selector_in_game(
182                    &selector,
183                    game.card(cid),
184                    game.card(source),
185                    game,
186                )
187            })
188            .collect()
189    }
190
191    pub fn count_with_source(
192        game: &GameState,
193        cards: &[CardId],
194        restriction: &str,
195        source: CardId,
196    ) -> usize {
197        Self::filter_as_list_with_source(game, cards, restriction, source).len()
198    }
199
200    pub fn cmc_can_sum_to(sum: i32, cards: &[CardId], game: &GameState) -> bool {
201        let mut nums = Vec::new();
202        for &cid in cards {
203            let cmc = game.card(cid).mana_value();
204            if cmc == sum {
205                return true;
206            }
207            if cmc < sum {
208                nums.push(cmc);
209            }
210        }
211        if nums.is_empty() {
212            return false;
213        }
214        nums.sort_unstable();
215        Self::subset_sum(&nums, sum)
216    }
217
218    fn subset_sum(nums: &[i32], sum: i32) -> bool {
219        if sum == 0 {
220            return true;
221        }
222        if nums.is_empty() || sum < 0 {
223            return false;
224        }
225        let (last, rest) = nums.split_last().expect("split_last on non-empty slice");
226        if *last > sum {
227            return Self::subset_sum(rest, sum);
228        }
229        Self::subset_sum(rest, sum) || Self::subset_sum(rest, sum - *last)
230    }
231}