1use utils::prelude::*;
2
3#[derive(Clone, Debug)]
5pub struct Day22 {
6 boss_health: u32,
7 boss_damage: u32,
8}
9
10impl Day22 {
11 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
12 let (boss_health, boss_damage) = parser::u32()
13 .with_prefix("Hit Points: ")
14 .then(parser::u32().with_prefix("\nDamage: "))
15 .parse_complete(input)?;
16
17 Ok(Self {
18 boss_health,
19 boss_damage,
20 })
21 }
22
23 #[must_use]
24 pub fn part1(&self) -> u32 {
25 self.min_mana(false)
26 }
27
28 #[must_use]
29 pub fn part2(&self) -> u32 {
30 self.min_mana(true)
31 }
32
33 fn min_mana(&self, hard_difficulty: bool) -> u32 {
34 let mut min = u32::MAX;
35
36 State {
37 boss_health: self.boss_health,
38 boss_damage: self.boss_damage,
39 hard_difficulty,
40 player_health: 50,
41 player_mana: 500,
42 spent_mana: 0,
43 shield_timer: 0,
44 poison_timer: 0,
45 recharge_timer: 0,
46 }
47 .player_turn(&mut min);
48
49 min
50 }
51}
52
53#[derive(Clone, Copy, Debug)]
54struct State {
55 boss_health: u32,
56 boss_damage: u32,
57 hard_difficulty: bool,
58 player_health: u32,
59 player_mana: u32,
60 spent_mana: u32,
61 shield_timer: u32,
62 poison_timer: u32,
63 recharge_timer: u32,
64}
65
66impl State {
67 fn player_turn(mut self, min_mana_to_win: &mut u32) {
68 if self.hard_difficulty {
69 if self.player_health <= 1 {
70 return;
72 }
73 self.player_health -= 1;
74 }
75
76 self.apply_effects();
77 if self.boss_health == 0 {
78 *min_mana_to_win = self.spent_mana.min(*min_mana_to_win);
80 return;
81 }
82
83 if self.player_mana >= 173 && self.poison_timer == 0 {
85 State {
86 player_mana: self.player_mana - 173,
87 spent_mana: self.spent_mana + 173,
88 poison_timer: 6,
89 ..self
90 }
91 .boss_turn(min_mana_to_win);
92 }
93
94 if self.player_mana >= 229 && self.recharge_timer == 0 {
96 State {
97 player_mana: self.player_mana - 229,
98 spent_mana: self.spent_mana + 229,
99 recharge_timer: 5,
100 ..self
101 }
102 .boss_turn(min_mana_to_win);
103 }
104
105 if self.player_mana >= 113 && self.shield_timer == 0 {
107 State {
108 player_mana: self.player_mana - 113,
109 spent_mana: self.spent_mana + 113,
110 shield_timer: 6,
111 ..self
112 }
113 .boss_turn(min_mana_to_win);
114 }
115
116 if self.player_mana >= 53 {
118 State {
119 boss_health: self.boss_health.saturating_sub(4),
120 player_mana: self.player_mana - 53,
121 spent_mana: self.spent_mana + 53,
122 ..self
123 }
124 .boss_turn(min_mana_to_win);
125 }
126
127 if self.player_mana >= 73 {
129 State {
130 boss_health: self.boss_health.saturating_sub(2),
131 player_health: self.player_health + 2,
132 player_mana: self.player_mana - 73,
133 spent_mana: self.spent_mana + 73,
134 ..self
135 }
136 .boss_turn(min_mana_to_win);
137 }
138 }
139
140 #[inline]
141 fn boss_turn(mut self, min_mana_to_win: &mut u32) {
142 let min_casts = self.boss_health / (3 + 3 + 4); if self.spent_mana + (min_casts * 53) >= *min_mana_to_win {
148 return;
149 }
150
151 self.apply_effects();
152 if self.boss_health == 0 {
153 *min_mana_to_win = self.spent_mana.min(*min_mana_to_win);
155 return;
156 }
157
158 let armor = if self.shield_timer > 0 { 7 } else { 0 };
159 let boss_damage = self.boss_damage.saturating_sub(armor).max(1);
160 if self.player_health <= boss_damage || self.player_mana < 53 {
161 return;
163 }
164 self.player_health -= boss_damage;
165
166 self.player_turn(min_mana_to_win)
167 }
168
169 #[inline]
170 fn apply_effects(&mut self) {
171 if self.shield_timer > 0 {
172 self.shield_timer -= 1;
173 }
174
175 if self.poison_timer > 0 {
176 self.poison_timer -= 1;
177 self.boss_health = self.boss_health.saturating_sub(3);
178 }
179
180 if self.recharge_timer > 0 {
181 self.recharge_timer -= 1;
182 self.player_mana += 101;
183 }
184 }
185}
186
187examples!(Day22 -> (u32, u32) []);