1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
//! # [Day 22: Wizard Simulator 20XX](https://adventofcode.com/2015/day/22)
//!
//! Little Henry Case decides that defeating bosses with
//! [swords and stuff](https://adventofcode.com/2015/day/21) is boring. Now he's playing the game
//! with a wizard. Of course, he gets stuck on another boss and needs your help again.
//!
//! In this version, combat still proceeds with the player and the boss taking alternating turns.
//! The player still goes first. Now, however, you don't get any equipment; instead, you must
//! choose one of your SPELLS to cast. The first character at or below `0` hit points loses.
//!
//! Since you're a wizard, you don't get to wear armor, and you can't attack normally.
//! However, since you do magic damage, your opponent's armor is ignored, and so the boss
//! effectively has zero armor as well. As before, if armor (from a spell, in this case) would
//! reduce damage below `1`, it becomes `1` instead - that is, the boss' attacks always deal at
//! least `1` damage.
//!
//! On each of your turns, you must select one of your SPELLS to cast. If you cannot afford to
//! cast any spell, you lose. Spells cost mana; you start with 500 mana, but have no maximum limit.
//! You must have enough mana to cast a spell, and its cost is immediately deducted when you
//! cast it. Your SPELLS are Magic Missile, Drain, Shield, Poison, and Recharge.
//!
//! - Magic Missile costs `53` mana. It instantly does `4` damage.
//! - Drain costs `73` mana. It instantly does `2` damage and heals you for `2` hit points.
//! - Shield costs `113` mana. It starts an effect that lasts for `6` turns.
//!     While it is active, your armor is increased by `7`.
//! - Poison costs `173` mana. It starts an effect that lasts for `6` turns.
//!     At the start of each turn while it is active, it deals the boss `3` damage.
//! - Recharge costs `229` mana. It starts an effect that lasts for `5` turns.
//!     At the start of each turn while it is active, it gives you `101` new mana.
//!
//! Effects all work the same way. Effects apply at the start of both the player's turns and the
//! boss' turns. Effects are created with a timer (the number of turns they last); at the start of
//! each turn, after they apply any effect they have, their timer is decreased by one. If this
//! decreases the timer to zero, the effect ends. You cannot cast a spell that would start an
//! effect which is already active. However, effects can be started on the same turn they end.
//!
//! For example, suppose the player has `10` hit points and `250` mana, and that the
//! boss has `13` hit points and `8` damage:
//!
//! ```plain
//! -- Player turn --
//! - Player has 10 hit points, 0 armor, 250 mana
//! - Boss has 13 hit points
//! Player casts Poison.
//!
//! -- Boss turn --
//! - Player has 10 hit points, 0 armor, 77 mana
//! - Boss has 13 hit points
//! Poison deals 3 damage; its timer is now 5.
//! Boss attacks for 8 damage.
//!
//! -- Player turn --
//! - Player has 2 hit points, 0 armor, 77 mana
//! - Boss has 10 hit points
//! Poison deals 3 damage; its timer is now 4.
//! Player casts Magic Missile, dealing 4 damage.
//!
//! -- Boss turn --
//! - Player has 2 hit points, 0 armor, 24 mana
//! - Boss has 3 hit points
//! Poison deals 3 damage. This kills the boss, and the player wins.
//! ```
//!
//! Now, suppose the same initial conditions, except that the boss has `14` hit points instead:
//!
//! ```plain
//! -- Player turn --
//! - Player has 10 hit points, 0 armor, 250 mana
//! - Boss has 14 hit points
//! Player casts Recharge.
//!
//! -- Boss turn --
//! - Player has 10 hit points, 0 armor, 21 mana
//! - Boss has 14 hit points
//! Recharge provides 101 mana; its timer is now 4.
//! Boss attacks for 8 damage!
//!
//! -- Player turn --
//! - Player has 2 hit points, 0 armor, 122 mana
//! - Boss has 14 hit points
//! Recharge provides 101 mana; its timer is now 3.
//! Player casts Shield, increasing armor by 7.
//!
//! -- Boss turn --
//! - Player has 2 hit points, 7 armor, 110 mana
//! - Boss has 14 hit points
//! Shield's timer is now 5.
//! Recharge provides 101 mana; its timer is now 2.
//! Boss attacks for 8 - 7 = 1 damage!
//!
//! -- Player turn --
//! - Player has 1 hit point, 7 armor, 211 mana
//! - Boss has 14 hit points
//! Shield's timer is now 4.
//! Recharge provides 101 mana; its timer is now 1.
//! Player casts Drain, dealing 2 damage, and healing 2 hit points.
//!
//! -- Boss turn --
//! - Player has 3 hit points, 7 armor, 239 mana
//! - Boss has 12 hit points
//! Shield's timer is now 3.
//! Recharge provides 101 mana; its timer is now 0.
//! Recharge wears off.
//! Boss attacks for 8 - 7 = 1 damage!
//!
//! -- Player turn --
//! - Player has 2 hit points, 7 armor, 340 mana
//! - Boss has 12 hit points
//! Shield's timer is now 2.
//! Player casts Poison.
//!
//! -- Boss turn --
//! - Player has 2 hit points, 7 armor, 167 mana
//! - Boss has 12 hit points
//! Shield's timer is now 1.
//! Poison deals 3 damage; its timer is now 5.
//! Boss attacks for 8 - 7 = 1 damage!
//!
//! -- Player turn --
//! - Player has 1 hit point, 7 armor, 167 mana
//! - Boss has 9 hit points
//! Shield's timer is now 0.
//! Shield wears off, decreasing armor by 7.
//! Poison deals 3 damage; its timer is now 4.
//! Player casts Magic Missile, dealing 4 damage.
//!
//! -- Boss turn --
//! - Player has 1 hit point, 0 armor, 114 mana
//! - Boss has 2 hit points
//! Poison deals 3 damage. This kills the boss, and the player wins.
//! ```
//!
//! You start with 50 hit points and 500 mana points.
//! The boss's actual stats are in your puzzle input.
//! **What is the least amount of mana you can spend and still win the fight?**
//! (Do not include mana RECHARGE effects as "spending" negative mana.)
//!
//! # Part Two
//!
//! On the next run through the game, you increase the difficulty to hard.
//!
//! At the start of each player turn (before any other effects apply), you lose 1 hit point.
//! If this brings you to or below 0 hit points, you lose.
//!
//! With the same starting stats for you and the boss, **what is the least amount of mana you can
//! spend and still win the fight?**
use std::sync::{Arc, Mutex};

