Skip to content

Commit

Permalink
Year 2015: Day 21
Browse files Browse the repository at this point in the history
  • Loading branch information
joshleaves committed Mar 2, 2024
1 parent eb1f01e commit e636e6f
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Of note:
- The changelog 2015.5.2 has been rewritten from each commit content.
- This file may be amended entirely in the future to adhere to the [GNU Changelog style](https://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html#Style-of-Change-Logs)

## [2015.21.1]
### Added
- Solved [exercice for 2015, day 21](src/year_2015/day_21.rs).

## [2015.20.2]
### Added
- More documentation.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "advent-rs"
version = "2015.20.2"
version = "2015.21.1"
edition = "2021"
authors = ["Arnaud 'red' Rouyer"]
readme = "README.md"
Expand Down
24 changes: 24 additions & 0 deletions NOTES_2015.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,27 @@ year_2015::day_20/year_2015::day_20_v2
</details>
I remembered this one was A PIECE OF SHIT to run in Ruby, as it lasted around over minute on my machine the last time I ran it. Having it run in only one second here is a real pleasure. Some issues flip-flapping between types, but nothing too brutal.

## Day 21: RPG Simulator 20XX

<details>
<summary>📊Tests and benchmarks</summary>

```
test year_2015::day_20::tests::works_with_samples_v1 ... ok
test year_2015::tests::day_20 ... ok
year_2015::day_21/year_2015::day_21_v1
time: [4.8485 µs 4.8549 µs 4.8618 µs]
year_2015::day_21/year_2015::day_21_v2
time: [4.8473 µs 4.8539 µs 4.8609 µs]
```
</details>

<details>
<summary>Ruby version comments</summary>

> ...really nothing to say here.
</details>
Couldn't believe this one is running so fast.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
As I said, [Consistency is hard](https://github.com/joshleaves/advent-rb), and I haven't taken the time to pick up a new language in quite a while.

I'm also adding notes that may be useful if you're (like me) discovering Rust:
- [2015, up to day 20](NOTES_2015.md)
- [2015, up to day 21](NOTES_2015.md)

# Regarding style rules
I'm gonna use a mix of what `cargo fmt` does, with some stuff that feels more natural to me.
Expand Down
11 changes: 11 additions & 0 deletions benches/advent-bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use advent_rs::year_2015::day_17;
use advent_rs::year_2015::day_18;
use advent_rs::year_2015::day_19;
use advent_rs::year_2015::day_20;
use advent_rs::year_2015::day_21;
use criterion::{black_box, criterion_group, criterion_main, Criterion};

pub fn year_2015_benchmark(c: &mut Criterion) {
Expand Down Expand Up @@ -222,6 +223,16 @@ pub fn year_2015_benchmark(c: &mut Criterion) {
b.iter(|| day_20::day_20_v2(black_box(input_year_2015_day_20)))
});
g2015_day_20.finish();

let mut g2015_day_21 = c.benchmark_group("year_2015::day_21");
let input_year_2015_day_21 = include_str!("../inputs/year_2015_day_21_input");
g2015_day_21.bench_function("year_2015::day_21_v1", |b| {
b.iter(|| day_21::day_21_v1(black_box(input_year_2015_day_21)))
});
g2015_day_21.bench_function("year_2015::day_21_v2", |b| {
b.iter(|| day_21::day_21_v2(black_box(input_year_2015_day_21)))
});
g2015_day_21.finish();
}

criterion_group!(benches, year_2015_benchmark);
Expand Down
3 changes: 3 additions & 0 deletions inputs/year_2015_day_21_input
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Hit Points: 100
Damage: 8
Armor: 2
8 changes: 8 additions & 0 deletions src/year_2015.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub mod day_17;
pub mod day_18;
pub mod day_19;
pub mod day_20;
pub mod day_21;

/// Returns the solution for a specified exercise and input.
///
Expand Down Expand Up @@ -248,4 +249,11 @@ mod tests {
assert_eq!(day_20::day_20_v1(input), 831_600);
assert_eq!(day_20::day_20_v2(input), 884_520);
}

#[test]
fn day_21() {
let input = include_str!("../inputs/year_2015_day_21_input");
assert_eq!(day_21::day_21_v1(input), 91);
assert_eq!(day_21::day_21_v2(input), 158);
}
}
233 changes: 233 additions & 0 deletions src/year_2015/day_21.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
//! Advent of Code 2015: Day 21: RPG Simulator 20XX
struct Equipment {
cost: u8,
damage: u8,
armor: u8,
}

