Solutions for 2022, as well as 2015-2018 and 2019 up to day 11

This commit is contained in:
Chris Alge 2023-03-12 15:20:02 +01:00
commit 1895197c49
722 changed files with 375457 additions and 0 deletions

View file

@ -0,0 +1,4 @@
[package]
name = "day24_immune_system_simulator_20xx"
version = "0.1.0"
edition = "2021"

View file

@ -0,0 +1,383 @@
After [a weird buzzing noise](https://www.youtube.com/watch?v=NDVjLt_QHL8&t=7), you appear back at the man's cottage. He seems relieved to see his friend, but quickly notices that the little reindeer caught some kind of cold while out exploring.
The portly man explains that this reindeer's immune system isn't similar to regular reindeer immune systems:
The *immune system* and the *infection* each have an army made up of several *groups*; each *group* consists of one or more identical *units*. The armies repeatedly *fight* until only one army has units remaining.
*Units* within a group all have the same *hit points* (amount of damage a unit can take before it is destroyed), *attack damage* (the amount of damage each unit deals), an *attack type*, an *initiative* (higher initiative units attack first and win ties), and sometimes *weaknesses* or *immunities*. Here is an example group:
```
18 units each with 729 hit points (weak to fire; immune to cold, slashing)
with an attack that does 8 radiation damage at initiative 10
```
Each group also has an *effective power*: the number of units in that group multiplied by their attack damage. The above group has an effective power of `18 * 8 = 144`. Groups never have zero or negative units; instead, the group is removed from combat.
Each *fight* consists of two phases: *target selection* and *attacking*.
During the *target selection* phase, each group attempts to choose one target. In decreasing order of effective power, groups choose their targets; in a tie, the group with the higher initiative chooses first. The attacking group chooses to target the group in the enemy army to which it would deal the most damage (after accounting for weaknesses and immunities, but not accounting for whether the defending group has enough units to actually receive all of that damage).
If an attacking group is considering two defending groups to which it would deal equal damage, it chooses to target the defending group with the largest effective power; if there is still a tie, it chooses the defending group with the highest initiative. If it cannot deal any defending groups damage, it does not choose a target. Defending groups can only be chosen as a target by one attacking group.
At the end of the target selection phase, each group has selected zero or one groups to attack, and each group is being attacked by zero or one groups.
During the *attacking* phase, each group deals damage to the target it selected, if any. Groups attack in decreasing order of initiative, regardless of whether they are part of the infection or the immune system. (If a group contains no units, it cannot attack.)
The damage an attacking group deals to a defending group depends on the attacking group's attack type and the defending group's immunities and weaknesses. By default, an attacking group would deal damage equal to its *effective power* to the defending group. However, if the defending group is *immune* to the attacking group's attack type, the defending group instead takes *no damage*; if the defending group is *weak* to the attacking group's attack type, the defending group instead takes *double damage*.
The defending group only loses *whole units* from damage; damage is always dealt in such a way that it kills the most units possible, and any remaining damage to a unit that does not immediately kill it is ignored. For example, if a defending group contains `10` units with `10` hit points each and receives `75` damage, it loses exactly `7` units and is left with `3` units at full health.
After the fight is over, if both armies still contain units, a new fight begins; combat only ends once one army has lost all of its units.
For example, consider the following armies:
```
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
```
If these armies were to enter combat, the following fights, including details during the target selection and attacking phases, would take place:
```
Immune System:
Group 1 contains 17 units
Group 2 contains 989 units
Infection:
Group 1 contains 801 units
Group 2 contains 4485 units
Infection group 1 would deal defending group 1 185832 damage
Infection group 1 would deal defending group 2 185832 damage
Infection group 2 would deal defending group 2 107640 damage
Immune System group 1 would deal defending group 1 76619 damage
Immune System group 1 would deal defending group 2 153238 damage
Immune System group 2 would deal defending group 1 24725 damage
Infection group 2 attacks defending group 2, killing 84 units
Immune System group 2 attacks defending group 1, killing 4 units
Immune System group 1 attacks defending group 2, killing 51 units
Infection group 1 attacks defending group 1, killing 17 units
```
```
Immune System:
Group 2 contains 905 units
Infection:
Group 1 contains 797 units
Group 2 contains 4434 units
Infection group 1 would deal defending group 2 184904 damage
Immune System group 2 would deal defending group 1 22625 damage
Immune System group 2 would deal defending group 2 22625 damage
Immune System group 2 attacks defending group 1, killing 4 units
Infection group 1 attacks defending group 2, killing 144 units
```
```
Immune System:
Group 2 contains 761 units
Infection:
Group 1 contains 793 units
Group 2 contains 4434 units
Infection group 1 would deal defending group 2 183976 damage
Immune System group 2 would deal defending group 1 19025 damage
Immune System group 2 would deal defending group 2 19025 damage
Immune System group 2 attacks defending group 1, killing 4 units
Infection group 1 attacks defending group 2, killing 143 units
```
```
Immune System:
Group 2 contains 618 units
Infection:
Group 1 contains 789 units
Group 2 contains 4434 units
Infection group 1 would deal defending group 2 183048 damage
Immune System group 2 would deal defending group 1 15450 damage
Immune System group 2 would deal defending group 2 15450 damage
Immune System group 2 attacks defending group 1, killing 3 units
Infection group 1 attacks defending group 2, killing 143 units
```
```
Immune System:
Group 2 contains 475 units
Infection:
Group 1 contains 786 units
Group 2 contains 4434 units
Infection group 1 would deal defending group 2 182352 damage
Immune System group 2 would deal defending group 1 11875 damage
Immune System group 2 would deal defending group 2 11875 damage
Immune System group 2 attacks defending group 1, killing 2 units
Infection group 1 attacks defending group 2, killing 142 units
```
```
Immune System:
Group 2 contains 333 units
Infection:
Group 1 contains 784 units
Group 2 contains 4434 units
Infection group 1 would deal defending group 2 181888 damage
Immune System group 2 would deal defending group 1 8325 damage
Immune System group 2 would deal defending group 2 8325 damage
Immune System group 2 attacks defending group 1, killing 1 unit
Infection group 1 attacks defending group 2, killing 142 units
```
```
Immune System:
Group 2 contains 191 units
Infection:
Group 1 contains 783 units
Group 2 contains 4434 units
Infection group 1 would deal defending group 2 181656 damage
Immune System group 2 would deal defending group 1 4775 damage
Immune System group 2 would deal defending group 2 4775 damage
Immune System group 2 attacks defending group 1, killing 1 unit
Infection group 1 attacks defending group 2, killing 142 units
```
```
Immune System:
Group 2 contains 49 units
Infection:
Group 1 contains 782 units
Group 2 contains 4434 units
Infection group 1 would deal defending group 2 181424 damage
Immune System group 2 would deal defending group 1 1225 damage
Immune System group 2 would deal defending group 2 1225 damage
Immune System group 2 attacks defending group 1, killing 0 units
Infection group 1 attacks defending group 2, killing 49 units
```
```
Immune System:
No groups remain.
Infection:
Group 1 contains 782 units
Group 2 contains 4434 units
```
In the example above, the winning army ends up with `782 + 4434 = *5216*` units.
You scan the reindeer's condition (your puzzle input); the white-bearded man looks nervous. As it stands now, *how many units would the winning army have*?
Your puzzle answer was `28976`.
\--- Part Two ---
----------
Things aren't looking good for the reindeer. The man asks whether more milk and cookies would help you think.
If only you could give the reindeer's immune system a boost, you might be able to change the outcome of the combat.
A *boost* is an integer increase in immune system units' attack damage. For example, if you were to boost the above example's immune system's units by `1570`, the armies would instead look like this:
```
Immune System:
17 units each with 5390 hit points (weak to radiation, bludgeoning) with
an attack that does 6077 fire damage at initiative 2
989 units each with 1274 hit points (immune to fire; weak to bludgeoning,
slashing) with an attack that does 1595 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
```
With this boost, the combat proceeds differently:
```
Immune System:
Group 2 contains 989 units
Group 1 contains 17 units
Infection:
Group 1 contains 801 units
Group 2 contains 4485 units
Infection group 1 would deal defending group 2 185832 damage
Infection group 1 would deal defending group 1 185832 damage
Infection group 2 would deal defending group 1 53820 damage
Immune System group 2 would deal defending group 1 1577455 damage
Immune System group 2 would deal defending group 2 1577455 damage
Immune System group 1 would deal defending group 2 206618 damage
Infection group 2 attacks defending group 1, killing 9 units
Immune System group 2 attacks defending group 1, killing 335 units
Immune System group 1 attacks defending group 2, killing 32 units
Infection group 1 attacks defending group 2, killing 84 units
```
```
Immune System:
Group 2 contains 905 units
Group 1 contains 8 units
Infection:
Group 1 contains 466 units
Group 2 contains 4453 units
Infection group 1 would deal defending group 2 108112 damage
Infection group 1 would deal defending group 1 108112 damage
Infection group 2 would deal defending group 1 53436 damage
Immune System group 2 would deal defending group 1 1443475 damage
Immune System group 2 would deal defending group 2 1443475 damage
Immune System group 1 would deal defending group 2 97232 damage
Infection group 2 attacks defending group 1, killing 8 units
Immune System group 2 attacks defending group 1, killing 306 units
Infection group 1 attacks defending group 2, killing 29 units
```
```
Immune System:
Group 2 contains 876 units
Infection:
Group 2 contains 4453 units
Group 1 contains 160 units
Infection group 2 would deal defending group 2 106872 damage
Immune System group 2 would deal defending group 2 1397220 damage
Immune System group 2 would deal defending group 1 1397220 damage
Infection group 2 attacks defending group 2, killing 83 units
Immune System group 2 attacks defending group 2, killing 427 units
```
After a few fights...
```
Immune System:
Group 2 contains 64 units
Infection:
Group 2 contains 214 units
Group 1 contains 19 units
Infection group 2 would deal defending group 2 5136 damage
Immune System group 2 would deal defending group 2 102080 damage
Immune System group 2 would deal defending group 1 102080 damage
Infection group 2 attacks defending group 2, killing 4 units
Immune System group 2 attacks defending group 2, killing 32 units
```
```
Immune System:
Group 2 contains 60 units
Infection:
Group 1 contains 19 units
Group 2 contains 182 units
Infection group 1 would deal defending group 2 4408 damage
Immune System group 2 would deal defending group 1 95700 damage
Immune System group 2 would deal defending group 2 95700 damage
Immune System group 2 attacks defending group 1, killing 19 units
```
```
Immune System:
Group 2 contains 60 units
Infection:
Group 2 contains 182 units
Infection group 2 would deal defending group 2 4368 damage
Immune System group 2 would deal defending group 2 95700 damage
Infection group 2 attacks defending group 2, killing 3 units
Immune System group 2 attacks defending group 2, killing 30 units
```
After a few more fights...
```
Immune System:
Group 2 contains 51 units
Infection:
Group 2 contains 40 units
Infection group 2 would deal defending group 2 960 damage
Immune System group 2 would deal defending group 2 81345 damage
Infection group 2 attacks defending group 2, killing 0 units
Immune System group 2 attacks defending group 2, killing 27 units
```
```
Immune System:
Group 2 contains 51 units
Infection:
Group 2 contains 13 units
Infection group 2 would deal defending group 2 312 damage
Immune System group 2 would deal defending group 2 81345 damage
Infection group 2 attacks defending group 2, killing 0 units
Immune System group 2 attacks defending group 2, killing 13 units
```
```
Immune System:
Group 2 contains 51 units
Infection:
No groups remain.
```
This boost would allow the immune system's armies to win! It would be left with `*51*` units.
You don't even know *how* you could boost the reindeer's immune system or what effect it might have, so you need to be cautious and find the *smallest boost* that would allow the immune system to win.
*How many units does the immune system have left* after getting the smallest boost it needs to win?
Your puzzle answer was `3534`.
Both parts of this puzzle are complete! They provide two gold stars: \*\*
At this point, you should [return to your Advent calendar](/2018) and try another puzzle.
If you still want to see it, you can [get your puzzle input](24/input).

View file

@ -0,0 +1,170 @@
#[derive(PartialEq, Clone)]
enum Faction { ImmuneSystem, Infection }
#[derive(Clone)]
struct Group {
faction: Faction,
units: usize,
hit_points: usize,
weaknesses: Vec<String>,
immunities: Vec<String>,
attack_dmg: usize,
attack_type: String,
initiative: usize,
chosen_target: Option<usize>,
targeted: bool,
}
impl Group {
fn effective_power(&self) -> usize {
self.units * self.attack_dmg
}
fn from(line: &str, faction: Faction) -> Self {
let outer: Vec<_> = line.split(&['(', ';', ')']).collect();
let left: Vec<_> = outer[0].split_whitespace().collect();
let atk: Vec<_> = if outer.len() == 1 {
left[7..].to_vec()
} else {
outer[outer.len()-1].split_whitespace().collect()
};
let units = left[0].parse().unwrap();
let hit_points = left[4].parse().unwrap();
let attack_dmg = atk[5].parse().unwrap();
let attack_type = atk[6].to_string();
let initiative = atk[10].parse().unwrap();
let mut weaknesses = Vec::new();
let mut immunities = Vec::new();
if outer.len() > 2 {
for inner in &outer[1..outer.len()-1] {
let line: Vec<_> = inner.trim().split(&[' ', ',']).collect();
match line[0] {
"weak" => {
for w in line.iter().skip(2).step_by(2) {
weaknesses.push(w.to_string());
}
},
"immune" => {
for i in line.iter().skip(2).step_by(2) {
immunities.push(i.to_string());
}
},
_ => panic!("Unexpected line: {inner}"),
}
}
}
Self {
faction,
hit_points,
units,
attack_dmg,
attack_type,
initiative,
weaknesses,
immunities,
chosen_target: None,
targeted: false,
}
}
}
#[derive(Clone)]
struct Battle {
groups: Vec<Group>,
}
impl Battle {
fn fight(&mut self) -> bool {
while self.groups.iter().any(|group| group.faction == Faction::ImmuneSystem) && self.groups.iter().any(|group| group.faction == Faction::Infection) {
let units_before = self.groups.iter().map(|group| group.units).sum::<usize>();
self.combat_round();
if units_before == self.groups.iter().map(|group| group.units).sum::<usize>() {
return false;
}
}
true
}
fn combat_round(&mut self) {
// Target Selection
self.groups.sort_by(|a, b| b.effective_power().cmp(&a.effective_power()).then_with(|| b.initiative.cmp(&a.initiative)));
for attacker_idx in 0..self.groups.len() {
let attacker = &self.groups[attacker_idx];
let mut possible_targets: Vec<_> = self.groups.iter().enumerate().filter(|(_idx, t)| t.faction != attacker.faction && !t.targeted && !t.immunities.contains(&attacker.attack_type)).collect();
let weak_targets: Vec<_> = possible_targets.iter().cloned().filter(|(_idx, t)| t.weaknesses.contains(&attacker.attack_type)).collect();
if !weak_targets.is_empty() {
possible_targets = weak_targets;
}
if !possible_targets.is_empty() {
let target_idx = possible_targets[0].0;
self.groups[attacker_idx].chosen_target = Some(target_idx);
self.groups[target_idx].targeted = true;
}
}
// Attacking
let mut attackers: Vec<usize> = (0..self.groups.len()).collect();
attackers.sort_by_key(|idx| usize::MAX - self.groups[*idx].initiative);
for attacker_idx in attackers {
let attacker = &self.groups[attacker_idx];
if let Some(defender_idx) = attacker.chosen_target {
let mut defender = self.groups[defender_idx].clone();
let mut damage = attacker.effective_power();
if defender.weaknesses.contains(&attacker.attack_type) {
damage *= 2;
}
defender.units = defender.units.saturating_sub(damage / defender.hit_points);
self.groups[defender_idx].units = defender.units;
self.groups[defender_idx].targeted = false;
self.groups[attacker_idx].chosen_target = None;
}
}
// Filter out dead groups
self.groups.retain(|group| group.units > 0);
}
}
pub fn run(input: &str) -> (usize, usize) {
let (immune_system, infection) = input.split_once("\n\n").unwrap();
let armies = Battle { groups: immune_system.lines().skip(1).map(|line| Group::from(line, Faction::ImmuneSystem)).chain(infection.lines().skip(1).map(|line| Group::from(line, Faction::Infection))).collect() };
let mut armies_1 = armies.clone();
armies_1.fight();
let first = armies_1.groups.iter().map(|group| group.units).sum();
let mut armies_2 = armies.clone();
for boost in 1.. {
armies_2.groups.iter_mut().for_each(|group| {
if group.faction == Faction::ImmuneSystem {
group.attack_dmg += boost;
}
});
if armies_2.fight() && armies_2.groups[0].faction == Faction::ImmuneSystem {
break;
}
armies_2 = armies.clone()
}
let second = armies_2.groups.iter().map(|group| group.units).sum();
(first, second)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), (5216, 51));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), (28976, 3534));
}
}

