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
|
#!/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()
|