diff options
author | Prefetch | 2022-12-31 22:21:39 +0100 |
---|---|---|
committer | Prefetch | 2022-12-31 22:21:39 +0100 |
commit | 68615a9ad2c942254135cffb00cf25a84a3b1356 (patch) | |
tree | 1ed3131f673207b2ef0bdaee3ee98bb68d6640ca /21/main.py |
Initial commit
Diffstat (limited to '21/main.py')
-rwxr-xr-x | 21/main.py | 155 |
1 files changed, 155 insertions, 0 deletions
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() |