type Stats = (u64, u64);

#[aoc_generator(day22)]
fn parse_input(input: &str) -> Stats {
    let numbers: Vec<u64> = input
        .lines()
        .map(|line| {
            let tokens: Vec<&str> = line.split(": ").collect();
            tokens[1].parse().unwrap()
        })
        .collect();
    (numbers[0], numbers[1])
}

type Spell = (i64, i64, i64, u64, u64, i64, u64);

const MISSILE: Spell = (53, 4, 0, 0, 0, 0, 0);
const DRAIN: Spell = (73, 2, 2, 0, 0, 0, 1);
const SHIELD: Spell = (113, 0, 0, 7, 0, 6, 2);
const POISON: Spell = (173, 3, 0, 0, 0, 6, 3);
const RECHARGE: Spell = (229, 0, 0, 0, 101, 5, 4);
const SPELLS: [Spell; 5] = [MISSILE, DRAIN, SHIELD, POISON, RECHARGE];

/// Part 1: What is the least amount of mana you can spend and still win the fight?
#[aoc(day22, part1)]
fn part1(input: &Stats) -> u64 {
    let least_mana = Arc::new(Mutex::new(u64::MAX));
    sim(
        least_mana.clone(),
        input.0 as i64,
        input.1,
        50,
        500,
        vec![],
        true,
        0,
        false,
    );
    let least_mana = least_mana.lock().unwrap();
    *least_mana
}

#[allow(clippy::too_many_arguments)]
fn sim(
    least_mana_used: Arc<Mutex<u64>>,
    mut boss_hitpoints: i64,
    boss_damage: u64,
    mut player_hitpoints: i64,
    mut player_mana: i64,
    active_spells: Vec<Spell>,
    player_turn: bool,
    mana_used: u64,
    part_two: bool,
) -> bool {
    let mut player_armor = 0;
    if part_two && player_turn {
        player_hitpoints -= 1;
        if player_hitpoints <= 0 {
            return false;
        }
    }
    let mut new_active_spells: Vec<Spell> = vec![];
    for active_spell in active_spells {
        if active_spell.5 >= 0 {
            boss_hitpoints -= active_spell.1;
            player_hitpoints += active_spell.2;
            player_armor += active_spell.3;
            player_mana += active_spell.4 as i64;
        }
        let new_active_spell: Spell = (
            active_spell.0,
            active_spell.1,
            active_spell.2,
            active_spell.3,
            active_spell.4,
            active_spell.5 - 1,
            active_spell.6,
        );
        if new_active_spell.5 > 0 {
            new_active_spells.push(new_active_spell);
        }
    }
    {
        let mut least_mana_used = least_mana_used.lock().unwrap();
        if boss_hitpoints <= 0 {
            if mana_used < *least_mana_used {
                *least_mana_used = mana_used;
            }
            return true;
        }
        if mana_used >= *least_mana_used {
            return false;
        }
        drop(least_mana_used);
    }
    if player_turn {
        for spell in SPELLS {
            let mut spell_already_active = false;
            for new_active_spell in &new_active_spells {
                if new_active_spell.6 == spell.6 {
                    spell_already_active = true;
                    break;
                }
            }
            let spell_mana_cost = spell.0;
            if spell_mana_cost <= player_mana && !spell_already_active {
                let mut a: Vec<Spell> = new_active_spells.clone();
                a.push(spell);
                sim(
                    least_mana_used.clone(),
                    boss_hitpoints,
                    boss_damage,
                    player_hitpoints,
                    player_mana - spell_mana_cost,
                    a,
                    false,
                    mana_used + spell_mana_cost as u64,
                    part_two,
                );
            }
        }
    } else {
        let damage = (boss_damage - player_armor).max(1) as i64;
        player_hitpoints -= damage;
        if player_hitpoints > 0 {
            sim(
                least_mana_used,
                boss_hitpoints,
                boss_damage,
                player_hitpoints,
                player_mana,
                new_active_spells,
                true,
                mana_used,
                part_two,
            );
        }
    }
    true
}

/// Part 2: What is the least amount of mana you can spend and still win the fight?
#[aoc(day22, part2)]
fn part2(input: &Stats) -> u64 {
    let least_mana = Arc::new(Mutex::new(u64::MAX));
    sim(
        least_mana.clone(),
        input.0 as i64,
        input.1,
        50,
        500,
        vec![],
        true,
        0,
        true,
    );
    let least_mana = least_mana.lock().unwrap();
    *least_mana
}