const WEAPONS: [Equipment; 5] = [
Equipment {
cost: 8,
damage: 4,
armor: 0,
}, // name: "Dagger"
Equipment {
cost: 10,
damage: 5,
armor: 0,
}, // name: "Shortsword"
Equipment {
cost: 25,
damage: 6,
armor: 0,
}, // name: "Warhammer"
Equipment {
cost: 40,
damage: 7,
armor: 0,
}, // name: "Longsword"
Equipment {
cost: 74,
damage: 8,
armor: 0,
}, // name: "Greataxe"
];
const ARMORS: [Equipment; 6] = [
Equipment {
cost: 0,
damage: 0,
armor: 0,
}, // name: "None"
Equipment {
cost: 13,
damage: 0,
armor: 1,
}, // name: "Leather"
Equipment {
cost: 31,
damage: 0,
armor: 2,
}, // name: "Chainmail"
Equipment {
cost: 53,
damage: 0,
armor: 3,
}, // name: "Splintmail"
Equipment {
cost: 75,
damage: 0,
armor: 4,
}, // name: "Bandedmail"
Equipment {
cost: 102,
damage: 0,
armor: 5,
}, // name: "Platemail"
];
const ACCESSORIES: [Equipment; 8] = [
Equipment {
cost: 0,
damage: 0,
armor: 0,
}, // name: "None"
Equipment {
cost: 0,
damage: 0,
armor: 0,
}, // name: "None"
Equipment {
cost: 25,
damage: 1,
armor: 0,
}, // name: "Damage +1"
Equipment {
cost: 50,
damage: 2,
armor: 0,
}, // name: "Damage +2"
Equipment {
cost: 100,
damage: 3,
armor: 0,
}, // name: "Damage +3"
Equipment {
cost: 20,
damage: 0,
armor: 1,
}, // name: "Defense +1"
Equipment {
cost: 40,
damage: 0,
armor: 2,
}, // name: "Defense +2"
Equipment {
cost: 80,
damage: 0,
armor: 3,
}, // name: "Defense +3"
];

const PLAYER_HP: u8 = 100;

struct Character {
hit_points: u8,
damage: u8,
armor: u8,
}

impl Character {
fn from_equipment(equipment: [&Equipment; 4]) -> (usize, Self) {
let hit_points = PLAYER_HP;
let cost: usize = equipment.iter().map(|s| s.cost as usize).sum();
let damage: u8 = equipment.iter().map(|s| s.damage).sum();
let armor: u8 = equipment.iter().map(|s| s.armor).sum();

(
cost as usize,
Character {
hit_points,
damage,
armor,
},
)
}

fn from_string(boss_input: &str) -> Character {
let mut hit_points: u8 = 0;
let mut damage: u8 = 0;
let mut armor: u8 = 0;
for line in boss_input.lines() {
let parts: Vec<&str> = line.split(": ").collect();
let value = parts[1].parse::<u8>().unwrap();
match parts[0] {
"Hit Points" => hit_points = value,
"Damage" => damage = value,
"Armor" => armor = value,
_ => {
panic!("Invalid input: {}", line)
}
}
}

Character {
hit_points,
damage,
armor,
}
}
}

fn simulate_battle(player: &Character, boss: &Character) -> bool {
let player_damage: u8 = std::cmp::max(1, player.damage as i8 - boss.armor as i8) as u8;
let boss_damage: u8 = std::cmp::max(1, boss.damage as i8 - player.armor as i8) as u8;
let player_turns = (player.hit_points / boss_damage) + 1;
let boss_turns = (boss.hit_points / player_damage) + 1;

player_turns >= boss_turns
}

fn try_on_equipment<CMP>(boss: &Character, initial_best: usize, price_updater: CMP) -> usize
where
CMP: Fn(bool, usize, usize) -> usize,
{
let mut best_price: usize = initial_best;
for weapon in WEAPONS.iter() {
for armor in ARMORS.iter() {
for acc_1 in ACCESSORIES[0..=6].iter() {
for acc_2 in ACCESSORIES[1..=7].iter() {
let (cost, player) = Character::from_equipment([
weapon,
armor,
acc_1,
acc_2,
]);
let result = simulate_battle(&player, boss);
best_price = price_updater(result, best_price, cost);
}
}
}
}

best_price
}

pub fn day_21_v1<'a>(input: impl Into<String>) -> usize {
let boss = Character::from_string(&input.into());
let price_updater = |result: bool, best_price, next_price| {
if result {
std::cmp::min(best_price, next_price)
} else {
best_price
}
};

try_on_equipment(&boss, 256, price_updater)
}

pub fn day_21_v2<'a>(input: impl Into<String>) -> usize {
let boss = Character::from_string(&input.into());
let price_updater = |result: bool, best_price, next_price| {
if !result {
std::cmp::max(best_price, next_price)
} else {
best_price
}
};

try_on_equipment(&boss, 0, price_updater)
}

solvable!(day_21, day_21_v1, day_21_v2, usize);

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn wins_the_fight_with_samples_v1() {
let sample_boss = Character::from_string("Hit Points: 12\nDamage: 7\nArmor: 2");
let sample_player = Character::from_string("Hit Points: 8\nDamage: 5\nArmor: 5");
assert_eq!(simulate_battle(&sample_player, &sample_boss), true);
}
}

0 comments on commit e636e6f

Please sign in to comment.