1use crate::util::hash::*;
11use crate::util::heap::*;
12use crate::util::iter::*;
13use crate::util::parse::*;
14
15type Input = [i16; 2];
16
17#[derive(Clone, Copy, PartialEq, Eq, Hash)]
18struct State {
19 boss_hp: i16,
20 player_hp: i16,
21 player_mana: i16,
22 shield_effect: u8,
23 poison_effect: u8,
24 recharge_effect: u8,
25}
26
27impl State {
28 #[inline]
30 fn apply_spell_effects(&mut self) -> bool {
31 if self.shield_effect > 0 {
32 self.shield_effect -= 1;
33 }
34 if self.poison_effect > 0 {
35 self.poison_effect -= 1;
36 self.boss_hp -= 3;
37 }
38 if self.recharge_effect > 0 {
39 self.recharge_effect -= 1;
40 self.player_mana += 101;
41 }
42
43 self.boss_hp <= 0
44 }
45
46 #[inline]
48 fn boss_turn(&mut self, mut attack: i16) -> bool {
49 if self.shield_effect > 0 {
50 attack = (attack - 7).max(1);
51 }
52
53 self.player_hp -= attack;
54 self.player_hp > 0 && self.player_mana >= 53
55 }
56}
57
58pub fn parse(input: &str) -> Input {
59 input.iter_signed().chunk::<2>().next().unwrap()
60}
61
62pub fn part1(input: &Input) -> i16 {
63 play(*input, false)
64}
65
66pub fn part2(input: &Input) -> i16 {
67 play(*input, true)
68}
69
70fn play(input: Input, hard_mode: bool) -> i16 {
71 let [boss_hp, boss_damage] = input;
72 let start = State {
73 boss_hp,
74 player_hp: 50,
75 player_mana: 500,
76 shield_effect: 0,
77 poison_effect: 0,
78 recharge_effect: 0,
79 };
80
81 let mut todo = MinHeap::new();
82 let mut cache = FastSet::with_capacity(5_000);
83
84 todo.push(0, start);
85 cache.insert(start);
86
87 while let Some((spent, mut state)) = todo.pop() {
88 if state.apply_spell_effects() {
90 return spent;
91 }
92
93 if hard_mode {
95 if state.player_hp > 1 {
96 state.player_hp -= 1;
97 } else {
98 continue;
99 }
100 }
101
102 if state.player_mana >= 53 {
104 let mut next =
105 State { boss_hp: state.boss_hp - 4, player_mana: state.player_mana - 53, ..state };
106
107 if next.apply_spell_effects() {
108 return spent + 53;
109 }
110 if next.boss_turn(boss_damage) && cache.insert(next) {
111 todo.push(spent + 53, next);
112 }
113 }
114
115 if state.player_mana >= 73 {
117 let mut next = State {
118 boss_hp: state.boss_hp - 2,
119 player_hp: state.player_hp + 2,
120 player_mana: state.player_mana - 73,
121 ..state
122 };
123
124 if next.apply_spell_effects() {
125 return spent + 73;
126 }
127 if next.boss_turn(boss_damage) && cache.insert(next) {
128 todo.push(spent + 73, next);
129 }
130 }
131
132 if state.player_mana >= 113 && state.shield_effect == 0 {
134 let mut next =
135 State { player_mana: state.player_mana - 113, shield_effect: 6, ..state };
136
137 if next.apply_spell_effects() {
138 return spent + 113;
139 }
140 if next.boss_turn(boss_damage) && cache.insert(next) {
141 todo.push(spent + 113, next);
142 }
143 }
144
145 if state.player_mana >= 173 && state.poison_effect == 0 {
147 let mut next =
148 State { player_mana: state.player_mana - 173, poison_effect: 6, ..state };
149
150 if next.apply_spell_effects() {
151 return spent + 173;
152 }
153 if next.boss_turn(boss_damage) && cache.insert(next) {
154 todo.push(spent + 173, next);
155 }
156 }
157
158 if state.player_mana >= 229 && state.recharge_effect == 0 {
160 let mut next =
161 State { player_mana: state.player_mana - 229, recharge_effect: 5, ..state };
162
163 if next.apply_spell_effects() {
164 return spent + 229;
165 }
166 if next.boss_turn(boss_damage) && cache.insert(next) {
167 todo.push(spent + 229, next);
168 }
169 }
170 }
171
172 unreachable!()
173}