manabrew_engine/combat/
attack_requirement.rs1use std::collections::HashMap;
2
3use super::DefenderId;
4use crate::card::Card;
5use crate::ids::{CardId, PlayerId};
6use crate::staticability::static_ability_must_attack;
7
8#[derive(Debug, Clone)]
11pub struct AttackRequirement {
12 pub attacker: CardId,
14 pub must_attack_any: bool,
16 pub must_attack_defender: Option<PlayerId>,
18 pub goaded_by: Option<PlayerId>,
20 pub defender_specific: HashMap<DefenderId, i32>,
23}
24
25impl AttackRequirement {
26 pub fn has_requirement(&self) -> bool {
29 self.defender_specific.values().any(|&v| v > 0) || self.must_attack_any
30 }
31
32 pub fn count_violations(&self, defender: Option<DefenderId>) -> i32 {
36 if !self.has_requirement() {
37 return 0;
38 }
39
40 let total: i32 = self.defender_specific.values().sum();
41 let is_attacking = defender.is_some();
42
43 let credit = if is_attacking {
44 defender
45 .and_then(|d| self.defender_specific.get(&d).copied())
46 .unwrap_or(0)
47 } else {
48 0
49 };
50
51 total - credit
52 }
53
54 pub fn get_sorted_requirements(&self) -> Vec<(DefenderId, i32)> {
57 let mut entries: Vec<(DefenderId, i32)> = self
58 .defender_specific
59 .iter()
60 .map(|(&d, &c)| (d, c))
61 .collect();
62 entries.sort_by_key(|&(_, c)| c);
63 entries
64 }
65}
66
67pub fn compute_attack_requirements(
74 cards: &[Card],
75 available: &[CardId],
76 defending: PlayerId,
77) -> Vec<AttackRequirement> {
78 compute_attack_requirements_with_defenders(cards, available, &[DefenderId::Player(defending)])
79}
80
81pub fn compute_attack_requirements_with_defenders(
83 cards: &[Card],
84 available: &[CardId],
85 possible_defenders: &[DefenderId],
86) -> Vec<AttackRequirement> {
87 let mut requirements = Vec::new();
88
89 for &attacker_id in available {
90 let card = &cards[attacker_id.index()];
91
92 let must_from_static = static_ability_must_attack::must_attack(cards, card);
93 let goaded = card.goaded_by;
94
95 let must_attack_any = must_from_static || goaded.is_some();
96
97 let mut n_attack_anything: i32 = 0;
100 if goaded.is_some() {
101 n_attack_anything += 1;
102 }
103 if must_from_static {
104 n_attack_anything += 1;
105 }
106
107 let mut defender_specific = HashMap::new();
108 for &defender in possible_defenders {
109 defender_specific.insert(defender, n_attack_anything);
110 }
111
112 let goaded_by_player = goaded;
113 let defending = possible_defenders
114 .iter()
115 .find_map(|d| d.as_player())
116 .unwrap_or(PlayerId(0));
117 let must_attack_defender = if goaded.is_some() && goaded != Some(defending) {
118 Some(defending)
119 } else {
120 None
121 };
122
123 if must_attack_any || !defender_specific.is_empty() {
124 requirements.push(AttackRequirement {
125 attacker: attacker_id,
126 must_attack_any,
127 must_attack_defender,
128 goaded_by: goaded_by_player,
129 defender_specific,
130 });
131 }
132 }
133
134 requirements
135}
136
137pub fn must_attack_ids(requirements: &[AttackRequirement]) -> Vec<CardId> {
139 requirements
140 .iter()
141 .filter(|r| r.must_attack_any)
142 .map(|r| r.attacker)
143 .collect()
144}