Skip to content

Commit 68e0a99

Browse files
committedOct 6, 2023
Year 2015 Day 22
1 parent fc68a0e commit 68e0a99

File tree

8 files changed

+187
-0
lines changed

8 files changed

+187
-0
lines changed
 

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,4 @@ pie
257257
| 19 | [Medicine for Rudolph](https://adventofcode.com/2015/day/19) | [Source](src/year2015/day19.rs) | 188 |
258258
| 20 | [Infinite Elves and Infinite Houses](https://adventofcode.com/2015/day/20) | [Source](src/year2015/day20.rs) | 1671 |
259259
| 21 | [RPG Simulator 20XX](https://adventofcode.com/2015/day/21) | [Source](src/year2015/day21.rs) | 3 |
260+
| 22 | [Wizard Simulator 20XX](https://adventofcode.com/2015/day/22) | [Source](src/year2015/day22.rs) | 257 |

‎benches/benchmark.rs

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ mod year2015 {
5757
benchmark!(year2015, day19);
5858
benchmark!(year2015, day20);
5959
benchmark!(year2015, day21);
60+
benchmark!(year2015, day22);
6061
}
6162

6263
mod year2019 {

‎input/year2015/day22.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hit Points: 55
2+
Damage: 8

‎src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ pub mod year2015 {
181181
pub mod day19;
182182
pub mod day20;
183183
pub mod day21;
184+
pub mod day22;
184185
}
185186

186187
/// # Rescue Santa from deep space with a solar system adventure.

‎src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ fn all_solutions() -> Vec<Solution> {
9797
solution!(year2015, day19),
9898
solution!(year2015, day20),
9999
solution!(year2015, day21),
100+
solution!(year2015, day22),
100101
// 2019
101102
solution!(year2019, day01),
102103
solution!(year2019, day02),

‎src/year2015/day22.rs

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//! # Wizard Simulator 20XX
2+
//!
3+
//! [Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) is ideal
4+
//! for solving this problem. A node in the graph is our current state and each edge is
5+
//! represented by casting a spell to get to a new state.
6+
//!
7+
//! The key to optimizing is to cache previously seen states. As we receive states in strictly
8+
//! increasing order of mana spent if we see a state again then it cannot possibly be optimal
9+
//! and we can discard.
10+
use crate::util::hash::*;
11+
use crate::util::heap::*;
12+
use crate::util::iter::*;
13+
use crate::util::parse::*;
14+
15+
type Input = [i16; 2];
16+
17+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
18+
struct 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+
27+
pub fn parse(input: &str) -> Input {
28+
input.iter_signed().chunk::<2>().next().unwrap()
29+
}
30+
31+
pub fn part1(input: &Input) -> i16 {
32+
play(*input, false)
33+
}
34+
35+
pub fn part2(input: &Input) -> i16 {
36+
play(*input, true)
37+
}
38+
39+
fn play(input: Input, hard_mode: bool) -> i16 {
40+
let [boss_hp, boss_damage] = input;
41+
let start = State {
42+
boss_hp,
43+
player_hp: 50,
44+
player_mana: 500,
45+
shield_effect: 0,
46+
poison_effect: 0,
47+
recharge_effect: 0,
48+
};
49+
50+
let mut todo = MinHeap::new();
51+
let mut cache = FastSet::with_capacity(5_000);
52+
53+
todo.push(0, start);
54+
cache.insert(start);
55+
56+
while let Some((spent, mut state)) = todo.pop() {
57+
// Check winning condition
58+
if apply_spell_effects(&mut state) {
59+
return spent;
60+
}
61+
62+
// Part two
63+
if hard_mode {
64+
if state.player_hp > 1 {
65+
state.player_hp -= 1;
66+
} else {
67+
continue;
68+
}
69+
}
70+
71+
// Magic Missile
72+
if state.player_mana >= 53 {
73+
let mut next =
74+
State { boss_hp: state.boss_hp - 4, player_mana: state.player_mana - 53, ..state };
75+
76+
if apply_spell_effects(&mut next) {
77+
return spent + 53;
78+
}
79+
if boss_turn(&mut next, boss_damage) && cache.insert(next) {
80+
todo.push(spent + 53, next);
81+
}
82+
}
83+
84+
// Drain
85+
if state.player_mana >= 73 {
86+
let mut next = State {
87+
boss_hp: state.boss_hp - 2,
88+
player_hp: state.player_hp + 2,
89+
player_mana: state.player_mana - 73,
90+
..state
91+
};
92+
93+
if apply_spell_effects(&mut next) {
94+
return spent + 73;
95+
}
96+
if boss_turn(&mut next, boss_damage) && cache.insert(next) {
97+
todo.push(spent + 73, next);
98+
}
99+
}
100+
101+
// Shield
102+
if state.player_mana >= 113 && state.shield_effect == 0 {
103+
let mut next =
104+
State { player_mana: state.player_mana - 113, shield_effect: 6, ..state };
105+
106+
if apply_spell_effects(&mut next) {
107+
return spent + 113;
108+
}
109+
if boss_turn(&mut next, boss_damage) && cache.insert(next) {
110+
todo.push(spent + 113, next);
111+
}
112+
}
113+
114+
// Poison
115+
if state.player_mana >= 173 && state.poison_effect == 0 {
116+
let mut next =
117+
State { player_mana: state.player_mana - 173, poison_effect: 6, ..state };
118+
119+
if apply_spell_effects(&mut next) {
120+
return spent + 173;
121+
}
122+
if boss_turn(&mut next, boss_damage) && cache.insert(next) {
123+
todo.push(spent + 173, next);
124+
}
125+
}
126+
127+
// Recharge
128+
if state.player_mana >= 229 && state.recharge_effect == 0 {
129+
let mut next =
130+
State { player_mana: state.player_mana - 229, recharge_effect: 5, ..state };
131+
132+
if apply_spell_effects(&mut next) {
133+
return spent + 229;
134+
}
135+
if boss_turn(&mut next, boss_damage) && cache.insert(next) {
136+
todo.push(spent + 229, next);
137+
}
138+
}
139+
}
140+
141+
unreachable!()
142+
}
143+
144+
/// Applies spell effects to state and returns true if the player has won.
145+
#[inline]
146+
fn apply_spell_effects(state: &mut State) -> bool {
147+
if state.shield_effect > 0 {
148+
state.shield_effect -= 1;
149+
}
150+
if state.poison_effect > 0 {
151+
state.poison_effect -= 1;
152+
state.boss_hp -= 3;
153+
}
154+
if state.recharge_effect > 0 {
155+
state.recharge_effect -= 1;
156+
state.player_mana += 101;
157+
}
158+
159+
state.boss_hp <= 0
160+
}
161+
162+
/// Applies boss attack and returns true if the wizard survives.
163+
#[inline]
164+
fn boss_turn(state: &mut State, mut attack: i16) -> bool {
165+
if state.shield_effect > 0 {
166+
attack = (attack - 7).max(1);
167+
}
168+
169+
state.player_hp -= attack;
170+
state.player_hp > 0 && state.player_mana >= 53
171+
}

‎tests/test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ mod year2015 {
5050
mod day19_test;
5151
mod day20_test;
5252
mod day21_test;
53+
mod day22_test;
5354
}
5455

5556
mod year2019 {

‎tests/year2015/day22_test.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#[test]
2+
fn part1_test() {
3+
// No example data
4+
}
5+
6+
#[test]
7+
fn part2_test() {
8+
// No example data
9+
}

0 commit comments

Comments
 (0)