summaryrefslogtreecommitdiff
path: root/22
diff options
context:
space:
mode:
Diffstat (limited to '22')
-rw-r--r--22/input.txt2
-rwxr-xr-x22/main.py137
-rwxr-xr-x22/test.py39
3 files changed, 178 insertions, 0 deletions
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()