Compare commits

..

4 Commits

Author SHA1 Message Date
fc803d8a0f Day 24 part 2. 2023-11-14 19:35:00 +01:00
3a9379f877 Day 24 part 1. 2023-11-14 18:48:45 +01:00
241c417a77 intermediate 2023-11-14 16:56:16 +01:00
4cbe831115 Removed warnings. 2023-11-14 16:41:54 +01:00
3 changed files with 119 additions and 38 deletions

View File

@@ -1,3 +1,3 @@
fn main() {
aoc_2018::tasks::day21::task1();
aoc_2018::tasks::day24::task2();
}

View File

@@ -277,6 +277,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_AddR() {
let op = Op::AddR;
let result = op.process(
@@ -292,6 +293,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_AddI() {
let op = Op::AddI;
let result = op.process(
@@ -307,6 +309,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_MulR() {
let op = Op::MulR;
let result = op.process(
@@ -322,6 +325,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_MulI() {
let op = Op::MulI;
let result = op.process(
@@ -337,6 +341,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_BanR() {
let op = Op::BanR;
let result = op.process(
@@ -352,6 +357,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_BanI() {
let op = Op::BanI;
let result = op.process(
@@ -367,6 +373,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_BorR() {
let op = Op::BorR;
let result = op.process(
@@ -382,6 +389,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_BorI() {
let op = Op::BorI;
let result = op.process(
@@ -397,6 +405,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_SetR() {
let op = Op::SetR;
let result = op.process(
@@ -412,6 +421,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_SetI() {
let op = Op::SetI;
let result = op.process(
@@ -427,6 +437,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_GtIR_true() {
let op = Op::GtIR;
let result = op.process(
@@ -442,6 +453,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_GtIR_false() {
let op = Op::GtIR;
let result = op.process(
@@ -457,6 +469,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_GtRI_true() {
let op = Op::GtRI;
let result = op.process(
@@ -472,6 +485,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_GtRI_false() {
let op = Op::GtRI;
let result = op.process(
@@ -487,6 +501,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_GtRR_true() {
let op = Op::GtRR;
let result = op.process(
@@ -502,6 +517,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_GtRR() {
let op = Op::GtRR;
let result = op.process(
@@ -517,6 +533,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_EqIR_true() {
let op = Op::EqIR;
let result = op.process(
@@ -532,6 +549,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_EqIR_false() {
let op = Op::EqIR;
let result = op.process(
@@ -547,6 +565,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_EqRI_true() {
let op = Op::EqRI;
let result = op.process(
@@ -562,6 +581,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_EqRI_false() {
let op = Op::EqRI;
let result = op.process(
@@ -577,6 +597,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_EqRR_true() {
let op = Op::EqRR;
let result = op.process(
@@ -592,6 +613,7 @@ mod test {
}
#[test]
#[allow(non_snake_case)]
fn test_EqRR_false() {
let op = Op::EqRR;
let result = op.process(

View File

@@ -1,17 +1,32 @@
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
use itertools::Itertools;
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";
use crate::utils;
pub fn task1() {
let input = utils::read_file("input/day24.txt");
let mut game = Game::create(&input);
let result = play(&input, 0);
println!("Standing units after the game: {}", result.unwrap().1);
}
pub fn task2() {
let input = utils::read_file("input/day24.txt");
let immune_final_count = (0..)
.map(|boost| play(&input, boost))
.find(|result| result.map(|(team, _)| team) == Some(Team::ImmuneSystem))
.unwrap()
.unwrap()
.1;
println!("Immune systems final count: {immune_final_count}");
}
fn play(input: &str, boost: i64) -> Option<(Team, i64)> {
let mut game = Game::create(&input, boost);
println!(
"Immune start units: {}",
@@ -30,31 +45,41 @@ Infection:
.sum::<i64>()
);
let mut rounds_played = 0;
while !game.is_over() {
game.round();
rounds_played += 1;
if game.draw {
return None;
}
}
println!("{:#?}", game);
println!("Played {} rounds", rounds_played);
println!(
"Standing units after the game: {}",
game.groups.values().map(|it| it.units).sum::<i64>()
);
// 21107 too high
// 21004 too high
Some((game.winning_team(), game.remaining_units()))
}
#[derive(Debug)]
struct Game {
groups: HashMap<usize, Group>,
draw: bool,
}
impl Game {
fn create(input: &str) -> Self {
fn remaining_units(&self) -> i64 {
self.groups.values().map(|it| it.units).sum::<i64>()
}
fn winning_team(&self) -> Team {
let teams: Vec<Team> = self
.groups
.values()
.map(|group| group.team)
.unique()
.collect();
if teams.len() != 1 {
panic!("No winning team. Remaining teams: {teams:?}");
}
teams[0]
}
fn create(input: &str, boost: i64) -> Self {
let mut groups = HashMap::new();
let mut team = Team::ImmuneSystem;
for (id, line) in input.lines().enumerate() {
@@ -67,13 +92,21 @@ impl Game {
}
"" => (),
group => {
if let Some(group) = Group::from_str(group, team, id) {
if let Some(mut group) = Group::from_str(group, team, id) {
if group.team == Team::ImmuneSystem {
group.attack_damage += boost;
}
groups.insert(id, group);
} else {
panic!("bad group: {group}");
}
}
}
}
Game { groups }
Game {
groups,
draw: false,
}
}
fn round(&mut self) {
@@ -96,7 +129,6 @@ impl Game {
.values()
.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 {}",
@@ -114,10 +146,10 @@ impl Game {
})
{
if group.compute_attack_damage_to(t) <= 0 {
println!(
"Didn't find a target where {:?} can deal positive damage.",
group
);
// println!(
// "Didn't find a target where {:?} can deal positive damage.",
// group
// );
continue;
} else {
target.insert(group.id, t.id);
@@ -129,18 +161,21 @@ impl Game {
let mut all_ids_by_initiative: Vec<usize> = self.groups.values().map(|it| it.id).collect();
all_ids_by_initiative.sort_unstable_by_key(|id| Reverse(self.groups[id].initiative));
self.draw = true;
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);
// 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;
self.groups.get_mut(enemy_id).unwrap().units -= dying_units;
if dying_units > 0 {
self.draw = false;
}
// println!(
// "{} dealt {} ({} units) damage to {}",
@@ -155,8 +190,7 @@ impl Game {
}
fn is_over(&self) -> bool {
self.groups.is_empty()
|| self.groups.iter().all(|(_, it)| it.team == Team::Infection)
self.groups.iter().all(|(_, it)| it.team == Team::Infection)
|| self
.groups
.iter()
@@ -164,7 +198,7 @@ impl Game {
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
enum Team {
Infection,
ImmuneSystem,
@@ -186,8 +220,9 @@ struct Group {
impl Group {
fn from_str(input: &str, team: Team, id: usize) -> Option<Self> {
// 801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1
// 2347 units each with 3322 hit points with an attack that does 12 cold damage at initiative 2
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();
let regex = Regex::new(r"(\d+) units each with (\d+) hit points(.*)with an attack that does (\d+) (\w+) damage at initiative (\d+)").unwrap();
if let Some(m) = regex.captures(input) {
let units: i64 = m[1].parse().unwrap();
let hp_each: i64 = m[2].parse().unwrap();
@@ -196,7 +231,8 @@ impl Group {
let initiative: u64 = m[6].parse().unwrap();
let mut weaknesses: Vec<String> = Vec::new();
let mut immunities: Vec<String> = Vec::new();
for part in m[3].split("; ") {
let attributes = m[3].trim().trim_start_matches("(").trim_end_matches(")");
for part in attributes.split("; ") {
if let Some(stripped) = part.strip_prefix("weak to ") {
weaknesses = stripped.split(", ").map(|it| it.to_string()).collect();
} else if let Some(stripped) = part.strip_prefix("immune to ") {
@@ -246,3 +282,26 @@ impl Group {
}
}
}
#[cfg(test)]
mod test {
use crate::tasks::day24::play;
const INPUT: &str = "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
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";
#[test]
fn example1() {
assert_eq!(5216, play(INPUT, 0).unwrap().1);
}
#[test]
fn example2() {
assert_eq!(51, play(INPUT, 1570).unwrap().1);
}
}