From 0ccdec1152932afd4333b161a0880116ab836839 Mon Sep 17 00:00:00 2001 From: timeshifter Date: Sun, 5 Jul 2026 15:40:56 +0200 Subject: [PATCH] add 21 and 22 --- day21/Cargo.lock | 25 +++++ day21/Cargo.toml | 7 ++ day21/src/main.rs | 141 +++++++++++++++++++++++++ day22/Cargo.lock | 7 ++ day22/Cargo.toml | 6 ++ day22/src/main.rs | 255 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 441 insertions(+) create mode 100644 day21/Cargo.lock create mode 100644 day21/Cargo.toml create mode 100644 day21/src/main.rs create mode 100644 day22/Cargo.lock create mode 100644 day22/Cargo.toml create mode 100644 day22/src/main.rs diff --git a/day21/Cargo.lock b/day21/Cargo.lock new file mode 100644 index 0000000..0477d65 --- /dev/null +++ b/day21/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "day21" +version = "0.1.0" +dependencies = [ + "itertools", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] diff --git a/day21/Cargo.toml b/day21/Cargo.toml new file mode 100644 index 0000000..6b9256a --- /dev/null +++ b/day21/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "day21" +version = "0.1.0" +edition = "2024" + +[dependencies] +itertools = "*" diff --git a/day21/src/main.rs b/day21/src/main.rs new file mode 100644 index 0000000..dffed59 --- /dev/null +++ b/day21/src/main.rs @@ -0,0 +1,141 @@ +use itertools::Itertools; + +const WEAPONS: [(&str, i32, i32, i32); 5] = [ + ("Dagger", 8, 4, 0), + ("Shortsword", 10, 5, 0), + ("Warhammer", 25, 6, 0), + ("Longsword", 40, 7, 0), + ("Greataxe", 74, 8, 0), +]; + +const ARMOR: [(&str, i32, i32, i32); 5] = [ + ("Leather", 13, 0, 1), + ("Chainmail", 31, 0, 2), + ("Splintmail", 53, 0, 3), + ("Bandedmail", 75, 0, 4), + ("Platemail", 102, 0, 5), +]; + +const RINGS: [(&str, i32, i32, i32); 6] = [ + ("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), +]; + +#[derive(Clone, Debug)] +struct Loadout { + weapon: Item, + armor: Option, + rings: Vec, +} + +#[derive(Copy, Clone, Debug)] +struct Item { + cost: i32, + damage: i32, + armor: i32, +} + +impl Item { + fn new(cost: i32, damage: i32, armor: i32) -> Self { + Self { + cost, + damage, + armor, + } + } + + fn from_table((_name, cost, damage, armor): (&str, i32, i32, i32)) -> Self { + Self { + cost, + damage, + armor, + } + } +} + +impl Loadout { + fn new(weapon: Item, armor: Option, rings: Vec) -> Self { + Self { + weapon, + armor, + rings, + } + } + + fn damage_value(&self) -> i32 { + self.weapon.damage + self.rings.iter().map(|r| r.damage).sum::() + } + + fn armor_value(&self) -> i32 { + (match &self.armor { + Some(armor) => armor.armor, + None => 0, + }) + self.rings.iter().map(|r| r.armor).sum::() + } + + fn cost(&self) -> i32 { + self.weapon.cost + + (match &self.armor { + Some(armor) => armor.cost, + None => 0, + }) + + self.rings.iter().map(|r| r.cost).sum::() + } +} + +fn main() { + let weapons: Vec<_> = WEAPONS.into_iter().map(Item::from_table).collect(); + let armor: Vec<_> = ARMOR.into_iter().map(Item::from_table).collect(); + let rings: Vec<_> = RINGS.into_iter().map(Item::from_table).collect(); + + let mut loadouts = vec![]; + + for w in &weapons { + for r in rings + .iter() + .combinations(0) + .chain(rings.iter().combinations(1)) + .chain(rings.iter().combinations(2)) + { + for a in &armor { + let loadout = Loadout::new(*w, Some(*a), r.iter().map(|r0| **r0).collect()); + loadouts.push(loadout); + } + let loadout = Loadout::new(*w, None, r.iter().map(|r0| **r0).collect()); + loadouts.push(loadout); + } + } + + let boss = Loadout::new(Item::new(0, 8, 0), Some(Item::new(0, 0, 2)), vec![]); + + let player_hp = 100.; + let boss_hp = 100.; + + let mut cost_where_player_wins = vec![]; + let mut cost_where_player_looses = vec![]; + + for l in &loadouts { + let player_dmg_per_turn = 1.max(l.damage_value() - boss.armor_value()) as f64; + let boss_dmg_per_turn = 1.max(boss.damage_value() - l.armor_value()) as f64; + + let turns_to_kill_boss = (boss_hp / player_dmg_per_turn).ceil(); + let turns_to_kill_player = (player_hp / boss_dmg_per_turn).ceil(); + + let player_wins = turns_to_kill_boss <= turns_to_kill_player; + if player_wins { + cost_where_player_wins.push(l.cost()) + } else { + cost_where_player_looses.push(l.cost()) + } + } + + let result = cost_where_player_wins.iter().min().unwrap(); + println!("{result}"); + + let result = cost_where_player_looses.iter().max().unwrap(); + println!("{result}"); +} diff --git a/day22/Cargo.lock b/day22/Cargo.lock new file mode 100644 index 0000000..b72d6d7 --- /dev/null +++ b/day22/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "day22" +version = "0.1.0" diff --git a/day22/Cargo.toml b/day22/Cargo.toml new file mode 100644 index 0000000..64cb936 --- /dev/null +++ b/day22/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day22" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/day22/src/main.rs b/day22/src/main.rs new file mode 100644 index 0000000..b8e7a10 --- /dev/null +++ b/day22/src/main.rs @@ -0,0 +1,255 @@ +use std::collections::{HashSet, VecDeque}; + +fn main() { + // part 1: 900 + // part 2: 1216 + let player = Character::new(50, 500, 0); + let boss = Character::new(51, 0, 9); + + let game = Game::new(player, boss); + + let mut visited = HashSet::new(); + visited.insert(game.clone()); + + let mut queue = VecDeque::new(); + + queue.push_back(game); + + while let Some(state) = queue.pop_front() { + if !state.player.is_alive || !state.boss.is_alive { + continue; + } + + for spell in get_spells() { + let new_state = state.execute_turn(spell); + if !visited.contains(&new_state) { + visited.insert(new_state.clone()); + queue.push_back(new_state); + } + } + } + let mut best_mana_spent = 10_000_000; + for state in visited { + if state.player.is_alive && !state.boss.is_alive && state.mana_spent < best_mana_spent { + best_mana_spent = state.mana_spent; + } + } + println!("{best_mana_spent}"); +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +struct Game { + player: Character, + boss: Character, + mana_spent: u64, +} + +impl Game { + fn new(player: Character, boss: Character) -> Self { + Self { + player, + boss, + mana_spent: 0, + } + } + + fn run_effects(&mut self) { + self.player.apply_effects(); + self.player.effects_tick(); + + self.boss.apply_effects(); + self.boss.effects_tick(); + } + + fn execute_turn(&self, spell: Spell) -> Self { + let mut new_state = self.clone(); + + new_state.mana_spent += spell.cost; + new_state.player_turn(spell); + new_state.boss_turn(); + + new_state + } + + fn player_turn(&mut self, spell: Spell) { + // part 2 here + self.player.hp -= 1; + + if self.player.hp == 0 { + self.player.is_alive = false; + return; + } + // end part 2 + + self.run_effects(); + + if spell.cost > self.player.mana { + self.player.is_alive = false; + return; + } else { + self.player.mana -= spell.cost; + } + + if spell.remaining_duration > 0 { + match spell.name.as_str() { + "Shield" => { + if self + .player + .effects + .iter() + .all(|s| s.name.as_str() != "Shield") + { + self.player.effects.push(spell); + } + } + "Poison" => { + if self + .boss + .effects + .iter() + .all(|s| s.name.as_str() != "Poison") + { + self.boss.effects.push(spell); + } + } + "Recharge" => { + if self + .player + .effects + .iter() + .all(|s| s.name.as_str() != "Recharge") + { + self.player.effects.push(spell); + } + } + _ => panic!(), + } + } else { + self.boss.hp = self.boss.hp.saturating_sub(1.max(spell.damage)); + self.player.hp += spell.heal; + } + + if self.boss.hp == 0 { + self.boss.is_alive = false; + } + } + + fn boss_turn(&mut self) { + self.run_effects(); + + if self.boss.hp == 0 { + self.boss.is_alive = false; + return; + } + + self.player.hp = self + .player + .hp + .saturating_sub(1.max(self.boss.damage - self.player.armor())); + + if self.player.hp == 0 { + self.player.is_alive = false; + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct Character { + hp: u64, + mana: u64, + damage: u64, + effects: Vec, + is_alive: bool, +} + +impl Character { + fn effects_tick(&mut self) { + let mut to_remove = vec![]; + + for (idx, effect) in self.effects.iter_mut().enumerate() { + effect.remaining_duration -= 1; + if effect.remaining_duration == 0 { + to_remove.push(idx); + } + } + + for idx in to_remove.iter().rev() { + self.effects.remove(*idx); + } + } + + fn new(hp: u64, mana: u64, damage: u64) -> Self { + Self { + hp, + mana, + damage, + effects: vec![], + is_alive: true, + } + } + + fn armor(&self) -> u64 { + self.effects.iter().map(|e| e.armor).sum() + } + + fn apply_effects(&mut self) { + let mut heal = 0; + let mut damage = 0; + let mut mana_increase = 0; + + for effect in &self.effects { + heal += effect.heal; + damage += effect.damage; + mana_increase += effect.mana_increase; + } + + self.hp += heal; + self.hp = self.hp.saturating_sub(damage); + self.mana += mana_increase; + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct Spell { + name: String, + remaining_duration: u64, + cost: u64, + armor: u64, + damage: u64, + mana_increase: u64, + heal: u64, +} + +impl Spell { + fn new( + name: String, + remaining_duration: u64, + cost: u64, + armor: u64, + damage: u64, + mana_increase: u64, + heal: u64, + ) -> Self { + Self { + name, + remaining_duration, + cost, + armor, + damage, + mana_increase, + heal, + } + } +} + +#[rustfmt::skip] +fn get_spells() -> impl Iterator { + [ + Spell::new("Magic Missile".to_string(), 0, 53, 0, 4, 0, 0), + Spell::new("Drain".to_string(), 0, 73, 0, 2, 0, 2), + Spell::new("Shield".to_string(), 6, 113, 7, 0, 0, 0), + Spell::new("Poison".to_string(), 6, 173, 0, 3, 0, 0), + Spell::new("Recharge".to_string(), 5, 229, 0, 0, 101, 0), + ] + .into_iter() +}