#!/usr/bin/python from copy import copy from random import randrange class Entity: pass def solve_partn(partn, player_stats, boss_stats): # We assign numbers 0-4 to the spells mana_costs = [53, 73, 113, 173, 229] least_mana = 666666 # 100K attempts turns out to be enough to reliably find the solution for i in range(100000): boss = copy( boss_stats) player = copy(player_stats) spent_mana = 0 # Timers for "Shield", "Poison" and "Recharge", respectively timers = [0, 0, 0] turn = 0 while boss.health > 0 and player.health > 0: # Already spent too much; this can't be the solution, abort if spent_mana > least_mana: break # Part 2: hard mode: player loses 1 HP at start of their turn if partn == 2 and turn % 2 == 0: player.health -= 1 if player.health <= 0: break # Apply status effects from past spells # "Shield" if timers[0] > 0: timers[0] -= 1 if timers[0] == 0: player.armour -= 7 # "Poison" if timers[1] > 0: boss.health -= 3 # Did the boss die before any actions were taken? if boss.health <= 0: break timers[1] -= 1 # "Recharge" if timers[2] > 0: player.mana += 101 timers[2] -= 1 # Player's turn to cast a spell if turn % 2 == 0: # Can we afford to cast any spell? If not, we've lost; abort if player.mana < min(mana_costs): break # Randomly select which spell to cast. Sounds inefficient, but # works fine: we want to minimize the total mana spent, so we're # looking for short sequences, which we're very unlikely to miss. s = -1 while s == -1: s = randrange(0, 5) # Can we afford this spell? if mana_costs[s] > player.mana: s = -1 # Isn't this spell already active? if s in [2, 3, 4] and timers[s - 2] > 0: s = -1 player.mana -= mana_costs[s] spent_mana += mana_costs[s] # Apply effect of the spell we've selected # "Magic Missile" if s == 0: boss.health -= 4 # "Drain" if s == 1: boss.health -= 2 player.health += 2 # "Shield" if s == 2: timers[0] = 6 player.armour += 7 # "Poison" if s == 3: timers[1] = 6 # "Recharge" if s == 4: timers[2] = 5 # Boss' turn to attack else: # turn % 2 == 1 player.health -= max(1, boss.damage - player.armour) turn += 1 # Did we stop because the player won? if boss.health <= 0: if spent_mana < least_mana: least_mana = spent_mana return least_mana def main(): # Set up player's stats given in puzzle text player = Entity() player.health = 50 player.armour = 0 player.mana = 500 # Read boss' stats from input text file boss = Entity() with open("input.txt", "r") as f: lines = f.read().splitlines() for i in range(len(lines)): # len(lines) = 2 words = lines[i].split() if i == 0: boss.health = int(words[-1]) if i == 1: boss.damage = int(words[-1]) print("Part 1 solution:", solve_partn(1, player, boss)) # 1269 for me print("Part 2 solution:", solve_partn(2, player, boss)) # 1309 for me if __name__ == "__main__": main()