y2015d21
This commit is contained in:
parent
aa28df24b3
commit
1d9c848525
3
y2015/resources/21_input.txt
Normal file
3
y2015/resources/21_input.txt
Normal file
@ -0,0 +1,3 @@
|
||||
Hit Points: 103
|
||||
Damage: 9
|
||||
Armor: 2
|
20
y2015/src/bin/d21.rs
Normal file
20
y2015/src/bin/d21.rs
Normal file
@ -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));
|
||||
}
|
290
y2015/src/days/d21.rs
Normal file
290
y2015/src/days/d21.rs
Normal file
@ -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::<u32>().unwrap();
|
||||
}
|
||||
"Damage" => {
|
||||
boss.damage = value.parse::<u32>().unwrap();
|
||||
}
|
||||
"Armor" => {
|
||||
boss.armor = value.parse::<u32>().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::<Vec<&str>>();
|
||||
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::<u32>().unwrap(),
|
||||
damage: item_vec[cols - 2].parse::<u32>().unwrap(),
|
||||
armor: item_vec[cols - 1].parse::<u32>().unwrap(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Item>>();
|
||||
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::<u32>().unwrap();
|
||||
}
|
||||
"Damage" => {
|
||||
boss.damage = value.parse::<u32>().unwrap();
|
||||
}
|
||||
"Armor" => {
|
||||
boss.armor = value.parse::<u32>().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::<Vec<&str>>();
|
||||
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::<u32>().unwrap(),
|
||||
damage: item_vec[cols - 2].parse::<u32>().unwrap(),
|
||||
armor: item_vec[cols - 1].parse::<u32>().unwrap(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Item>>();
|
||||
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<Item>,
|
||||
armors: Vec<Item>,
|
||||
rings: Vec<Item>,
|
||||
) -> Vec<(Item, Option<Item>, Vec<Item>)> {
|
||||
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);
|
||||
}
|
||||
}
|
@ -20,3 +20,5 @@ pub mod d9;
|
||||
pub mod d19;
|
||||
|
||||
pub mod d20;
|
||||
|
||||
pub mod d21;
|
||||
|
Loading…
Reference in New Issue
Block a user