diff --git a/input/day24.txt b/input/day24.txt index e69de29..0a31990 100644 --- a/input/day24.txt +++ b/input/day24.txt @@ -0,0 +1,23 @@ +Immune System: +3400 units each with 1430 hit points (immune to fire, radiation, slashing) with an attack that does 4 radiation damage at initiative 4 +138 units each with 8650 hit points (weak to bludgeoning; immune to slashing, cold, radiation) with an attack that does 576 slashing damage at initiative 16 +255 units each with 9469 hit points (weak to radiation, fire) with an attack that does 351 bludgeoning damage at initiative 8 +4145 units each with 2591 hit points (immune to cold; weak to slashing) with an attack that does 6 fire damage at initiative 12 +3605 units each with 10989 hit points with an attack that does 26 fire damage at initiative 19 +865 units each with 11201 hit points with an attack that does 102 slashing damage at initiative 10 +633 units each with 10092 hit points (weak to slashing, radiation) with an attack that does 150 slashing damage at initiative 11 +2347 units each with 3322 hit points with an attack that does 12 cold damage at initiative 2 +7045 units each with 3877 hit points (weak to radiation) with an attack that does 5 bludgeoning damage at initiative 5 +1086 units each with 8626 hit points (weak to radiation) with an attack that does 69 slashing damage at initiative 13 + +Infection: +2152 units each with 12657 hit points (weak to fire, cold) with an attack that does 11 fire damage at initiative 18 +40 units each with 39458 hit points (immune to radiation, fire, slashing; weak to bludgeoning) with an attack that does 1519 slashing damage at initiative 7 +59 units each with 35138 hit points (immune to radiation; weak to fire) with an attack that does 1105 fire damage at initiative 15 +1569 units each with 51364 hit points (weak to radiation) with an attack that does 55 radiation damage at initiative 17 +929 units each with 23887 hit points (weak to bludgeoning) with an attack that does 48 cold damage at initiative 14 +5264 units each with 14842 hit points (immune to cold, fire; weak to slashing, bludgeoning) with an attack that does 4 bludgeoning damage at initiative 9 +1570 units each with 30419 hit points (weak to radiation, cold; immune to fire) with an attack that does 35 slashing damage at initiative 1 +1428 units each with 21393 hit points (weak to radiation) with an attack that does 29 cold damage at initiative 6 +1014 units each with 25717 hit points (weak to fire) with an attack that does 47 fire damage at initiative 3 +7933 units each with 29900 hit points (immune to bludgeoning, radiation, slashing) with an attack that does 5 slashing damage at initiative 20 diff --git a/src/tasks/day24.rs b/src/tasks/day24.rs index 637998d..9889b91 100644 --- a/src/tasks/day24.rs +++ b/src/tasks/day24.rs @@ -1,42 +1,181 @@ +use crate::utils; +use std::cmp::Reverse; +use std::collections::HashMap; + pub fn task1() { let input = "Immune System: -17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2 -989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3 + 17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2 + 989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3 Infection: -801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1 -4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4"; - let mut groups_immune: Vec = Vec::new(); - let mut groups_infection: Vec = Vec::new(); + 801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1 + 4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4"; + let input = utils::read_file("input/day24.txt"); + let mut game = Game::create(&input); - let mut to_immune = false; - for line in input.lines() { - match line { - "Immune System:" => { - to_immune = true; - } - "Infection:" => { - to_immune = false; - } - "" => (), - group => { - if let Some(group) = Group::from_str(group) { - if to_immune { - groups_immune.push(group); - } else { - groups_infection.push(group); + println!( + "Immune start units: {}", + game.groups + .iter() + .filter(|(_, it)| it.team == Team::ImmuneSystem) + .map(|(_, it)| it.units) + .sum::() + ); + println!( + "Infection start units: {}", + game.groups + .iter() + .filter(|(_, it)| it.team == Team::Infection) + .map(|(_, it)| it.units) + .sum::() + ); + + let mut rounds_played = 0; + while !game.is_over() { + game.round(); + rounds_played += 1; + } + + println!("{:#?}", game); + println!("Played {} rounds", rounds_played); + + println!( + "Standing units after the game: {}", + game.groups.iter().map(|(_, it)| it.units).sum::() + ); + + // 21107 too high + // 21004 too high +} + +#[derive(Debug)] +struct Game { + groups: HashMap, +} + +impl Game { + fn create(input: &str) -> Self { + let mut groups = HashMap::new(); + let mut team = Team::ImmuneSystem; + for (id, line) in input.lines().enumerate() { + match line { + "Immune System:" => { + team = Team::ImmuneSystem; + } + "Infection:" => { + team = Team::Infection; + } + "" => (), + group => { + if let Some(group) = Group::from_str(group, team, id) { + groups.insert(id, group); } } } } + Game { groups } } - println!("Immune System:\n{:?}", groups_immune); - println!("Infection:\n{:?}", groups_infection); + fn round(&mut self) { + let mut target: HashMap = HashMap::new(); + // lock targets ordered by effective power + let mut all_by_power: Vec<&Group> = self.groups.iter().map(|(_, it)| it).collect(); + all_by_power.sort_unstable_by_key(|a| Reverse((a.effective_power(), a.initiative))); + // for group in all_by_power.iter() { + // println!( + // "{}: {} ({})", + // group.id, + // group.effective_power(), + // group.initiative + // ); + // } + // println!("{:?}", all_by_power); + for group in all_by_power.iter() { + if let Some(t) = self + .groups + .iter() + .map(|(_, it)| it) + .filter(|it| it.team != group.team) + .filter(|it| !target.values().any(|t| *t == it.id)) + // .filter(|it| group.compute_attack_damage_to(&it) >= it.hp_each) + // .inspect(|it| { + // println!( + // "{} would deal {} damage to {}", + // group.id, + // group.compute_attack_damage_to(it), + // it.id + // ) + // }) + .max_by_key(|it| { + ( + group.compute_attack_damage_to(&it), + it.effective_power(), + it.initiative, + ) + }) + { + if group.compute_attack_damage_to(&t) <= 0 { + println!( + "Didn't find a target where {:?} can deal positive damage.", + group + ); + continue; + } else { + target.insert(group.id, t.id); + } + } + } + + // attack ordered by initiative + let mut all_ids_by_initiative: Vec = + self.groups.iter().map(|(_, it)| it.id).collect(); + all_ids_by_initiative.sort_unstable_by_key(|id| Reverse(self.groups[id].initiative)); + + for active_id in all_ids_by_initiative { + if !self.groups[&active_id].alive() { + // was killed in this round + println!("Group {} already dead", active_id); + continue; + } + if let Some(enemy_id) = target.get(&active_id) { + let enemy = &self.groups[enemy_id]; + let damage: i64 = self.groups[&active_id].compute_attack_damage_to(&enemy); + let dying_units = damage / enemy.hp_each; + if let Some(enemy) = self.groups.get_mut(enemy_id) { + enemy.units -= dying_units; + } + // println!( + // "{} dealt {} ({} units) damage to {}", + // active_id, damage, dying_units, enemy_id + // ); + } + } + + // clean up dead groups + self.groups.retain(|_, it| it.alive()); + // println!("{:?}", self.groups); + } + + fn is_over(&self) -> bool { + self.groups.len() == 0 + || self.groups.iter().all(|(_, it)| it.team == Team::Infection) + || self + .groups + .iter() + .all(|(_, it)| it.team == Team::ImmuneSystem) + } } -#[derive(Debug)] +#[derive(Debug, Copy, Clone, PartialEq)] +enum Team { + Infection, + ImmuneSystem, +} + +#[derive(Debug, Clone)] struct Group { + id: usize, + team: Team, units: i64, hp_each: i64, weaknesses: Vec, @@ -47,7 +186,7 @@ struct Group { } impl Group { - fn from_str(input: &str) -> Option { + fn from_str(input: &str, team: Team, id: usize) -> Option { // 801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1 use regex::Regex; let regex = Regex::new(r"(\d+) units each with (\d+) hit points \((.+)\) with an attack that does (\d+) (\w+) damage at initiative (\d+)").unwrap(); @@ -68,6 +207,8 @@ impl Group { } let group = Group { + id, + team, units, hp_each, weaknesses, @@ -81,4 +222,29 @@ impl Group { None } } + + fn alive(&self) -> bool { + self.units > 0 + } + + fn effective_power(&self) -> i64 { + if !self.alive() { + panic!("I have no power, im dead!") + } + self.units * self.attack_damage + } + + fn compute_attack_damage_to(&self, other: &Self) -> i64 { + if self.alive() { + if other.weaknesses.contains(&self.attack_type) { + self.effective_power() * 2 + } else if other.immunities.contains(&self.attack_type) { + 0 + } else { + self.effective_power() + } + } else { + panic!("I'm not alive, I cannot attack anyone") + } + } }