1use std::ops::ControlFlow;
16
17use crate::util::hash::*;
18use crate::util::heap::*;
19use crate::util::iter::*;
20use crate::util::parse::*;
21
22type Input = [i16; 2];
23
24#[derive(Clone, Copy, Eq, Hash, PartialEq)]
25struct State {
26 boss_hp: i16,
27 player_hp: i16,
28 player_mana: i16,
29 shield_effect: u8,
30 poison_effect: u8,
31 recharge_effect: u8,
32 spent: i16,
33}
34
35impl State {
36 #[inline]
38 fn apply_spell_effects(&mut self) -> bool {
39 if self.shield_effect > 0 {
40 self.shield_effect -= 1;
41 }
42 if self.poison_effect > 0 {
43 self.poison_effect -= 1;
44 self.boss_hp -= 3;
45 }
46 if self.recharge_effect > 0 {
47 self.recharge_effect -= 1;
48 self.player_mana += 101;
49 }
50
51 self.boss_hp <= 0
52 }
53
54 #[inline]
56 fn boss_turn(&mut self, mut attack: i16) -> bool {
57 if self.shield_effect > 0 {
58 attack = (attack - 7).max(1);
59 }
60
61 self.player_hp -= attack;
62 self.player_hp > 0 && self.player_mana >= 53
63 }
64}
65
66pub fn parse(input: &str) -> Input {
67 input.iter_signed().chunk::<2>().next().unwrap()
68}
69
70pub fn part1(input: &Input) -> i16 {
71 play(*input, false).break_value().unwrap()
72}
73
74pub fn part2(input: &Input) -> i16 {
75 play(*input, true).break_value().unwrap()
76}
77
78fn heuristic(spent: i16, boss_hp: i16) -> i16 {
79 let damage_per_turn = 4 + 6; let mana_per_turn = 53; spent + (boss_hp + (damage_per_turn - 1) - 6) / damage_per_turn * mana_per_turn
89}
90
91fn play(input: Input, hard_mode: bool) -> ControlFlow<i16> {
92 let [boss_hp, boss_damage] = input;
93 let start = State {
94 boss_hp,
95 player_hp: 50,
96 player_mana: 500,
97 shield_effect: 0,
98 poison_effect: 0,
99 recharge_effect: 0,
100 spent: 0,
101 };
102
103 let mut todo = MinHeap::new();
104 let mut cache = FastSet::with_capacity(5_000);
105
106 todo.push(heuristic(0, boss_hp), start);
107 cache.insert(start);
108
109 while let Some((_, mut state)) = todo.pop() {
110 let spent = state.spent;
111 if state.apply_spell_effects() {
113 return ControlFlow::Break(spent);
114 }
115
116 if hard_mode {
118 if state.player_hp > 1 {
119 state.player_hp -= 1;
120 } else {
121 continue;
122 }
123 }
124
125 let mut try_cast = |mut next: State| {
127 if next.apply_spell_effects() {
128 return ControlFlow::Break(next.spent);
129 }
130 if next.boss_turn(boss_damage) && cache.insert(next) {
131 todo.push(heuristic(next.spent, next.boss_hp), next);
132 }
133 ControlFlow::Continue(())
134 };
135
136 if state.player_mana >= 53 {
138 let next = State {
139 boss_hp: state.boss_hp - 4,
140 player_mana: state.player_mana - 53,
141 spent: spent + 53,
142 ..state
143 };
144 try_cast(next)?;
145 }
146
147 if state.player_mana >= 73 {
149 let next = State {
150 boss_hp: state.boss_hp - 2,
151 player_hp: state.player_hp + 2,
152 player_mana: state.player_mana - 73,
153 spent: spent + 73,
154 ..state
155 };
156 try_cast(next)?;
157 }
158
159 if state.player_mana >= 113 && state.shield_effect == 0 {
161 let next = State {
162 player_mana: state.player_mana - 113,
163 shield_effect: 6,
164 spent: spent + 113,
165 ..state
166 };
167 try_cast(next)?;
168 }
169
170 if state.player_mana >= 173 && state.poison_effect == 0 {
172 let next = State {
173 player_mana: state.player_mana - 173,
174 poison_effect: 6,
175 spent: spent + 173,
176 ..state
177 };
178 try_cast(next)?;
179 }
180
181 if state.player_mana >= 229 && state.recharge_effect == 0 {
183 let next = State {
184 player_mana: state.player_mana - 229,
185 recharge_effect: 5,
186 spent: spent + 229,
187 ..state
188 };
189 try_cast(next)?;
190 }
191 }
192
193 unreachable!()
194}