Skip to content

Commit 22ce867

Browse files
committed
Day 21
1 parent 08a4592 commit 22ce867

4 files changed

Lines changed: 176 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
1313
| [2025](aoc2025) | 24/24 |
1414
| [2024](aoc2024) | 50/50 |
1515
| [2023](aoc2023) | 50/50 |
16-
| [2015](aoc2015) | 40/50 |
16+
| [2015](aoc2015) | 42/50 |
1717

1818
---
1919

aoc2015/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
2626
| [Day 18](https://adventofcode.com/2024/day/18) | [code](src/bin/18.rs) |||
2727
| [Day 19](https://adventofcode.com/2024/day/19) | [code](src/bin/19.rs) |||
2828
| [Day 20](https://adventofcode.com/2024/day/20) | [code](src/bin/20.rs) |||
29-
| [Day 21](https://adventofcode.com/2024/day/21) | [code](src/bin/21.rs) | _ | _ |
29+
| [Day 21](https://adventofcode.com/2024/day/21) | [code](src/bin/21.rs) | | |
3030
| [Day 22](https://adventofcode.com/2024/day/22) | [code](src/bin/22.rs) | _ | _ |
3131
| [Day 23](https://adventofcode.com/2024/day/23) | [code](src/bin/23.rs) | _ | _ |
3232
| [Day 24](https://adventofcode.com/2024/day/24) | [code](src/bin/24.rs) | _ | _ |

aoc2015/data/examples/21.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Hit Points: 17
2+
Damage: 7
3+
Armor: 2

aoc2015/src/bin/21.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#![feature(int_roundings)]
2+
use std::str::Lines;
3+
4+
use itertools::Itertools;
5+
6+
advent_of_code::solution!(21);
7+
8+
#[derive(Debug, Clone, Copy)]
9+
struct Item(u64, i64, i64);
10+
11+
const WEAPONS: [Item; 5] = [
12+
Item(8, 4, 0),
13+
Item(10, 5, 0),
14+
Item(25, 6, 0),
15+
Item(40, 7, 0),
16+
Item(74, 8, 0),
17+
];
18+
19+
const ARMOURS: [Item; 6] = [
20+
Item(0, 0, 0), // dummy armour
21+
Item(13, 0, 1),
22+
Item(31, 0, 2),
23+
Item(53, 0, 3),
24+
Item(75, 0, 4),
25+
Item(102, 0, 5),
26+
];
27+
28+
const DMG_RINGS: [Item; 4] = [
29+
Item(0, 0, 0), // dummy ring
30+
Item(25, 1, 0),
31+
Item(50, 2, 0),
32+
Item(100, 3, 0),
33+
];
34+
35+
const ARM_RINGS: [Item; 4] = [
36+
Item(0, 0, 0), // dummy ring
37+
Item(20, 0, 1),
38+
Item(40, 0, 2),
39+
Item(80, 0, 3),
40+
];
41+
42+
#[derive(Debug, Clone, Copy)]
43+
struct Loadout {
44+
weapon: Item,
45+
armour: Item,
46+
ring1: Item,
47+
ring2: Item,
48+
}
49+
50+
impl Default for Loadout {
51+
fn default() -> Self {
52+
Self {
53+
weapon: WEAPONS[0],
54+
armour: ARMOURS[0],
55+
ring1: DMG_RINGS[0],
56+
ring2: ARM_RINGS[0],
57+
}
58+
}
59+
}
60+
61+
impl Loadout {
62+
fn loadouts_to_try(part2: bool) -> Vec<Loadout> {
63+
// buying a sword is clearly never the right play in part 2 - upgrades are too cost efficient
64+
let mut rings = DMG_RINGS.iter().copied().collect_vec();
65+
rings.extend(ARM_RINGS.iter().copied());
66+
let ring_sets = rings.iter().combinations(2).collect_vec();
67+
let mut loadouts = Vec::with_capacity(ring_sets.len() * ARMOURS.len() * WEAPONS.len());
68+
let weps = if part2 {
69+
vec![WEAPONS[0]]
70+
} else {
71+
WEAPONS.iter().copied().collect_vec()
72+
};
73+
for rs in ring_sets {
74+
for a in ARMOURS.iter().copied() {
75+
for w in weps.iter().copied() {
76+
loadouts.push(Loadout {
77+
weapon: w,
78+
armour: a,
79+
ring1: *rs[0],
80+
ring2: *rs[1],
81+
});
82+
}
83+
}
84+
}
85+
loadouts.sort_by_key(|r| if part2 { u64::MAX - r.cost() } else { r.cost() });
86+
loadouts
87+
}
88+
89+
fn damage(&self) -> i64 {
90+
self.weapon.1 + self.ring1.1 + self.ring2.1
91+
}
92+
93+
fn def(&self) -> i64 {
94+
self.armour.2 + self.ring1.2 + self.ring2.2
95+
}
96+
97+
fn cost(&self) -> u64 {
98+
self.weapon.0 + self.armour.0 + self.ring1.0 + self.ring2.0
99+
}
100+
}
101+
102+
fn parse_input(input: &str) -> (i64, i64, i64) {
103+
let mut lines = input.lines();
104+
let to_i64 = |lines: &mut Lines<'_>| {
105+
lines
106+
.next()
107+
.expect("Should find line")
108+
.split_whitespace()
109+
.last()
110+
.expect("Should find value")
111+
.parse::<i64>()
112+
.expect("Value should parse")
113+
};
114+
let boss_hp = to_i64(&mut lines);
115+
let boss_damage = to_i64(&mut lines);
116+
let boss_def = to_i64(&mut lines);
117+
(boss_hp, boss_damage, boss_def)
118+
}
119+
120+
pub fn part_one(input: &str) -> Option<u64> {
121+
let (boss_hp, boss_damage, boss_def) = parse_input(input);
122+
#[cfg(test)]
123+
let my_hp = 8_i64;
124+
#[cfg(not(test))]
125+
let my_hp = 100_i64;
126+
let loadouts = Loadout::loadouts_to_try(false);
127+
loadouts.iter().find_map(|lo| {
128+
let turns_to_kill_boss = boss_hp.div_ceil(1.max(lo.damage() - boss_def));
129+
let turns_to_kill_me = my_hp.div_ceil(1.max(boss_damage - lo.def()));
130+
if turns_to_kill_boss <= turns_to_kill_me {
131+
Some(lo.cost())
132+
} else {
133+
None
134+
}
135+
})
136+
}
137+
138+
pub fn part_two(input: &str) -> Option<u64> {
139+
let (boss_hp, boss_damage, boss_def) = parse_input(input);
140+
#[cfg(test)]
141+
let my_hp = 8_i64;
142+
#[cfg(not(test))]
143+
let my_hp = 100_i64;
144+
let loadouts = Loadout::loadouts_to_try(true);
145+
loadouts.iter().find_map(|lo| {
146+
let turns_to_kill_boss = boss_hp.div_ceil(1.max(lo.damage() - boss_def));
147+
let turns_to_kill_me = my_hp.div_ceil(1.max(boss_damage - lo.def()));
148+
if turns_to_kill_boss > turns_to_kill_me {
149+
Some(lo.cost())
150+
} else {
151+
None
152+
}
153+
})
154+
}
155+
156+
#[cfg(test)]
157+
mod tests {
158+
use super::*;
159+
160+
#[test]
161+
fn test_part_one() {
162+
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
163+
assert_eq!(result, Some(123));
164+
}
165+
166+
#[test]
167+
fn test_part_two() {
168+
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
169+
assert_eq!(result, Some(230));
170+
}
171+
}

0 commit comments

Comments
 (0)