From 1d9c848525757d7d254c9e37fc39ce86168356c6 Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Tue, 5 Nov 2024 15:44:23 +0100 Subject: [PATCH] y2015d21 --- y2015/resources/21_input.txt | 3 + y2015/src/bin/d21.rs | 20 +++ y2015/src/days/d21.rs | 290 +++++++++++++++++++++++++++++++++++ y2015/src/days/mod.rs | 2 + 4 files changed, 315 insertions(+) create mode 100644 y2015/resources/21_input.txt create mode 100644 y2015/src/bin/d21.rs create mode 100644 y2015/src/days/d21.rs diff --git a/y2015/resources/21_input.txt b/y2015/resources/21_input.txt new file mode 100644 index 0000000..e749051 --- /dev/null +++ b/y2015/resources/21_input.txt @@ -0,0 +1,3 @@ +Hit Points: 103 +Damage: 9 +Armor: 2 diff --git a/y2015/src/bin/d21.rs b/y2015/src/bin/d21.rs new file mode 100644 index 0000000..977185c --- /dev/null +++ b/y2015/src/bin/d21.rs @@ -0,0 +1,20 @@ +use std::fs; + +use y2015::days::d21; + +fn main() { + part1(); + part2(); +} + +fn part1() { + let root = env!("CARGO_MANIFEST_DIR"); + let content = fs::read_to_string(format!("{root}/resources/21_input.txt")).unwrap(); + println!("{}", d21::process_part1(&content)); +} + +fn part2() { + let root = env!("CARGO_MANIFEST_DIR"); + let content = fs::read_to_string(format!("{root}/resources/21_input.txt")).unwrap(); + println!("{}", d21::process_part2(&content)); +} diff --git a/y2015/src/days/d21.rs b/y2015/src/days/d21.rs new file mode 100644 index 0000000..ea8583c --- /dev/null +++ b/y2015/src/days/d21.rs @@ -0,0 +1,290 @@ +use core::panic; + +// 1 Weapon +// 0-1 Armor +// 0-2 Rings +const SHOP: &str = "Weapons: Cost Damage Armor +Dagger 8 4 0 +Shortsword 10 5 0 +Warhammer 25 6 0 +Longsword 40 7 0 +Greataxe 74 8 0 + +Armor: Cost Damage Armor +Leather 13 0 1 +Chainmail 31 0 2 +Splintmail 53 0 3 +Bandedmail 75 0 4 +Platemail 102 0 5 + +Rings: Cost Damage Armor +Damage +1 25 1 0 +Damage +2 50 2 0 +Damage +3 100 3 0 +Defense +1 20 0 1 +Defense +2 40 0 2 +Defense +3 80 0 3"; + +pub fn process_part1(input: &str) -> u32 { + let player = Character { + hp: 100, + ..Default::default() + }; + let mut boss = Character::default(); + input.lines().for_each(|line| { + let (attribute, value) = line.split_once(": ").unwrap(); + match attribute { + "Hit Points" => { + boss.hp = value.parse::().unwrap(); + } + "Damage" => { + boss.damage = value.parse::().unwrap(); + } + "Armor" => { + boss.armor = value.parse::().unwrap(); + } + _ => panic!("Should not happen"), + } + }); + + let mut weapons = Vec::new(); + let mut armors = Vec::new(); + let mut rings = Vec::new(); + + SHOP.split("\n\n").for_each(|category| { + let mut items = category.lines(); + let (category_name, _) = items.next().unwrap().split_once(":").unwrap(); + let item_vec = items + .map(|item| { + let item_vec = item.split_whitespace().collect::>(); + let cols = item_vec.len(); + let mut name = item_vec[0].to_string(); + if item_vec[1].contains("+") { + name.push_str(item_vec[1]); + } + Item { + name, + cost: item_vec[cols - 3].parse::().unwrap(), + damage: item_vec[cols - 2].parse::().unwrap(), + armor: item_vec[cols - 1].parse::().unwrap(), + } + }) + .collect::>(); + match category_name { + "Weapons" => weapons = item_vec, + "Armor" => armors = item_vec, + "Rings" => rings = item_vec, + _ => panic!("Should not happen"), + } + }); + + //println!("{weapons:#?}"); + //println!("{armors:#?}"); + //println!("{rings:#?}"); + + rings.sort_by(|ring_a, ring_b| ring_a.cost.cmp(&ring_b.cost)); + + let choices = create_item_choices(weapons, armors, rings); + let mut lowest_cost = u32::MAX; + for (weapon, armor, rings) in choices { + let mut current_player = player; + current_player.equip(weapon); + if let Some(armor) = armor { + current_player.equip(armor); + } + if !rings.is_empty() { + for ring in rings { + current_player.equip(ring); + } + } + if current_player.wins_against(&boss) && current_player.items_value < lowest_cost { + lowest_cost = current_player.items_value; + } + } + lowest_cost +} + +pub fn process_part2(input: &str) -> u32 { + let player = Character { + hp: 100, + ..Default::default() + }; + let mut boss = Character::default(); + input.lines().for_each(|line| { + let (attribute, value) = line.split_once(": ").unwrap(); + match attribute { + "Hit Points" => { + boss.hp = value.parse::().unwrap(); + } + "Damage" => { + boss.damage = value.parse::().unwrap(); + } + "Armor" => { + boss.armor = value.parse::().unwrap(); + } + _ => panic!("Should not happen"), + } + }); + + let mut weapons = Vec::new(); + let mut armors = Vec::new(); + let mut rings = Vec::new(); + + SHOP.split("\n\n").for_each(|category| { + let mut items = category.lines(); + let (category_name, _) = items.next().unwrap().split_once(":").unwrap(); + let item_vec = items + .map(|item| { + let item_vec = item.split_whitespace().collect::>(); + let cols = item_vec.len(); + let mut name = item_vec[0].to_string(); + if item_vec[1].contains("+") { + name.push_str(item_vec[1]); + } + Item { + name, + cost: item_vec[cols - 3].parse::().unwrap(), + damage: item_vec[cols - 2].parse::().unwrap(), + armor: item_vec[cols - 1].parse::().unwrap(), + } + }) + .collect::>(); + match category_name { + "Weapons" => weapons = item_vec, + "Armor" => armors = item_vec, + "Rings" => rings = item_vec, + _ => panic!("Should not happen"), + } + }); + + //println!("{weapons:#?}"); + //println!("{armors:#?}"); + //println!("{rings:#?}"); + + rings.sort_by(|ring_a, ring_b| ring_a.cost.cmp(&ring_b.cost)); + + let choices = create_item_choices(weapons, armors, rings); + let mut highest_cost = 0; + for (weapon, armor, rings) in choices { + let mut current_player = player; + current_player.equip(weapon); + if let Some(armor) = armor { + current_player.equip(armor); + } + if !rings.is_empty() { + for ring in rings { + current_player.equip(ring); + } + } + if !current_player.wins_against(&boss) && current_player.items_value > highest_cost { + highest_cost = current_player.items_value; + } + } + highest_cost +} + +fn create_item_choices( + weapons: Vec, + armors: Vec, + rings: Vec, +) -> Vec<(Item, Option, Vec)> { + let mut choices = Vec::new(); + + for weapon in &weapons { + choices.push((weapon.clone(), None, vec![])); + for armor in &armors { + choices.push((weapon.clone(), Some(armor.clone()), vec![])); + for ring1 in &rings { + choices.push((weapon.clone(), Some(armor.clone()), vec![ring1.clone()])); + for ring2 in &rings { + if ring2.name == ring1.name { + continue; + } + choices.push(( + weapon.clone(), + Some(armor.clone()), + vec![ring1.clone(), ring2.clone()], + )); + } + } + } + for ring1 in &rings { + choices.push((weapon.clone(), None, vec![ring1.clone()])); + for ring2 in &rings { + if ring2.name == ring1.name { + continue; + } + choices.push((weapon.clone(), None, vec![ring1.clone(), ring2.clone()])); + } + } + } + choices +} + +#[derive(Debug, Clone)] +struct Item { + name: String, + cost: u32, + damage: u32, + armor: u32, +} + +#[derive(Debug, Default, Clone, Copy)] +struct Character { + hp: u32, + damage: u32, + armor: u32, + items_value: u32, +} + +impl Character { + fn hit_damage(&self, other: &Self) -> u32 { + let damage = self.damage.saturating_sub(other.armor); + if damage > 0 { + damage + } else { + 1 + } + } + + fn wins_against(&self, other: &Self) -> bool { + let damage_to_other = self.hit_damage(other); + let mut turns_to_win = other.hp / damage_to_other; + if other.hp % damage_to_other != 0 { + turns_to_win += 1; + } + let damage_to_self = other.hit_damage(self); + let mut turns_to_lose = self.hp / damage_to_self; + if self.hp % damage_to_self != 0 { + turns_to_lose += 1; + } + turns_to_win <= turns_to_lose + } + + fn equip(&mut self, item: Item) { + self.damage += item.damage; + self.armor += item.armor; + self.items_value += item.cost; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const INPUT: &str = "Hit Points: 12 +Damage: 7 +Armor: 2"; + + #[test] + fn part1() { + let result = process_part1(INPUT); + assert_eq!(result, 0); + } + + #[test] + fn part2() { + let result = process_part2(INPUT); + assert_eq!(result, 0); + } +} diff --git a/y2015/src/days/mod.rs b/y2015/src/days/mod.rs index b715254..fac8109 100644 --- a/y2015/src/days/mod.rs +++ b/y2015/src/days/mod.rs @@ -20,3 +20,5 @@ pub mod d9; pub mod d19; pub mod d20; + +pub mod d21;