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