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, Eq, PartialEq)]
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: &Self) -> 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(input: &str) -> Input {
70 let mut elements = FastMap::new();
72 let mut mask = |key| {
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);
101
102 if kind == Kind::Immune {
103 iter.stop();
104 return Some((boost, units));
105 }
106 }
107
108 None
109}
110
111fn fight(input: &Input, boost: i32) -> (Kind, i32) {
112 let mut immune = input.immune.clone();
113 let mut infection = input.infection.clone();
114 let mut attacks = vec![None; immune.len() + infection.len()];
115
116 immune.iter_mut().for_each(|group| group.damage += boost);
118
119 for turn in 1.. {
120 let mut target_selection = |attacker: &[Group], defender: &mut [Group], kind: Kind| {
122 for (from, group) in attacker.iter().enumerate() {
123 let target = (0..defender.len())
124 .filter(|&to| {
125 defender[to].chosen < turn && group.actual_damage(&defender[to]) > 0
126 })
127 .max_by_key(|&to| {
128 (
129 group.actual_damage(&defender[to]),
130 defender[to].effective_power(),
131 defender[to].initiative,
132 )
133 });
134
135 if let Some(to) = target {
136 let index = attacks.len() - group.initiative as usize;
138 defender[to].chosen = turn;
139 attacks[index] = Some((kind, from, to));
140 }
141 }
142 };
143
144 immune.sort_unstable_by_key(Group::target_selection_order);
146 infection.sort_unstable_by_key(Group::target_selection_order);
147
148 target_selection(&immune, &mut infection, Kind::Immune);
149 target_selection(&infection, &mut immune, Kind::Infection);
150
151 let mut killed = 0;
153
154 for next in &mut attacks {
155 if let Some((kind, from, to)) = next.take() {
156 if kind == Kind::Immune {
157 killed += immune[from].attack(&mut infection[to]);
158 } else {
159 killed += infection[from].attack(&mut immune[to]);
160 }
161 }
162 }
163
164 if killed == 0 {
166 return (Kind::Draw, 0);
167 }
168
169 immune.retain(|group| group.units > 0);
171 infection.retain(|group| group.units > 0);
172
173 if immune.is_empty() {
174 return (Kind::Infection, infection.iter().map(|group| group.units).sum());
175 }
176 if infection.is_empty() {
177 return (Kind::Immune, immune.iter().map(|group| group.units).sum());
178 }
179 }
180
181 unreachable!()
182}
183
184fn parse_group<'a>(input: &'a str, mask: &mut impl FnMut(&'a str) -> u32) -> Vec<Group> {
186 let delimiters = [' ', '(', ')', ',', ';'];
187 input
188 .lines()
189 .skip(1)
190 .map(|line| {
191 let tokens: Vec<_> = line.split(delimiters).collect();
192
193 let units = tokens[0].signed();
194 let hit_points = tokens[4].signed();
195 let damage = tokens[tokens.len() - 6].signed();
196 let initiative = tokens[tokens.len() - 1].signed();
197 let attack = mask(tokens[tokens.len() - 5]);
198 let weak = parse_list(&tokens, "weak", mask);
199 let immune = parse_list(&tokens, "immune", mask);
200 let chosen = 0;
201
202 Group { units, hit_points, damage, initiative, weak, immune, attack, chosen }
203 })
204 .collect()
205}
206
207fn parse_list<'a>(tokens: &[&'a str], start: &str, mask: &mut impl FnMut(&'a str) -> u32) -> u32 {
209 let end = ["weak", "immune", "with"];
210 if let Some(index) = tokens.iter().position(|&t| t == start) {
211 tokens[index + 2..]
213 .iter()
214 .take_while(|&&t| !end.contains(&t))
215 .fold(0, |elements, &t| elements | mask(t))
216 } else {
217 0
218 }
219}