View file

@ -0,0 +1,23 @@
Immune System:
1193 units each with 4200 hit points (immune to slashing, radiation, fire) with an attack that does 33 bludgeoning damage at initiative 2
151 units each with 9047 hit points (immune to slashing, cold; weak to fire) with an attack that does 525 slashing damage at initiative 1
218 units each with 4056 hit points (weak to radiation; immune to fire, slashing) with an attack that does 176 fire damage at initiative 9
5066 units each with 4687 hit points (weak to slashing, fire) with an attack that does 8 slashing damage at initiative 8
2023 units each with 5427 hit points (weak to slashing) with an attack that does 22 slashing damage at initiative 3
3427 units each with 5532 hit points (weak to slashing) with an attack that does 15 cold damage at initiative 13
1524 units each with 8584 hit points (immune to fire) with an attack that does 49 fire damage at initiative 7
82 units each with 2975 hit points (weak to cold, fire) with an attack that does 359 bludgeoning damage at initiative 5
5628 units each with 9925 hit points (weak to fire; immune to cold) with an attack that does 17 radiation damage at initiative 11
1410 units each with 9872 hit points (weak to cold; immune to fire) with an attack that does 60 slashing damage at initiative 10
Infection:
5184 units each with 12832 hit points (weak to fire, cold) with an attack that does 4 fire damage at initiative 20
2267 units each with 13159 hit points (weak to fire; immune to bludgeoning) with an attack that does 10 fire damage at initiative 4
3927 units each with 50031 hit points (weak to slashing, cold; immune to fire, radiation) with an attack that does 23 cold damage at initiative 17
9435 units each with 23735 hit points (immune to cold) with an attack that does 4 cold damage at initiative 12
3263 units each with 26487 hit points (weak to fire) with an attack that does 11 fire damage at initiative 14
222 units each with 15916 hit points (weak to fire) with an attack that does 135 fire damage at initiative 18
972 units each with 45332 hit points (weak to bludgeoning, slashing) with an attack that does 86 cold damage at initiative 19
1456 units each with 39583 hit points (immune to radiation; weak to cold, fire) with an attack that does 53 bludgeoning damage at initiative 16
2813 units each with 28251 hit points with an attack that does 17 cold damage at initiative 15
1179 units each with 42431 hit points (immune to fire, slashing) with an attack that does 55 fire damage at initiative 6

View file

@ -0,0 +1,7 @@
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