diff --git a/justfile b/justfile new file mode 100644 index 0000000..8ae16d2 --- /dev/null +++ b/justfile @@ -0,0 +1,8 @@ +gen year day: + cargo run -- {{year}} {{day}} + +run year day: + cargo run --package y{{year}} --bin d{{day}} + +test year day *part='1': + cargo test --package y{{year}} days::d{{day}}::tests::part{{part}} -- --nocapture diff --git a/y2015/resources/22_input.txt b/y2015/resources/22_input.txt new file mode 100644 index 0000000..db93bd0 --- /dev/null +++ b/y2015/resources/22_input.txt @@ -0,0 +1,2 @@ +Hit Points: 71 +Damage: 10 diff --git a/y2015/src/bin/d22.rs b/y2015/src/bin/d22.rs new file mode 100644 index 0000000..7b23363 --- /dev/null +++ b/y2015/src/bin/d22.rs @@ -0,0 +1,20 @@ +use std::fs; + +use y2015::days::d22; + +fn main() { + part1(); + part2(); +} + +fn part1() { + let root = env!("CARGO_MANIFEST_DIR"); + let content = fs::read_to_string(format!("{root}/resources/22_input.txt")).unwrap(); + println!("{}", d22::process_part1(&content)); +} + +fn part2() { + let root = env!("CARGO_MANIFEST_DIR"); + let content = fs::read_to_string(format!("{root}/resources/22_input.txt")).unwrap(); + println!("{}", d22::process_part2(&content)); +} diff --git a/y2015/src/days/d21.rs b/y2015/src/days/d21.rs index ea8583c..de8f310 100644 --- a/y2015/src/days/d21.rs +++ b/y2015/src/days/d21.rs @@ -267,24 +267,3 @@ impl Character { self.items_value += item.cost; } } - -#[cfg(test)] -mod tests { - use super::*; - - const INPUT: &str = "Hit Points: 12 -Damage: 7 -Armor: 2"; - - #[test] - fn part1() { - let result = process_part1(INPUT); - assert_eq!(result, 0); - } - - #[test] - fn part2() { - let result = process_part2(INPUT); - assert_eq!(result, 0); - } -} diff --git a/y2015/src/days/d22.rs b/y2015/src/days/d22.rs new file mode 100644 index 0000000..d5b904c --- /dev/null +++ b/y2015/src/days/d22.rs @@ -0,0 +1,319 @@ +pub fn process_part1(input: &str) -> u32 { + let mut boss = Character::default(); + input.lines().for_each(|line| { + let (attribute, value) = line.split_once(": ").unwrap(); + if attribute == "Hit Points" { + boss.hp = value.parse::().unwrap(); + } else if attribute == "Damage" { + boss.damage = value.parse::().unwrap(); + } + }); + let player = Character { + hp: 50, + mana: 500, + ..Default::default() + }; + let mut used_mana = Vec::new(); + for spell in Spell::get_all() { + used_mana.push(round(player.clone(), boss.clone(), spell, 0)); + } + let mut smallest = u32::MAX; + for state in used_mana { + if state.get_value() < smallest { + smallest = state.get_value(); + } + } + smallest +} + +pub fn process_part2(input: &str) -> u32 { + 0 +} + +fn round(mut player: Character, mut boss: Character, spell: Spell, spent_so_far: u32) -> State { + // Player turn + for effect in player.status_effects.iter_mut() { + if effect.name == SpellID::Recharge { + player.mana += effect.mana; + } + effect.duration -= 1; + if effect.duration > 0 && effect.name == SpellID::Shield { + player.armor = effect.armor; + } else if effect.name == SpellID::Shield { + player.armor = 0; + } + } + for effect in boss.status_effects.iter_mut() { + boss.hp = boss.hp.saturating_sub(effect.damage); + effect.duration -= 1; + } + boss.status_effects = boss + .status_effects + .into_iter() + .filter(|&effect| effect.duration > 0) + .collect::>(); + player.status_effects = player + .status_effects + .into_iter() + .filter(|&effect| effect.duration > 0) + .collect::>(); + let used_mana = spell.cost + spent_so_far; + let mut dmg = 0; + match spell.name { + SpellID::MagicMissile => { + player.mana = player.mana.saturating_sub(spell.cost); + dmg = spell.damage; + } + SpellID::Drain => { + player.mana = player.mana.saturating_sub(spell.cost); + dmg = spell.damage; + player.hp += spell.hp; + } + SpellID::Shield => { + player.mana = player.mana.saturating_sub(spell.cost); + player.status_effects.push(spell); + player.armor = spell.armor; + } + SpellID::Poison => { + player.mana = player.mana.saturating_sub(spell.cost); + boss.status_effects.push(spell); + } + SpellID::Recharge => { + player.mana = player.mana.saturating_sub(spell.cost); + player.status_effects.push(spell); + } + } + boss.hp = boss.hp.saturating_sub(dmg); + if boss.hp == 0 { + return State::Win(used_mana); + } + if player.mana == 0 { + return State::Loss(0); + } + + // Boss turn + for effect in player.status_effects.iter_mut() { + if effect.name == SpellID::Recharge { + player.mana += effect.mana; + } + effect.duration -= 1; + if effect.duration > 0 && effect.name == SpellID::Shield { + player.armor = effect.armor; + } else if effect.name == SpellID::Shield { + player.armor = 0; + } + } + player.status_effects = player + .status_effects + .into_iter() + .filter(|&effect| effect.duration > 0) + .collect::>(); + for effect in boss.status_effects.iter_mut() { + boss.hp = boss.hp.saturating_sub(effect.damage); + effect.duration -= 1; + } + if boss.hp == 0 { + return State::Win(used_mana); + } + boss.status_effects = boss + .status_effects + .into_iter() + .filter(|&effect| effect.duration > 0) + .collect::>(); + let dmg = boss.hit_damage(&player); + player.hp = player.hp.saturating_sub(dmg); + + if player.hp == 0 { + return State::Loss(0); + } + + // next round + let mut smallest_used_mana = State::Playing(u32::MAX); + for spell in Spell::get_all() { + let next_state = round(player.clone(), boss.clone(), spell, used_mana); + match next_state { + State::Playing(mana) => { + if mana < smallest_used_mana.get_value() { + smallest_used_mana = State::Playing(mana); + } + } + State::Win(mana) => { + if mana < smallest_used_mana.get_value() { + smallest_used_mana = State::Win(mana); + } + } + State::Loss(_) => {} + }; + } + smallest_used_mana +} + +#[derive(Debug)] +enum State { + Playing(u32), + Win(u32), + Loss(u32), +} + +impl State { + fn get_value(&self) -> u32 { + match self { + State::Playing(value) => *value, + State::Win(value) => *value, + State::Loss(value) => *value, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SpellID { + MagicMissile, + Drain, + Shield, + Poison, + Recharge, +} + +#[derive(Debug, Clone, Copy)] +struct Spell { + name: SpellID, + hp: u32, + damage: u32, + mana: u32, + armor: u32, + duration: u32, + cost: u32, +} + +impl Spell { + fn get_all() -> Vec { + vec![ + Spell { + name: SpellID::MagicMissile, + hp: 0, + damage: 4, + mana: 0, + armor: 0, + duration: 0, + cost: 53, + }, + Spell { + name: SpellID::Drain, + hp: 2, + damage: 2, + mana: 0, + armor: 0, + duration: 0, + cost: 73, + }, + Spell { + name: SpellID::Shield, + hp: 0, + damage: 0, + mana: 0, + armor: 7, + duration: 6, + cost: 113, + }, + Spell { + name: SpellID::Poison, + hp: 0, + damage: 3, + mana: 0, + armor: 0, + duration: 6, + cost: 173, + }, + Spell { + name: SpellID::Recharge, + hp: 0, + damage: 0, + mana: 101, + armor: 0, + duration: 5, + cost: 229, + }, + ] + } +} + +#[derive(Debug, Default, Clone)] +struct Character { + hp: u32, + damage: u32, + armor: u32, + mana: u32, + status_effects: Vec, +} + +impl Character { + fn hit_damage(&self, other: &Self) -> u32 { + let damage = self.damage.saturating_sub(other.armor); + if damage > 0 { + damage + } else { + 1 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_1() { + let boss = Character { + hp: 13, + damage: 8, + ..Default::default() + }; + let player = Character { + hp: 10, + mana: 250, + ..Default::default() + }; + let mut used_mana = Vec::new(); + for spell in Spell::get_all() { + used_mana.push(round(player.clone(), boss.clone(), spell, 0)); + } + let mut result = u32::MAX; + for state in used_mana { + if state.get_value() < result { + result = state.get_value(); + } + } + assert_eq!(result, 226); + } + + #[test] + fn part1_2() { + let boss = Character { + hp: 14, + damage: 8, + ..Default::default() + }; + let player = Character { + hp: 10, + mana: 250, + ..Default::default() + }; + let mut used_mana = Vec::new(); + for spell in Spell::get_all() { + used_mana.push(round(player.clone(), boss.clone(), spell, 0)); + } + let mut result = u32::MAX; + for state in used_mana { + if state.get_value() < result { + result = state.get_value(); + } + } + assert_eq!(result, 641); + } + + #[test] + fn part2() { + let result = process_part2(""); + assert_eq!(result, 0); + } +} diff --git a/y2015/src/days/mod.rs b/y2015/src/days/mod.rs index fac8109..60520cf 100644 --- a/y2015/src/days/mod.rs +++ b/y2015/src/days/mod.rs @@ -22,3 +22,5 @@ pub mod d19; pub mod d20; pub mod d21; + +pub mod d22;