manabrew_engine/card/
card_lists.rs1use 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
10pub 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}