From 68615a9ad2c942254135cffb00cf25a84a3b1356 Mon Sep 17 00:00:00 2001 From: Prefetch Date: Sat, 31 Dec 2022 22:21:39 +0100 Subject: Initial commit --- 22/input.txt | 2 + 22/main.py | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 22/test.py | 39 +++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 22/input.txt create mode 100755 22/main.py create mode 100755 22/test.py (limited to '22') diff --git a/22/input.txt b/22/input.txt new file mode 100644 index 0000000..7f98dd7 --- /dev/null +++ b/22/input.txt @@ -0,0 +1,2 @@ +Hit Points: 58 +Damage: 9 diff --git a/22/main.py b/22/main.py new file mode 100755 index 0000000..9b7bb0b --- /dev/null +++ b/22/main.py @@ -0,0 +1,137 @@ +#!/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() diff --git a/22/test.py b/22/test.py new file mode 100755 index 0000000..8410d32 --- /dev/null +++ b/22/test.py @@ -0,0 +1,39 @@ +#!/usr/bin/python + +import unittest + +import main + + + +class ExamplesPart1(unittest.TestCase): + def test_example1(self): + player = main.Entity() + player.health = 10 + player.armour = 0 + player.mana = 250 + + boss = main.Entity() + boss.health = 13 + boss.damage = 8 + + result = main.solve_partn(1, player, boss) + self.assertEqual(result, 173 + 53) + + def test_example2(self): + player = main.Entity() + player.health = 10 + player.armour = 0 + player.mana = 250 + + boss = main.Entity() + boss.health = 14 + boss.damage = 8 + + result = main.solve_partn(1, player, boss) + self.assertEqual(result, 229 + 113 + 73 + 173 + 53) + + + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3