y2015d22 finally, alot of small things to watch out for

This commit is contained in:
Fabian Schmidt 2024-12-02 15:51:01 +01:00
parent 67d3a56011
commit 4d1477d7c1
3 changed files with 347 additions and 54 deletions

179
day22.js Normal file
View File

@ -0,0 +1,179 @@
'use strict';
var bossStats = {
hp: 71,
damageAmt: 10,
}
class Player {
constructor(initial, isWizard) {
this.history = [];
this.initial = initial;
this.isWizard = !!isWizard;
if (this.isWizard) {
this.spells = [
{
cost: 53,
effect: (m, o) => o.damage(4),
},
{
cost: 73,
effect: (m, o) => { o.damage(2); m.hp += 2; },
},
{
cost: 113,
start: (m, o) => m.armor += 7,
effect: (m, o) => { },
end: (m, o) => m.armor -= 7,
duration: 6,
},
{
cost: 173,
effect: (m, o) => o.damage(3),
duration: 6,
},
{
cost: 229,
effect: (m, o) => m.mana += 101,
duration: 5,
},
];
}
this.start();
}
attack(opponent, spellIdx) {
if (!this.isWizard) {
opponent.damage(this.damageAmt);
} else {
this.history.push(spellIdx);
var spell = this.spells[spellIdx];
this.spent += spell.cost;
this.mana -= spell.cost;
if (spell.duration) {
var newSpell = {
idx: spellIdx,
effect: spell.effect,
duration: spell.duration,
};
if (spell.start) {
spell.start(this, opponent);
}
if (spell.end) {
newSpell.end = spell.end;
}
this.activeSpells.push(newSpell);
} else {
spell.effect(this, opponent);
}
}
}
damage(n) {
this.hp -= Math.max(1, n - this.armor);
}
duplicate() {
var newPlayer = new Player(this.initial, this.isWizard);
newPlayer.hp = this.hp;
newPlayer.spent = this.spent;
newPlayer.armor = this.armor;
newPlayer.turn = this.turn;
for (var i = 0; i < this.activeSpells.length; i++) {
newPlayer.activeSpells.push(Object.assign({}, this.activeSpells[i]));
}
for (var i = 0; i < this.history.length; i++) {
newPlayer.history.push(this.history[i]);
}
if (this.isWizard)
newPlayer.mana = this.mana;
else
newPlayer.damageAmt = this.damageAmt;
return newPlayer;
}
takeTurn(opponent) {
this.turn++;
for (var i = 0; i < this.activeSpells.length; i++) {
var spell = this.activeSpells[i];
if (spell.duration > 0) {
spell.effect(this, opponent);
spell.duration--;
if (spell.duration === 0 && spell.end) {
spell.end(this, opponent);
}
}
}
}
start() {
this.hp = this.initial.hp;
this.spent = 0;
this.armor = 0;
this.turn = 0;
this.activeSpells = [];
if (this.isWizard)
this.mana = this.initial.mana;
else
this.damageAmt = this.initial.damageAmt;
}
}
var me = new Player({ hp: 50, mana: 500 }, true);
var boss = new Player(bossStats);
var cheapestSpent = Infinity;
function playAllGames(me, boss, partTwo, depth) {
depth = depth || 0;
for (var i = 0; i < me.spells.length; i++) {
var spellMatch = false;
for (var j = 0; j < me.activeSpells.length; j++) {
if (me.activeSpells[j].duration > 1 && i === me.activeSpells[j].idx) {
spellMatch = true;;
}
}
if (spellMatch)
continue;
if (me.spells[i].cost > me.mana) {
continue;
}
var newMe = me.duplicate();
var newBoss = boss.duplicate();
if (partTwo)
newMe.hp--;
newMe.takeTurn(newBoss);
newBoss.takeTurn(newMe);
newMe.attack(newBoss, i);
newMe.takeTurn(newBoss);
newBoss.takeTurn(newMe);
newBoss.attack(newMe);
if (newBoss.hp <= 0) {
cheapestSpent = Math.min(cheapestSpent, newMe.spent);
}
if (newMe.hp > (partTwo ? 1 : 0) && newBoss.hp > 0 && newMe.spent < cheapestSpent)
playAllGames(newMe, newBoss, partTwo, depth + 1);
}
}
playAllGames(me, boss);
console.log('Part One:', cheapestSpent);
cheapestSpent = Infinity;
playAllGames(me, boss, true);
console.log('Part Two:', cheapestSpent);

