diff --git a/y2015/src/days/d22.rs b/y2015/src/days/d22.rs index d5b904c..f0c7a19 100644 --- a/y2015/src/days/d22.rs +++ b/y2015/src/days/d22.rs @@ -1,3 +1,5 @@ +use core::panic; + pub fn process_part1(input: &str) -> u32 { let mut boss = Character::default(); input.lines().for_each(|line| { @@ -13,15 +15,62 @@ pub fn process_part1(input: &str) -> u32 { 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)); - } + // Initial state, nothing happened yet + let start = RoundNode { + player, + boss, + spent_mana: 0, + state: State::Playing, + }; let mut smallest = u32::MAX; - for state in used_mana { - if state.get_value() < smallest { - smallest = state.get_value(); + let mut leafs = vec![start]; + let mut test = 0; + println!("start"); + 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() { + leafs.push(leaf.use_spell(spell)); + } + } } + for leaf in &leafs { + if leaf.state == State::Win && leaf.spent_mana < smallest { + smallest = leaf.spent_mana; + } + //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(); + 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 } @@ -30,139 +79,179 @@ 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::>(); - player.status_effects = player - .status_effects - .into_iter() - .filter(|&effect| effect.duration > 0) - .collect::>(); - 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::>(); - 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::>(); - 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)] +#[derive(Debug, Clone, PartialEq, Eq)] enum State { - Playing(u32), - Win(u32), - Loss(u32), + Playing, + Win, + Loss, } -impl State { - fn get_value(&self) -> u32 { - match self { - State::Playing(value) => *value, - State::Win(value) => *value, - State::Loss(value) => *value, +#[derive(Debug, Clone)] +struct RoundNode { + player: Character, + boss: Character, + spent_mana: u32, + state: State, +} + +impl RoundNode { + fn use_spell(&self, spell: Spell) -> RoundNode { + if self.state != State::Playing { + eprintln!("State: {:#?}", self.state); + eprintln!("spell: {spell:#?}"); + panic!("State should be Playing"); + } + let mut player = self.player.clone(); + let mut boss = self.boss.clone(); + // 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::>(); + if boss.hp == 0 { + return RoundNode { + player, + boss, + spent_mana: self.spent_mana, + state: State::Win, + }; + } + player.status_effects = player + .status_effects + .into_iter() + .filter(|&effect| effect.duration > 0) + .collect::>(); + let used_mana = spell.cost; + 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 RoundNode { + player, + boss, + spent_mana: used_mana + self.spent_mana, + state: State::Win, + }; + } + if player.mana == 0 { + return RoundNode { + player, + boss, + spent_mana: used_mana + self.spent_mana, + state: State::Loss, + }; + } + + // 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::>(); + for effect in boss.status_effects.iter_mut() { + boss.hp = boss.hp.saturating_sub(effect.damage); + effect.duration -= 1; + } + if boss.hp == 0 { + return RoundNode { + player, + boss, + spent_mana: used_mana + self.spent_mana, + state: State::Win, + }; + } + boss.status_effects = boss + .status_effects + .into_iter() + .filter(|&effect| effect.duration > 0) + .collect::>(); + let dmg = boss.hit_damage(&player); + player.hp = player.hp.saturating_sub(dmg); + + if player.hp == 0 { + return RoundNode { + player, + boss, + spent_mana: used_mana + self.spent_mana, + state: State::Loss, + }; + } + RoundNode { + player, + boss, + spent_mana: used_mana + self.spent_mana, + state: State::Playing, } } + + //fn get_leafs(&self) -> Vec { + // let mut leafs = Vec::new(); + // match self.state { + // State::Playing => { + // if self.next_rounds.is_empty() { + // return vec![self.clone()]; + // } + // let mut round_leafs = Vec::new(); + // for round in self.next_rounds.clone() { + // round_leafs = round.get_leafs(); + // } + // leafs.append(&mut round_leafs); + // leafs + // } + // State::Win => vec![self.clone()], + // State::Loss => vec![self.clone()], + // } + //} } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -273,17 +362,49 @@ mod tests { 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(); + // 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]; + 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)); + } + } } + let mut all_done = true; + for leaf in &leafs { + if leaf.state == State::Win && leaf.spent_mana < smallest { + smallest = leaf.spent_mana; + } + 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 + ); + } + if all_done { + break; + } + test += 1; + println!(" smallest: {smallest}"); } - assert_eq!(result, 226); + assert_eq!(smallest, 226); } #[test] @@ -298,17 +419,49 @@ mod tests { 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(); + // 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]; + 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)); + } + } } + let mut all_done = true; + for leaf in &leafs { + if leaf.state == State::Win && leaf.spent_mana < smallest { + smallest = leaf.spent_mana; + } + 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 + ); + } + if all_done { + break; + } + test += 1; + println!(" smallest: {smallest}"); } - assert_eq!(result, 641); + assert_eq!(smallest, 641); } #[test]