1use crate::util::hash::*;
11use crate::util::parse::*;
12use crate::util::thread::*;
13
14pub struct Input {
15 immune: Vec<Group>,
16 infection: Vec<Group>,
17}
18
19#[derive(Clone, Copy, PartialEq, Eq)]
20enum Kind {
21 Immune,
22 Infection,
23 Draw,
24}
25
26#[derive(Clone, Copy)]
27struct Group {
28 units: i32,
29 hit_points: i32,
30 damage: i32,
31 initiative: i32,
32 weak: u32,
33 immune: u32,
34 attack: u32,
35 chosen: u32,
36}
37
38impl Group {
40 fn effective_power(&self) -> i32 {
41 self.units * self.damage
42 }
43
44 fn actual_damage(&self, other: &Group) -> i32 {
46 if self.attack & other.weak != 0 {
47 2 * self.effective_power()
48 } else if self.attack & other.immune == 0 {
49 self.effective_power()
50 } else {
51 0
52 }
53 }
54
55 fn target_selection_order(&self) -> (i32, i32) {
56 (-self.effective_power(), -self.initiative)
57 }
58
59 fn attack(&self, defender: &mut Self) -> i32 {
60 let damage = self.actual_damage(defender).max(0);
63 let amount = damage / defender.hit_points;
64 defender.units -= amount;
65 amount
66 }
67}
68
69pub fn parse<'a>(input: &'a str) -> Input {
70 let mut elements = FastMap::new();
72 let mut mask = |key: &'a str| {
73 let next = 1 << elements.len();
74 *elements.entry(key).or_insert(next)
75 };
76
77 let (first, second) = input.split_once("\n\n").unwrap();
78 let immune = parse_group(first, &mut mask);
79 let infection = parse_group(second, &mut mask);
80 Input { immune, infection }
81}
82
83pub fn part1(input: &Input) -> i32 {
84 let (_, units) = fight(input, 0);
85 units
86}
87
88pub fn part2(input: &Input) -> i32 {
89 let iter = AtomicIter::new(1, 1);
90
91 let result = spawn(|| worker(input, &iter));
93 result.into_iter().flatten().min_by_key(|&(boost, _)| boost).map(|(_, units)| units).unwrap()
95}
96
97fn worker(input: &Input, iter: &AtomicIter) -> Option<(u32, i32)> {
98 while let Some(boost) = iter.next() {
99 let (kind, units) = fight(input, boost as i32);
103
104 if kind == Kind::Immune {
105 iter.stop();
106 return Some((boost, units));
107 }
108 }
109
110 None
111}
112
113fn fight(input: &Input, boost: i32) -> (Kind, i32) {
114 let mut immune = input.immune.clone();
115 let mut infection = input.infection.clone();
116 let mut attacks = vec![None; immune.len() + infection.len()];
117
118 immune.iter_mut().for_each(|group| group.damage += boost);
120
121 for turn in 1.. {
122 let mut target_selection = |attacker: &[Group], defender: &mut [Group], kind: Kind| {
124 for (from, group) in attacker.iter().enumerate() {
125 let target = (0..defender.len())
126 .filter(|&to| {
127 defender[to].chosen < turn && group.actual_damage(&defender[to]) > 0
128 })
129 .max_by_key(|&to| {
130 (
131 group.actual_damage(&defender[to]),
132 defender[to].effective_power(),
133 defender[to].initiative,
134 )
135 });
136
137 if let Some(to) = target {
138 let index = attacks.len() - group.initiative as usize;
140 defender[to].chosen = turn;
141 attacks[index] = Some((kind, from, to));
142 }
143 }
144 };
145
146 immune.sort_unstable_by_key(Group::target_selection_order);
148 infection.sort_unstable_by_key(Group::target_selection_order);
149
150 target_selection(&immune, &mut infection, Kind::Immune);
151 target_selection(&infection, &mut immune, Kind::Infection);
152
153 let mut killed = 0;
155
156 for next in &mut attacks {
157 if let Some((kind, from, to)) = *next {
158 if kind == Kind::Immune {
159 killed += immune[from].attack(&mut infection[to]);
160 } else {
161 killed += infection[from].attack(&mut immune[to]);
162 }
163 *next = None;
164 }
165 }
166
167 if killed == 0 {
169 return (Kind::Draw, 0);
170 }
171
172 immune.retain(|group| group.units > 0);
174 infection.retain(|group| group.units > 0);
175
176 if immune.is_empty() {
177 return (Kind::Infection, infection.iter().map(|group| group.units).sum());
178 }
179 if infection.is_empty() {
180 return (Kind::Immune, immune.iter().map(|group| group.units).sum());
181 }
182 }
183
184 unreachable!()
185}
186
187fn parse_group<'a>(input: &'a str, mask: &mut impl FnMut(&'a str) -> u32) -> Vec<Group> {
189 let delimiters = [' ', '(', ')', ',', ';'];
190 input
191 .lines()
192 .skip(1)
193 .map(|line| {
194 let tokens: Vec<_> = line.split(delimiters).collect();
195
196 let units = tokens[0].signed();
197 let hit_points = tokens[4].signed();
198 let damage = tokens[tokens.len() - 6].signed();
199 let initiative = tokens[tokens.len() - 1].signed();
200 let attack = mask(tokens[tokens.len() - 5]);
201 let weak = parse_list(&tokens, "weak", mask);
202 let immune = parse_list(&tokens, "immune", mask);
203 let chosen = 0;
204
205 Group { units, hit_points, damage, initiative, weak, immune, attack, chosen }
206 })
207 .collect()
208}
209
210fn parse_list<'a>(tokens: &[&'a str], start: &str, mask: &mut impl FnMut(&'a str) -> u32) -> u32 {
212 let end = ["weak", "immune", "with"];
213 let mut elements = 0;
214
215 if let Some(index) = tokens.iter().position(|&t| t == start) {
216 let mut index = index + 2;
217 while !end.contains(&tokens[index]) {
218 elements |= mask(tokens[index]);
219 index += 1;
220 }
221 }
222
223 elements
224}