y2015d22 tests work, solution incorrect and slow

This commit is contained in:
Fabian Schmidt 2024-11-15 13:23:25 +01:00
parent 5015474745
commit 33e6f1241c
6 changed files with 351 additions and 21 deletions

8
justfile Normal file
View File

@ -0,0 +1,8 @@
gen year day:
cargo run -- {{year}} {{day}}
run year day:
cargo run --package y{{year}} --bin d{{day}}
test year day *part='1':
cargo test --package y{{year}} days::d{{day}}::tests::part{{part}} -- --nocapture

View File

@ -0,0 +1,2 @@
Hit Points: 71
Damage: 10

20
y2015/src/bin/d22.rs Normal file
View File

@ -0,0 +1,20 @@
use std::fs;
use y2015::days::d22;
fn main() {
part1();
part2();
}
fn part1() {
let root = env!("CARGO_MANIFEST_DIR");
let content = fs::read_to_string(format!("{root}/resources/22_input.txt")).unwrap();
println!("{}", d22::process_part1(&content));
}
fn part2() {
let root = env!("CARGO_MANIFEST_DIR");
let content = fs::read_to_string(format!("{root}/resources/22_input.txt")).unwrap();
println!("{}", d22::process_part2(&content));
}

View File

@ -267,24 +267,3 @@ impl Character {
self.items_value += item.cost; 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);
}
}

319
y2015/src/days/d22.rs Normal file
View File

