diff --git a/src/day22.rs b/src/day22.rs index cddc4f2..1a786af 100644 --- a/src/day22.rs +++ b/src/day22.rs @@ -1,4 +1,7 @@ -use std::{cmp::max, collections::BinaryHeap}; +use std::{ + cmp::{max, min}, + collections::BinaryHeap, +}; use aoc_runner_derive::{aoc, aoc_generator}; @@ -73,17 +76,11 @@ impl Wizard { } } -#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] -enum Party { - Wiz, - Bo, -} - #[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] struct State { boss: Boss, wizard: Wizard, - next: Party, + hard: bool, } impl Ord for State { @@ -91,6 +88,7 @@ impl Ord for State { other.wizard.spent.cmp(&self.wizard.spent) } } + impl PartialOrd for State { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -98,7 +96,7 @@ impl PartialOrd for State { } impl State { - fn new(boss: Boss) -> State { + fn new(boss: Boss, hard: bool) -> State { State { boss, wizard: Wizard { @@ -109,17 +107,29 @@ impl State { rem_poison: 0, rem_recharge: 0, }, - next: Party::Wiz, + hard, } } - fn play_wiz(&self) -> RoundResult { - let mut next = self.apply_effects(); - next.next = Party::Bo; - - if self.winner().is_some() { - panic!("should not play if there is a winner!") + fn play_two_turns(&self) -> RoundResult { + let mut next = *self; + if self.hard { + next.wizard.health -= 1; + if next.winner() != Winner::Ongoing { + return vec![]; + } } + let next = next.apply_effects(); + // if next.winner() != Winner::Ongoing { + // return vec![next]; + // } + + if next.wizard.cant_spell() { + // next.wizard.health = -100; + // return vec![next]; + return vec![]; + } + let mut nexts = vec![]; if self.wizard.can_missile() { @@ -155,25 +165,23 @@ impl State { nexts.push(s); } - if self.wizard.can_missile() { + if self.wizard.can_recharge() { let mut s = next; s.wizard.mana -= 229; s.wizard.spent += 229; s.wizard.rem_recharge = 5; nexts.push(s); } - nexts - } - fn play_boss(&self) -> RoundResult { - let armor = self.wizard.armor(); // calc before reducing counter - let mut next = self.apply_effects(); - next.next = Party::Wiz; - if self.winner().is_some() { - panic!("should not play if there is a winner!") + for n in &mut nexts { + let armor = n.wizard.armor(); + *n = n.apply_effects(); + if n.boss.health < 1 { + continue; + } + n.wizard.health -= max(1, n.boss.damage - armor); } - next.wizard.health -= max(1, next.boss.damage - armor); - vec![next] + nexts } fn apply_effects(&self) -> Self { @@ -192,59 +200,114 @@ impl State { next } - fn winner(&self) -> Option { - if self.wizard.cant_spell() && self.next == Party::Wiz { - if self.boss.health < 1 { - return None; - } - return Some(Party::Bo); + fn winner(&self) -> Winner { + if self.wizard.health < 1 { + Winner::Loose + } else if self.boss.health < 1 { + Winner::Win + } else { + Winner::Ongoing } - if self.wizard.health > 0 && self.boss.health < 1 { - return Some(Party::Wiz); - } - if self.wizard.health < 1 && self.boss.health > 0 { - return Some(Party::Bo); - } - if self.wizard.health < 1 && self.boss.health < 1 { - return None; - } - None } } +#[derive(PartialEq, Debug)] +enum Winner { + Win, + Loose, + Ongoing, +} + type RoundResult = Vec; const INTERACTIVE: bool = false; -#[aoc(day22, part1)] -fn part1(boss: &Boss) -> i32 { - let first = State::new(*boss); +fn play(boss: Boss, hard: bool) -> i32 { + let first = State::new(boss, hard); let mut queue = BinaryHeap::new(); queue.push(first); + let mut min_mana = i32::MAX; while let Some(current) = queue.pop() { - if let Some(winner) = current.winner() { - if winner == Party::Wiz { - return current.wizard.spent; - } - continue; - } - let nexts = match current.next { - Party::Wiz => current.play_wiz(), - Party::Bo => current.play_boss(), - }; if INTERACTIVE { let _ = std::io::stdin().read_line(&mut String::new()).unwrap(); println!("{current:?}"); - println!("{:#?}", nexts); + println!("{:?}", current.winner()); } + let mut next = current; + if hard { + next.wizard.health -= 1; + if next.wizard.health < 1 { + continue; + } + } + let next = next.apply_effects(); + + let mut nexts = vec![]; + + if next.wizard.can_missile() { + let mut s = next; + s.wizard.mana -= 53; + s.wizard.spent += 53; + s.boss.health -= 4; + nexts.push(s); + } + + if next.wizard.can_drain() { + let mut s = next; + s.wizard.mana -= 73; + s.wizard.spent += 73; + s.boss.health -= 2; + s.wizard.health += 2; + nexts.push(s); + } + + if next.wizard.can_shield() { + let mut s = next; + s.wizard.mana -= 113; + s.wizard.spent += 113; + s.wizard.rem_shield = 6; + nexts.push(s); + } + + if next.wizard.can_poison() { + let mut s = next; + s.wizard.mana -= 173; + s.wizard.spent += 173; + s.wizard.rem_poison = 6; + nexts.push(s); + } + + if next.wizard.can_recharge() { + let mut s = next; + s.wizard.mana -= 229; + s.wizard.spent += 229; + s.wizard.rem_recharge = 5; + nexts.push(s); + } + for n in nexts { - queue.push(n); + let armor = n.wizard.armor(); + let mut n = n.apply_effects(); + if n.boss.health < 1 { + min_mana = min(min_mana, n.wizard.spent); + queue.retain(|s| s.wizard.spent > min_mana); + } else { + n.wizard.health -= max(1, n.boss.damage - armor); + if n.wizard.health > 0 && n.wizard.spent < min_mana { + queue.push(n); + } + } } } - panic!("no solution found") + return min_mana; +} + +#[aoc(day22, part1)] +fn part1(boss: &Boss) -> i32 { + play(*boss, false) } #[aoc(day22, part2)] -fn part2(_boss: &Boss) -> usize { - 0 +fn part2(boss: &Boss) -> i32 { + play(*boss, true) // 1295 too high }