y2015d22 finally, alot of small things to watch out for
This commit is contained in:
parent
67d3a56011
commit
4d1477d7c1
179
day22.js
Normal file
179
day22.js
Normal 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);
|
2
justfile
2
justfile
@ -5,4 +5,4 @@ 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
|
||||
cargo test --package y{{year}} --lib days::d{{day}}::tests::part{{part}} -- --nocapture
|
||||
|
@ -24,20 +24,20 @@ pub fn process_part1(input: &str) -> u32 {
|
||||
};
|
||||
let mut smallest = u32::MAX;
|
||||
let mut leafs = vec![start];
|
||||
let mut test = 0;
|
||||
println!("start");
|
||||
// Playing, Win, Loss
|
||||
let mut stats = (0, 0, 0);
|
||||
loop {
|
||||
println!("iter: {test},");
|
||||
let current_leafs = leafs.clone();
|
||||
println!("leaf count: {}", current_leafs.len());
|
||||
leafs.clear();
|
||||
// create potential rounds
|
||||
for leaf in current_leafs {
|
||||
if leaf.state == State::Playing {
|
||||
//println!("try for leaf");
|
||||
for spell in Spell::get_all() {
|
||||
match spell.name {
|
||||
SpellID::MagicMissile | SpellID::Drain => {}
|
||||
SpellID::MagicMissile | SpellID::Drain => {
|
||||
leafs.push(leaf.use_spell(spell, false));
|
||||
}
|
||||
SpellID::Shield => {
|
||||
if !leaf
|
||||
.player
|
||||
@ -45,68 +45,151 @@ pub fn process_part1(input: &str) -> u32 {
|
||||
.iter()
|
||||
.any(|effect| effect.name == SpellID::Shield && effect.duration > 1)
|
||||
{
|
||||
continue;
|
||||
leafs.push(leaf.use_spell(spell, false));
|
||||
}
|
||||
}
|
||||
SpellID::Poison => {
|
||||
if !leaf
|
||||
.player
|
||||
.boss
|
||||
.status_effects
|
||||
.iter()
|
||||
.any(|effect| effect.name == SpellID::Poison && effect.duration > 1)
|
||||
{
|
||||
continue;
|
||||
leafs.push(leaf.use_spell(spell, false));
|
||||
}
|
||||
}
|
||||
SpellID::Recharge => {
|
||||
if !leaf.player.status_effects.iter().any(|effect| {
|
||||
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 {
|
||||
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
|
||||
//);
|
||||
}
|
||||
leafs = leafs
|
||||
.clone()
|
||||
.iter()
|
||||
.filter(|&leaf| leaf.state == State::Playing || leaf.spent_mana <= smallest)
|
||||
.map(|leaf| leaf.to_owned())
|
||||
.collect();
|
||||
println!("Playing, win, loss: {stats:?}");
|
||||
leafs.retain(|leaf| leaf.state == State::Playing && leaf.spent_mana < smallest);
|
||||
if leafs.is_empty() {
|
||||
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}");
|
||||
}
|
||||
smallest
|
||||
}
|
||||
|
||||
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)]
|
||||
@ -125,7 +208,7 @@ struct 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 {
|
||||
eprintln!("State: {:#?}", self.state);
|
||||
eprintln!("spell: {spell:#?}");
|
||||
@ -133,10 +216,25 @@ impl RoundNode {
|
||||
}
|
||||
let mut player = self.player.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
|
||||
for effect in player.status_effects.iter_mut() {
|
||||
if effect.name == SpellID::Recharge {
|
||||
player.mana += effect.mana;
|
||||
if effect.duration > 1 {
|
||||
recharge_actif = true;
|
||||
}
|
||||
}
|
||||
effect.duration -= 1;
|
||||
if effect.duration > 0 && effect.name == SpellID::Shield {
|
||||
@ -167,8 +265,15 @@ impl RoundNode {
|
||||
.into_iter()
|
||||
.filter(|&effect| effect.duration > 0)
|
||||
.collect::<Vec<Spell>>();
|
||||
let used_mana = spell.cost;
|
||||
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 {
|
||||
SpellID::MagicMissile => {
|
||||
player.mana = player.mana.saturating_sub(spell.cost);
|
||||
@ -198,15 +303,15 @@ impl RoundNode {
|
||||
return RoundNode {
|
||||
player,
|
||||
boss,
|
||||
spent_mana: used_mana + self.spent_mana,
|
||||
spent_mana: spell.cost + self.spent_mana,
|
||||
state: State::Win,
|
||||
};
|
||||
}
|
||||
if player.mana == 0 {
|
||||
if player.mana == 0 && !recharge_actif {
|
||||
return RoundNode {
|
||||
player,
|
||||
boss,
|
||||
spent_mana: used_mana + self.spent_mana,
|
||||
spent_mana: spell.cost + self.spent_mana,
|
||||
state: State::Loss,
|
||||
};
|
||||
}
|
||||
@ -236,7 +341,7 @@ impl RoundNode {
|
||||
return RoundNode {
|
||||
player,
|
||||
boss,
|
||||
spent_mana: used_mana + self.spent_mana,
|
||||
spent_mana: spell.cost + self.spent_mana,
|
||||
state: State::Win,
|
||||
};
|
||||
}
|
||||
@ -252,14 +357,14 @@ impl RoundNode {
|
||||
return RoundNode {
|
||||
player,
|
||||
boss,
|
||||
spent_mana: used_mana + self.spent_mana,
|
||||
spent_mana: spell.cost + self.spent_mana,
|
||||
state: State::Loss,
|
||||
};
|
||||
}
|
||||
RoundNode {
|
||||
player,
|
||||
boss,
|
||||
spent_mana: used_mana + self.spent_mana,
|
||||
spent_mana: spell.cost + self.spent_mana,
|
||||
state: State::Playing,
|
||||
}
|
||||
}
|
||||
@ -401,17 +506,15 @@ mod tests {
|
||||
};
|
||||
let mut smallest = u32::MAX;
|
||||
let mut leafs = vec![start];
|
||||
let mut test = 0;
|
||||
println!("start");
|
||||
loop {
|
||||
println!("iter: {test},");
|
||||
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() {
|
||||
leafs.push(leaf.use_spell(spell));
|
||||
leafs.push(leaf.use_spell(spell, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -423,15 +526,14 @@ mod tests {
|
||||
if leaf.state == State::Playing {
|
||||
all_done = false;
|
||||
}
|
||||
println!(
|
||||
"player hp: {}, boss hp: {}, spent mana so far: {}",
|
||||
leaf.player.hp, leaf.boss.hp, leaf.spent_mana
|
||||
);
|
||||
//println!(
|
||||
// "player hp: {}, boss hp: {}, spent mana so far: {}",
|
||||
// leaf.player.hp, leaf.boss.hp, leaf.spent_mana
|
||||
//);
|
||||
}
|
||||
if all_done {
|
||||
break;
|
||||
}
|
||||
test += 1;
|
||||
println!(" smallest: {smallest}");
|
||||
}
|
||||
assert_eq!(smallest, 226);
|
||||
@ -458,17 +560,15 @@ mod tests {
|
||||
};
|
||||
let mut smallest = u32::MAX;
|
||||
let mut leafs = vec![start];
|
||||
let mut test = 0;
|
||||
println!("start");
|
||||
loop {
|
||||
println!("iter: {test},");
|
||||
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() {
|
||||
leafs.push(leaf.use_spell(spell));
|
||||
leafs.push(leaf.use_spell(spell, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -480,20 +580,34 @@ mod tests {
|
||||
if leaf.state == State::Playing {
|
||||
all_done = false;
|
||||
}
|
||||
println!(
|
||||
"player hp: {}, boss hp: {}, spent mana so far: {}",
|
||||
leaf.player.hp, leaf.boss.hp, leaf.spent_mana
|
||||
);
|
||||
//println!(
|
||||
// "player hp: {}, boss hp: {}, spent mana so far: {}",
|
||||
// leaf.player.hp, leaf.boss.hp, leaf.spent_mana
|
||||
//);
|
||||
}
|
||||
if all_done {
|
||||
break;
|
||||
}
|
||||
test += 1;
|
||||
println!(" smallest: {smallest}");
|
||||
}
|
||||
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]
|
||||
fn part2() {
|
||||
let result = process_part2("");
|
||||
|
Loading…
Reference in New Issue
Block a user