@ -0,0 +1,319 @@
pub fn process_part1(input: &str) -> u32 {
let mut boss = Character::default();
input.lines().for_each(|line| {
let (attribute, value) = line.split_once(": ").unwrap();
if attribute == "Hit Points" {
boss.hp = value.parse::<u32>().unwrap();
} else if attribute == "Damage" {
boss.damage = value.parse::<u32>().unwrap();
}
});
let player = Character {
hp: 50,
mana: 500,
..Default::default()
};
let mut used_mana = Vec::new();
for spell in Spell::get_all() {
used_mana.push(round(player.clone(), boss.clone(), spell, 0));
}
let mut smallest = u32::MAX;
for state in used_mana {
if state.get_value() < smallest {
smallest = state.get_value();
}
}
smallest
}
pub fn process_part2(input: &str) -> u32 {
0
}
fn round(mut player: Character, mut boss: Character, spell: Spell, spent_so_far: u32) -> State {
// Player turn
for effect in player.status_effects.iter_mut() {
if effect.name == SpellID::Recharge {
player.mana += effect.mana;
}
effect.duration -= 1;
if effect.duration > 0 && effect.name == SpellID::Shield {
player.armor = effect.armor;
} else if effect.name == SpellID::Shield {
player.armor = 0;
}
}
for effect in boss.status_effects.iter_mut() {
boss.hp = boss.hp.saturating_sub(effect.damage);
effect.duration -= 1;
}
boss.status_effects = boss
.status_effects
.into_iter()
.filter(|&effect| effect.duration > 0)
.collect::<Vec<Spell>>();
player.status_effects = player
.status_effects
.into_iter()
.filter(|&effect| effect.duration > 0)
.collect::<Vec<Spell>>();
let used_mana = spell.cost + spent_so_far;
let mut dmg = 0;
match spell.name {
SpellID::MagicMissile => {
player.mana = player.mana.saturating_sub(spell.cost);
dmg = spell.damage;
}
SpellID::Drain => {
player.mana = player.mana.saturating_sub(spell.cost);
dmg = spell.damage;
player.hp += spell.hp;
}
SpellID::Shield => {
player.mana = player.mana.saturating_sub(spell.cost);
player.status_effects.push(spell);
player.armor = spell.armor;
}
SpellID::Poison => {
player.mana = player.mana.saturating_sub(spell.cost);
boss.status_effects.push(spell);
}
SpellID::Recharge => {
player.mana = player.mana.saturating_sub(spell.cost);
player.status_effects.push(spell);
}
}
boss.hp = boss.hp.saturating_sub(dmg);
if boss.hp == 0 {
return State::Win(used_mana);
}
if player.mana == 0 {
return State::Loss(0);
}
// Boss turn
for effect in player.status_effects.iter_mut() {
if effect.name == SpellID::Recharge {
player.mana += effect.mana;
}
effect.duration -= 1;
if effect.duration > 0 && effect.name == SpellID::Shield {
player.armor = effect.armor;
} else if effect.name == SpellID::Shield {
player.armor = 0;
}
}
player.status_effects = player
.status_effects
.into_iter()
.filter(|&effect| effect.duration > 0)
.collect::<Vec<Spell>>();
for effect in boss.status_effects.iter_mut() {
boss.hp = boss.hp.saturating_sub(effect.damage);
effect.duration -= 1;
}
if boss.hp == 0 {
return State::Win(used_mana);
}
boss.status_effects = boss
.status_effects
.into_iter()
.filter(|&effect| effect.duration > 0)
.collect::<Vec<Spell>>();
let dmg = boss.hit_damage(&player);
player.hp = player.hp.saturating_sub(dmg);
if player.hp == 0 {
return State::Loss(0);
}
// next round
let mut smallest_used_mana = State::Playing(u32::MAX);
for spell in Spell::get_all() {
let next_state = round(player.clone(), boss.clone(), spell, used_mana);
match next_state {
State::Playing(mana) => {
if mana < smallest_used_mana.get_value() {
smallest_used_mana = State::Playing(mana);
}
}
State::Win(mana) => {
if mana < smallest_used_mana.get_value() {
smallest_used_mana = State::Win(mana);
}
}
State::Loss(_) => {}
};
}
smallest_used_mana
}
#[derive(Debug)]
enum State {
Playing(u32),
Win(u32),
Loss(u32),
}
impl State {
fn get_value(&self) -> u32 {
match self {
State::Playing(value) => *value,
State::Win(value) => *value,
State::Loss(value) => *value,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SpellID {
MagicMissile,
Drain,
Shield,
Poison,
Recharge,
}
#[derive(Debug, Clone, Copy)]
struct Spell {
name: SpellID,
hp: u32,
damage: u32,
mana: u32,
armor: u32,
duration: u32,
cost: u32,
}
impl Spell {
fn get_all() -> Vec<Spell> {
vec![
Spell {
name: SpellID::MagicMissile,
hp: 0,
damage: 4,
mana: 0,
armor: 0,
duration: 0,
cost: 53,
},
Spell {
name: SpellID::Drain,
hp: 2,
damage: 2,
mana: 0,
armor: 0,
duration: 0,
cost: 73,
},
Spell {
name: SpellID::Shield,
hp: 0,
damage: 0,
mana: 0,
armor: 7,
duration: 6,
cost: 113,
},
Spell {
name: SpellID::Poison,
hp: 0,
damage: 3,
mana: 0,
armor: 0,
duration: 6,
cost: 173,
},
Spell {
name: SpellID::Recharge,
hp: 0,
damage: 0,
mana: 101,
armor: 0,
duration: 5,
cost: 229,
},
]
}
}
#[derive(Debug, Default, Clone)]
struct Character {
hp: u32,
damage: u32,
armor: u32,
mana: u32,
status_effects: Vec<Spell>,
}
impl Character {
fn hit_damage(&self, other: &Self) -> u32 {
let damage = self.damage.saturating_sub(other.armor);
if damage > 0 {
damage
} else {
1
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn part1_1() {
let boss = Character {
hp: 13,
damage: 8,
..Default::default()
};
let player = Character {
hp: 10,
mana: 250,
..Default::default()
};
let mut used_mana = Vec::new();
for spell in Spell::get_all() {
used_mana.push(round(player.clone(), boss.clone(), spell, 0));
}
let mut result = u32::MAX;
for state in used_mana {
if state.get_value() < result {
result = state.get_value();
}
}
assert_eq!(result, 226);
}
#[test]
fn part1_2() {
let boss = Character {
hp: 14,
damage: 8,
..Default::default()
};
let player = Character {
hp: 10,
mana: 250,
..Default::default()
};
let mut used_mana = Vec::new();
for spell in Spell::get_all() {
used_mana.push(round(player.clone(), boss.clone(), spell, 0));
}
let mut result = u32::MAX;
for state in used_mana {
if state.get_value() < result {
result = state.get_value();
}
}
assert_eq!(result, 641);
}
#[test]
fn part2() {
let result = process_part2("");
assert_eq!(result, 0);
}
}

View File

@ -22,3 +22,5 @@ pub mod d19;
pub mod d20; pub mod d20;
pub mod d21; pub mod d21;
pub mod d22;