summaryrefslogtreecommitdiff
path: root/21/main.py
diff options
context:
space:
mode:
authorPrefetch2022-12-31 22:21:39 +0100
committerPrefetch2022-12-31 22:21:39 +0100
commit68615a9ad2c942254135cffb00cf25a84a3b1356 (patch)
tree1ed3131f673207b2ef0bdaee3ee98bb68d6640ca /21/main.py
Initial commit
Diffstat (limited to '21/main.py')
-rwxr-xr-x21/main.py155
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()