diff options
Diffstat (limited to '21')
-rw-r--r-- | 21/input.txt | 3 | ||||
-rwxr-xr-x | 21/main.py | 155 | ||||
-rwxr-xr-x | 21/test.py | 27 |
3 files changed, 185 insertions, 0 deletions
diff --git a/21/input.txt b/21/input.txt new file mode 100644 index 0000000..e749051 --- /dev/null +++ b/21/input.txt @@ -0,0 +1,3 @@ +Hit Points: 103 +Damage: 9 +Armor: 2 diff --git a/21/main.py b/21/main.py new file mode 100755 index 0000000..2c75798 --- /dev/null +++ b/21/main.py @@ -0,0 +1,155 @@ +#!/usr/bin/python + +from copy import copy + + + +class Entity: + pass + +class Item: + pass + +class Shop: + pass + + + +# Annoying boilerplate to initialize the shop with the items +# given in the text. This is as compact as I could make it. +def init_shop(): + table_weapons = [ + "Dagger 8 4 0", + "Shortsword 10 5 0", + "Warhammer 25 6 0", + "Longsword 40 7 0", + "Greataxe 74 8 0" + ] + table_armour = [ + "Leather 13 0 1", + "Chainmail 31 0 2", + "Splintmail 53 0 3", + "Bandedmail 75 0 4", + "Platemail 102 0 5", + "NONE 0 0 0" # Because it's optional + ] + table_rings = [ + "Damage+1 25 1 0", + "Damage+2 50 2 0", + "Damage+3 100 3 0", + "Defense+1 20 0 1", + "Defense+2 40 0 2", + "Defense+3 80 0 3", + "NONE 0 0 0" # Because it's optional + ] + tables = [ + (table_weapons, "weapons"), + (table_armour, "armour"), + (table_rings, "rings") + ] + + shop = Shop() + shop.weapons = [] + shop.armour = [] + shop.rings = [] + + for table, attr in tables: + for row in table: + fields = row.split() + + item = Item() + item.name = fields[0] + item.cost = int(fields[1]) + item.damage = int(fields[2]) + item.armour = int(fields[3]) + + # Sometimes I love you, Python. But usually I don't. + shop.__dict__[attr].append(item) + + return shop + + + +def will_win(player, boss): + turn = 0 + while boss.health > 0 and player.health > 0: + # Player's turn to attack + if turn % 2 == 0: + boss.health -= max(1, player.damage - boss.armour) + # Boss' turn to attack + else: # turn % 2 == 1 + player.health -= max(1, boss.damage - player.armour) + turn += 1 + + # Did the player win? + return boss.health <= 0 + + + +def solve_partn(partn, player_stats, boss_stats): + shop = init_shop() + + min_cost = 666 + max_cost = 0 + for w in shop.weapons: + for a in shop.armour: + for r1 in shop.rings: + for r2 in shop.rings: + # No more than one of each item + if r1 == r2: + continue + + boss = copy( boss_stats) + player = copy(player_stats) + + cost = w.cost + a.cost + r1.cost + r2.cost + player.damage += w.damage + player.damage += r1.damage + player.damage += r2.damage + player.armour += a.armour + player.armour += r1.armour + player.armour += r2.armour + + # Part 1: find lowest cost where player wins + if will_win(player, boss): + if cost < min_cost: + min_cost = cost + # Part 2: find highest cost where player loses + else: + if cost > max_cost: + max_cost = cost + + if partn == 1: + return min_cost + else: # partn == 2 + return max_cost + + + +def main(): + # Set up player's stats given in puzzle text + player = Entity() + player.health = 100 + player.damage = 0 + player.armour = 0 + + # 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) = 3 + words = lines[i].split() + if i == 0: + boss.health = int(words[-1]) + if i == 1: + boss.damage = int(words[-1]) + if i == 2: + boss.armour = int(words[-1]) + + print("Part 1 solution:", solve_partn(1, player, boss)) # 121 for me + print("Part 2 solution:", solve_partn(2, player, boss)) # 201 for me + + + +if __name__ == "__main__": + main() diff --git a/21/test.py b/21/test.py new file mode 100755 index 0000000..4527f49 --- /dev/null +++ b/21/test.py @@ -0,0 +1,27 @@ +#!/usr/bin/python + +import unittest + +import main + + + +class ExamplesPart1(unittest.TestCase): + def test_example1(self): + player = main.Entity() + player.health = 8 + player.damage = 0 + player.armour = 0 + + boss = main.Entity() + boss.health = 12 + boss.damage = 7 + boss.armour = 2 + + result = main.solve_partn(1, player, boss) + self.assertEqual(result, 40 + 25) + + + +if __name__ == "__main__": + unittest.main() |