1use utils::prelude::*;
2
3#[derive(Clone, Debug)]
5pub struct Day21 {
6    part1: u32,
7    part2: u32,
8}
9
10const WEAPONS: [(u32, u32); 5] = [(8, 4), (10, 5), (25, 6), (40, 7), (74, 8)];
11const ARMOR: [(u32, u32); 6] = [(0, 0), (13, 1), (31, 2), (53, 3), (75, 4), (102, 5)];
12const RINGS: [(u32, u32, u32); 8] = [
13    (0, 0, 0),
14    (0, 0, 0),
15    (25, 1, 0),
16    (50, 2, 0),
17    (100, 3, 0),
18    (20, 0, 1),
19    (40, 0, 2),
20    (80, 0, 3),
21];
22
23impl Day21 {
24    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
25        let (boss_health, boss_damage, boss_armor) = parser::u32()
26            .with_prefix("Hit Points: ")
27            .with_eol()
28            .then(parser::u32().with_prefix("Damage: ").with_eol())
29            .then(parser::u32().with_prefix("Armor: "))
30            .parse_complete(input)?;
31
32        let mut min_gold_win = u32::MAX;
33        let mut max_gold_loss = 0;
34        for weapon in WEAPONS {
35            for armor in ARMOR {
36                for (i, &ring1) in RINGS.iter().enumerate() {
37                    for &ring2 in &RINGS[i + 1..] {
38                        let gold = weapon.0 + armor.0 + ring1.0 + ring2.0;
39                        let player_damage = weapon.1 + ring1.1 + ring2.1;
40                        let player_armor = armor.1 + ring1.2 + ring2.2;
41
42                        let player_deals = player_damage.saturating_sub(boss_armor).max(1);
43                        let turn_boss_dies = boss_health.div_ceil(player_deals);
44
45                        let boss_deals = boss_damage.saturating_sub(player_armor).max(1);
46                        let turn_player_dies = 100u32.div_ceil(boss_deals);
47
48                        if turn_boss_dies <= turn_player_dies {
49                            min_gold_win = min_gold_win.min(gold);
50                        } else {
51                            max_gold_loss = max_gold_loss.max(gold);
52                        }
53                    }
54                }
55            }
56        }
57
58        Ok(Self {
59            part1: min_gold_win,
60            part2: max_gold_loss,
61        })
62    }
63
64    #[must_use]
65    pub fn part1(&self) -> u32 {
66        self.part1
67    }
68
69    #[must_use]
70    pub fn part2(&self) -> u32 {
71        self.part2
72    }
73}
74
75examples!(Day21 -> (u32, u32) []);