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