1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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()
|