View File

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

View File

@ -24,20 +24,20 @@ pub fn process_part1(input: &str) -> u32 {
}; };
let mut smallest = u32::MAX; let mut smallest = u32::MAX;
let mut leafs = vec![start]; let mut leafs = vec![start];
let mut test = 0;
println!("start"); println!("start");
// Playing, Win, Loss
let mut stats = (0, 0, 0);
loop { loop {
println!("iter: {test},");
let current_leafs = leafs.clone(); let current_leafs = leafs.clone();
println!("leaf count: {}", current_leafs.len());
leafs.clear(); leafs.clear();
// create potential rounds // create potential rounds
for leaf in current_leafs { for leaf in current_leafs {
if leaf.state == State::Playing { if leaf.state == State::Playing {
//println!("try for leaf");
for spell in Spell::get_all() { for spell in Spell::get_all() {
match spell.name { match spell.name {
SpellID::MagicMissile | SpellID::Drain => {} SpellID::MagicMissile | SpellID::Drain => {
leafs.push(leaf.use_spell(spell, false));
}
SpellID::Shield => { SpellID::Shield => {
if !leaf if !leaf
.player .player
@ -45,68 +45,151 @@ pub fn process_part1(input: &str) -> u32 {
.iter() .iter()
.any(|effect| effect.name == SpellID::Shield && effect.duration > 1) .any(|effect| effect.name == SpellID::Shield && effect.duration > 1)
{ {
continue; leafs.push(leaf.use_spell(spell, false));
} }
} }
SpellID::Poison => { SpellID::Poison => {
if !leaf if !leaf
.player .boss
.status_effects .status_effects
.iter() .iter()
.any(|effect| effect.name == SpellID::Poison && effect.duration > 1) .any(|effect| effect.name == SpellID::Poison && effect.duration > 1)
{ {
continue; leafs.push(leaf.use_spell(spell, false));
} }
} }
SpellID::Recharge => { SpellID::Recharge => {
if !leaf.player.status_effects.iter().any(|effect| { if !leaf.player.status_effects.iter().any(|effect| {
effect.name == SpellID::Recharge && effect.duration > 1 effect.name == SpellID::Recharge && effect.duration > 1
}) { }) {
continue; leafs.push(leaf.use_spell(spell, false));
} }
} }
} }
leafs.push(leaf.use_spell(spell));
} }
} }
//leafs.iter().for_each(|leaf| println!("{:?}", leaf));
} }
println!("leaf count: {}", leafs.len());
stats.0 = 0;
for leaf in &leafs { for leaf in &leafs {
if leaf.state == State::Win && leaf.spent_mana < smallest { if leaf.state == State::Win && leaf.spent_mana < smallest {
smallest = leaf.spent_mana; smallest = leaf.spent_mana;
} }
match leaf.state {
State::Playing => stats.0 += 1,
State::Win => stats.1 += 1,
State::Loss => stats.2 += 1,
};
//println!( //println!(
// "player hp: {}, boss hp: {}, spent mana so far: {}", // "player hp: {}, boss hp: {}, spent mana so far: {}",
// leaf.player.hp, leaf.boss.hp, leaf.spent_mana // leaf.player.hp, leaf.boss.hp, leaf.spent_mana
//); //);
} }
leafs = leafs println!("Playing, win, loss: {stats:?}");
.clone() leafs.retain(|leaf| leaf.state == State::Playing && leaf.spent_mana < smallest);
.iter()
.filter(|&leaf| leaf.state == State::Playing || leaf.spent_mana <= smallest)
.map(|leaf| leaf.to_owned())
.collect();
if leafs.is_empty() { if leafs.is_empty() {
break; break;
} }
// already know it's higher than this
if smallest <= 2000 {
let mut count_losses = 0;
for leaf in leafs {
if leaf.state == State::Loss {
count_losses += 1;
}
}
println!("Losses: {count_losses}");
break;
}
test += 1;
println!(" smallest: {smallest}"); println!(" smallest: {smallest}");
} }
smallest smallest
} }
pub fn process_part2(input: &str) -> u32 { pub fn process_part2(input: &str) -> u32 {
0 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()
};
// Initial state, nothing happened yet
let start = RoundNode {
player,
boss,
spent_mana: 0,
state: State::Playing,
};
let mut smallest = u32::MAX;
let mut leafs = vec![start];
println!("start");
// Playing, Win, Loss
let mut stats = (0, 0, 0);
loop {
let current_leafs = leafs.clone();
leafs.clear();
// create potential rounds
for leaf in current_leafs {
if leaf.state == State::Playing {
for spell in Spell::get_all() {
match spell.name {
SpellID::MagicMissile | SpellID::Drain => {
leafs.push(leaf.use_spell(spell, true));
}
SpellID::Shield => {
if !leaf
.player
.status_effects
.iter()
.any(|effect| effect.name == SpellID::Shield && effect.duration > 1)
{
leafs.push(leaf.use_spell(spell, true));
}
}
SpellID::Poison => {
if !leaf
.boss
.status_effects
.iter()
.any(|effect| effect.name == SpellID::Poison && effect.duration > 1)
{
leafs.push(leaf.use_spell(spell, true));
}
}
SpellID::Recharge => {
if !leaf.player.status_effects.iter().any(|effect| {
effect.name == SpellID::Recharge && effect.duration > 1
}) {
leafs.push(leaf.use_spell(spell, true));
}
}
}
}
}
//leafs.iter().for_each(|leaf| println!("{:?}", leaf));
}
println!("leaf count: {}", leafs.len());
stats.0 = 0;
for leaf in &leafs {
if leaf.state == State::Win && leaf.spent_mana < smallest {
smallest = leaf.spent_mana;
}
match leaf.state {
State::Playing => stats.0 += 1,
State::Win => stats.1 += 1,
State::Loss => stats.2 += 1,
};
//println!(
// "player hp: {}, boss hp: {}, spent mana so far: {}",
// leaf.player.hp, leaf.boss.hp, leaf.spent_mana
//);
}
println!("Playing, win, loss: {stats:?}");
leafs.retain(|leaf| leaf.state == State::Playing && leaf.spent_mana < smallest);
if leafs.is_empty() {
break;
}
println!(" smallest: {smallest}");
}
smallest
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -125,7 +208,7 @@ struct RoundNode {
} }
impl RoundNode { impl RoundNode {
fn use_spell(&self, spell: Spell) -> RoundNode { fn use_spell(&self, spell: Spell, hard_mode: bool) -> RoundNode {
if self.state != State::Playing { if self.state != State::Playing {
eprintln!("State: {:#?}", self.state); eprintln!("State: {:#?}", self.state);
eprintln!("spell: {spell:#?}"); eprintln!("spell: {spell:#?}");
@ -133,10 +216,25 @@ impl RoundNode {
} }
let mut player = self.player.clone(); let mut player = self.player.clone();
let mut boss = self.boss.clone(); let mut boss = self.boss.clone();
let mut recharge_actif = false;
if hard_mode {
player.hp -= 1;
}
if player.hp == 0 {
return RoundNode {
player,
boss,
spent_mana: spell.cost + self.spent_mana,
state: State::Loss,
};
}
// Player turn // Player turn
for effect in player.status_effects.iter_mut() { for effect in player.status_effects.iter_mut() {
if effect.name == SpellID::Recharge { if effect.name == SpellID::Recharge {
player.mana += effect.mana; player.mana += effect.mana;
if effect.duration > 1 {
recharge_actif = true;
}
} }
effect.duration -= 1; effect.duration -= 1;
if effect.duration > 0 && effect.name == SpellID::Shield { if effect.duration > 0 && effect.name == SpellID::Shield {
@ -167,8 +265,15 @@ impl RoundNode {
.into_iter() .into_iter()
.filter(|&effect| effect.duration > 0) .filter(|&effect| effect.duration > 0)
.collect::<Vec<Spell>>(); .collect::<Vec<Spell>>();
let used_mana = spell.cost;
let mut dmg = 0; let mut dmg = 0;
if player.mana < spell.cost {
return RoundNode {
player,
boss,
spent_mana: spell.cost + self.spent_mana,
state: State::Loss,
};
}
match spell.name { match spell.name {
SpellID::MagicMissile => { SpellID::MagicMissile => {
player.mana = player.mana.saturating_sub(spell.cost); player.mana = player.mana.saturating_sub(spell.cost);
@ -198,15 +303,15 @@ impl RoundNode {
return RoundNode { return RoundNode {
player, player,
boss, boss,
spent_mana: used_mana + self.spent_mana, spent_mana: spell.cost + self.spent_mana,
state: State::Win, state: State::Win,
}; };
} }
if player.mana == 0 { if player.mana == 0 && !recharge_actif {
return RoundNode { return RoundNode {
player, player,
boss, boss,
spent_mana: used_mana + self.spent_mana, spent_mana: spell.cost + self.spent_mana,
state: State::Loss, state: State::Loss,
}; };
} }
@ -236,7 +341,7 @@ impl RoundNode {
return RoundNode { return RoundNode {
player, player,
boss, boss,
spent_mana: used_mana + self.spent_mana, spent_mana: spell.cost + self.spent_mana,
state: State::Win, state: State::Win,
}; };
} }
@ -252,14 +357,14 @@ impl RoundNode {
return RoundNode { return RoundNode {
player, player,
boss, boss,
spent_mana: used_mana + self.spent_mana, spent_mana: spell.cost + self.spent_mana,
state: State::Loss, state: State::Loss,
}; };
} }
RoundNode { RoundNode {
player, player,
boss, boss,
spent_mana: used_mana + self.spent_mana, spent_mana: spell.cost + self.spent_mana,
state: State::Playing, state: State::Playing,
} }
} }
@ -401,17 +506,15 @@ mod tests {
}; };
let mut smallest = u32::MAX; let mut smallest = u32::MAX;
let mut leafs = vec![start]; let mut leafs = vec![start];
let mut test = 0;
println!("start"); println!("start");
loop { loop {
println!("iter: {test},");
let current_leafs = leafs.clone(); let current_leafs = leafs.clone();
leafs.clear(); leafs.clear();
// create potential rounds // create potential rounds
for leaf in current_leafs { for leaf in current_leafs {
if leaf.state == State::Playing { if leaf.state == State::Playing {
for spell in Spell::get_all() { for spell in Spell::get_all() {
leafs.push(leaf.use_spell(spell)); leafs.push(leaf.use_spell(spell, false));
} }
} }
} }
@ -423,15 +526,14 @@ mod tests {
if leaf.state == State::Playing { if leaf.state == State::Playing {
all_done = false; all_done = false;
} }
println!( //println!(
"player hp: {}, boss hp: {}, spent mana so far: {}", // "player hp: {}, boss hp: {}, spent mana so far: {}",
leaf.player.hp, leaf.boss.hp, leaf.spent_mana // leaf.player.hp, leaf.boss.hp, leaf.spent_mana
); //);
} }
if all_done { if all_done {
break; break;
} }
test += 1;
println!(" smallest: {smallest}"); println!(" smallest: {smallest}");
} }
assert_eq!(smallest, 226); assert_eq!(smallest, 226);
@ -458,17 +560,15 @@ mod tests {
}; };
let mut smallest = u32::MAX; let mut smallest = u32::MAX;
let mut leafs = vec![start]; let mut leafs = vec![start];
let mut test = 0;
println!("start"); println!("start");
loop { loop {
println!("iter: {test},");
let current_leafs = leafs.clone(); let current_leafs = leafs.clone();
leafs.clear(); leafs.clear();
// create potential rounds // create potential rounds
for leaf in current_leafs { for leaf in current_leafs {
if leaf.state == State::Playing { if leaf.state == State::Playing {
for spell in Spell::get_all() { for spell in Spell::get_all() {
leafs.push(leaf.use_spell(spell)); leafs.push(leaf.use_spell(spell, false));
} }
} }
} }
@ -480,20 +580,34 @@ mod tests {
if leaf.state == State::Playing { if leaf.state == State::Playing {
all_done = false; all_done = false;
} }
println!( //println!(
"player hp: {}, boss hp: {}, spent mana so far: {}", // "player hp: {}, boss hp: {}, spent mana so far: {}",
leaf.player.hp, leaf.boss.hp, leaf.spent_mana // leaf.player.hp, leaf.boss.hp, leaf.spent_mana
); //);
} }
if all_done { if all_done {
break; break;
} }
test += 1;
println!(" smallest: {smallest}"); println!(" smallest: {smallest}");
} }
assert_eq!(smallest, 641); assert_eq!(smallest, 641);
} }
#[allow(dead_code)]
fn init_characters() -> (Character, Character) {
let player = Character {
hp: 50,
mana: 500,
..Default::default()
};
let boss = Character {
hp: 14,
damage: 8,
..Default::default()
};
(player, boss)
}
#[test] #[test]
fn part2() { fn part2() {
let result = process_part2(""); let result = process_part2("");