diff --git a/CHANGELOG.md b/CHANGELOG.md index ab05992..1d23f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/Cargo.toml b/Cargo.toml index 1af7517..594c950 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/NOTES_2015.md b/NOTES_2015.md index 8aeda80..72a5be7 100644 --- a/NOTES_2015.md +++ b/NOTES_2015.md @@ -586,3 +586,27 @@ year_2015::day_20/year_2015::day_20_v2 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 + +
+📊Tests and benchmarks + +``` +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] +``` +
+ +
+Ruby version comments + +> ...really nothing to say here. +
+ +Couldn't believe this one is running so fast. diff --git a/README.md b/README.md index c30d897..3e5d2bd 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/benches/advent-bench.rs b/benches/advent-bench.rs index b5fde12..36e30b5 100644 --- a/benches/advent-bench.rs +++ b/benches/advent-bench.rs @@ -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) { @@ -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); diff --git a/inputs/year_2015_day_21_input b/inputs/year_2015_day_21_input new file mode 100644 index 0000000..db43742 --- /dev/null +++ b/inputs/year_2015_day_21_input @@ -0,0 +1,3 @@ +Hit Points: 100 +Damage: 8 +Armor: 2 diff --git a/src/year_2015.rs b/src/year_2015.rs index 098f2c7..e9c78f9 100644 --- a/src/year_2015.rs +++ b/src/year_2015.rs @@ -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. /// @@ -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); + } } diff --git a/src/year_2015/day_21.rs b/src/year_2015/day_21.rs new file mode 100644 index 0000000..e13bf78 --- /dev/null +++ b/src/year_2015/day_21.rs @@ -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::().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(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) -> 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) -> 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); + } +}