from dataclasses import dataclass, astuple, replace @dataclass class State: boss_hp: int boss_damage: int player_hp: int = 50 player_mana: int = 500 player_shield: int = 0 shield_timer: int = 0 poison_timer: int = 0 recharge_timer: int = 0 @dataclass class Effect: timer: str start: int @dataclass class Instant: effects: dict[str, int] @dataclass class Spell: cost: int action: Effect | Instant def solve(boss_hp: int, boss_damage: int, hard_mode: bool) -> int | float: spells: list[Spell] = [ Spell(53, Instant({'boss_hp': -4})), Spell(73, Instant({'boss_hp': -2, 'player_hp': 2})), Spell(113, Effect('shield_timer', 6)), Spell(173, Effect('poison_timer', 6)), Spell(229, Effect('recharge_timer', 6)), ] seen: set[tuple] = set() def simulate_effects(state: State): if state.shield_timer > 0: state.shield_timer -= 1 state.player_shield = 7 else: state.player_shield = 0 if state.poison_timer > 0: state.poison_timer -= 1 state.boss_hp -= 3 if state.recharge_timer > 0: state.recharge_timer -= 1 state.player_mana += 101 def player_turn(state: State) -> int | float: t: tuple = astuple(state) if t in seen: return float('inf') seen.add(t) if hard_mode: state.player_hp -= 1 if state.player_hp <= 0: return float('inf') simulate_effects(state) if state.boss_hp <= 0: return 0 min_mana: int | float = float('inf') cost: int action: Effect | Instant for spell in spells: if state.player_mana < spell.cost: continue nstate = replace(state) nstate.player_mana -= spell.cost if isinstance(spell.action, Effect): if getattr(nstate, spell.action.timer) != 0: continue setattr(nstate, spell.action.timer, spell.action.start) else: for attr, delta in spell.action.effects.items(): setattr(nstate, attr, getattr(nstate, attr) + delta) min_mana = min(min_mana, spell.cost + boss_turn(nstate)) return min_mana def boss_turn(state: State) -> int | float: simulate_effects(state) if state.boss_hp <= 0: return 0 state.player_hp -= max(1, state.boss_damage - state.player_shield) if state.player_hp <= 0: return float('inf') return player_turn(state) return player_turn(State(boss_hp=boss_hp, boss_damage=boss_damage)) def part1(boss_hp: int, boss_damage: int) -> int | float: return solve(boss_hp, boss_damage, False) def part2(boss_hp: int, boss_damage: int) -> int | float: return solve(boss_hp, boss_damage, True) if __name__ == '__main__': boss_hp: int = 51 boss_damage: int = 9 print(part1(boss_hp, boss_damage)) print(part2(boss_hp, boss_damage))