use crate::util::hash::*;
use crate::util::parse::*;
use crate::util::thread::*;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::mpsc::{channel, Sender};
pub struct Input {
immune: Vec<Group>,
infection: Vec<Group>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Immune,
Infection,
Draw,
}
#[derive(Clone, Copy)]
struct Group {
units: i32,
hit_points: i32,
damage: i32,
initiative: i32,
weak: u32,
immune: u32,
attack: u32,
chosen: u32,
}
impl Group {
fn effective_power(&self) -> i32 {
self.units * self.damage
}
fn actual_damage(&self, other: &Group) -> i32 {
if self.attack & other.weak != 0 {
2 * self.effective_power()
} else if self.attack & other.immune == 0 {
self.effective_power()
} else {
0
}
}
fn target_selection_order(&self) -> (i32, i32) {
(-self.effective_power(), -self.initiative)
}
fn attack(&self, defender: &mut Self) -> i32 {
let damage = self.actual_damage(defender).max(0);
let amount = damage / defender.hit_points;
defender.units -= amount;
amount
}
}
struct Shared {
done: AtomicBool,
boost: AtomicI32,
tx: Sender<(i32, i32)>,
}
pub fn parse<'a>(input: &'a str) -> Input {
let mut elements = FastMap::new();
let mut mask = |key: &'a str| {
let next = 1 << elements.len();
*elements.entry(key).or_insert(next)
};
let (first, second) = input.split_once("\n\n").unwrap();
let immune = parse_group(first, &mut mask);
let infection = parse_group(second, &mut mask);
Input { immune, infection }
}
pub fn part1(input: &Input) -> i32 {
let (_, units) = fight(input, 0);
units
}
pub fn part2(input: &Input) -> i32 {
let (tx, rx) = channel();
let shared = Shared { done: AtomicBool::new(false), boost: AtomicI32::new(1), tx };
spawn(|| worker(input, &shared));
drop(shared.tx);
rx.iter().min_by_key(|&(boost, _)| boost).map(|(_, units)| units).unwrap()
}
fn worker(input: &Input, shared: &Shared) {
while !shared.done.load(Ordering::Relaxed) {
let boost = shared.boost.fetch_add(1, Ordering::Relaxed);
let (kind, units) = fight(input, boost);
if kind == Kind::Immune {
shared.done.store(true, Ordering::Relaxed);
let _unused = shared.tx.send((boost, units));
}
}
}
fn fight(input: &Input, boost: i32) -> (Kind, i32) {
let mut immune = input.immune.clone();
let mut infection = input.infection.clone();
let mut attacks = vec![None; immune.len() + infection.len()];
immune.iter_mut().for_each(|group| group.damage += boost);
for turn in 1.. {
let mut target_selection = |attacker: &[Group], defender: &mut [Group], kind: Kind| {
for (from, group) in attacker.iter().enumerate() {
let target = (0..defender.len())
.filter(|&to| {
defender[to].chosen < turn && group.actual_damage(&defender[to]) > 0
})
.max_by_key(|&to| {
(
group.actual_damage(&defender[to]),
defender[to].effective_power(),
defender[to].initiative,
)
});
if let Some(to) = target {
let index = attacks.len() - group.initiative as usize;
defender[to].chosen = turn;
attacks[index] = Some((kind, from, to));
}
}
};
immune.sort_unstable_by_key(Group::target_selection_order);
infection.sort_unstable_by_key(Group::target_selection_order);
target_selection(&immune, &mut infection, Kind::Immune);
target_selection(&infection, &mut immune, Kind::Infection);
let mut killed = 0;
for next in &mut attacks {
if let Some((kind, from, to)) = *next {
if kind == Kind::Immune {
killed += immune[from].attack(&mut infection[to]);
} else {
killed += infection[from].attack(&mut immune[to]);
}
*next = None;
}
}
if killed == 0 {
return (Kind::Draw, 0);
}
immune.retain(|group| group.units > 0);
infection.retain(|group| group.units > 0);
if immune.is_empty() {
return (Kind::Infection, infection.iter().map(|group| group.units).sum());
}
if infection.is_empty() {
return (Kind::Immune, immune.iter().map(|group| group.units).sum());
}
}
unreachable!()
}
fn parse_group<'a>(input: &'a str, mask: &mut impl FnMut(&'a str) -> u32) -> Vec<Group> {
let delimiters = [' ', '(', ')', ',', ';'];
input
.lines()
.skip(1)
.map(|line| {
let tokens: Vec<_> = line.split(delimiters).collect();
let units = tokens[0].signed();
let hit_points = tokens[4].signed();
let damage = tokens[tokens.len() - 6].signed();
let initiative = tokens[tokens.len() - 1].signed();
let attack = mask(tokens[tokens.len() - 5]);
let weak = parse_list(&tokens, "weak", mask);
let immune = parse_list(&tokens, "immune", mask);
let chosen = 0;
Group { units, hit_points, damage, initiative, weak, immune, attack, chosen }
})
.collect()
}
fn parse_list<'a>(tokens: &[&'a str], start: &str, mask: &mut impl FnMut(&'a str) -> u32) -> u32 {
let end = ["weak", "immune", "with"];
let mut elements = 0;
if let Some(index) = tokens.iter().position(|&t| t == start) {
let mut index = index + 2;
while !end.contains(&tokens[index]) {
elements |= mask(tokens[index]);
index += 1;
}
}
elements
}