My Advent of Code solutions in Python.
kevinyap.ca/2019/12/going-fast-in-advent-of-code/
advent-of-code
python
1import fileinput
2import itertools
3import sys
4from collections import namedtuple
5
6class Character:
7 def __init__(self, name, hp, mp, dmg, armour):
8 # Stats
9 self.name = name
10 self.hp = hp
11 self.mp = mp
12 self.dmg = dmg
13 self.armour = armour
14
15 # Status Effects
16 self.shielded = 0
17 self.poisoned = 0
18 self.recharging = 0
19
20 def print_player(self):
21 print '{} has {} hit points, {} mana, {} armour'.format(self.name, self.hp, self.mp, self.armour)
22
23 def print_boss(self):
24 print '{} has {} hit points'.format(self.name, self.hp)
25
26 def process_status(self):
27 if self.shielded:
28 self.shielded -= 1
29 self.armour = 7
30 # print "Shield's timer is now {}.".format(self.shielded)
31 if self.shielded == 0:
32 self.armour = 0
33 # print 'Shield wears off.'
34
35 if self.poisoned:
36 self.poisoned -= 1
37 self.hp -= 3
38 # print 'Poison deals 3 damage; its timer is now {}.'.format(self.poisoned)
39 if self.poisoned == 0:
40 pass
41 # print 'Poison effects expired.'
42
43 if self.recharging:
44 self.recharging -= 1
45 self.mp += 101
46 # print 'Recharge provides 101 mana; its timer is now {}.'.format(self.recharging)
47 if self.recharging == 0:
48 pass
49 # print 'Recharge wears off.'
50
51
52Spell = namedtuple('Spell', ['name', 'cost', 'dmg', 'heal', 'effect', 'duration'])
53
54# Magic Missile costs 53 mana. It instantly does 4 damage.
55# Drain costs 73 mana. It instantly does 2 damage and heals you for 2 hit points.
56# Shield costs 113 mana. It starts an effect that lasts for 6 turns. While it is active, your armor is increased by 7.
57# Poison costs 173 mana. It starts an effect that lasts for 6 turns. At the start of each turn while it is active, it deals the boss 3 damage.
58# Recharge costs 229 mana. It starts an effect that lasts for 5 turns. At the start of each turn while it is active, it gives you 101 new mana.
59SPELLS = [
60 Spell('Missl', 53, 4, 0, None, None),
61 Spell('Drain', 73, 2, 2, None, None),
62 Spell('Shild', 113, 0, 0, 'shielded', 6),
63 Spell('Poisn', 173, 0, 0, 'poisoned', 6),
64 Spell('Rchrg', 229, 0, 0, 'recharge', 5),
65]
66
67
68def boss_fight(player, boss, spell_order, hard_mode=False):
69 spent_mana = 0
70 effects = []
71 # print ', '.join(s.name for s in spell_order)
72
73 for spell in spell_order:
74 if hard_mode:
75 player.hp -= 1
76 if player.hp <= 0:
77 # print 'Player died. Game over. :('
78 return sys.maxint
79
80 # print '-- Player Turn --'
81 # player.print_player()
82 # boss.print_boss()
83
84 player.process_status()
85 boss.process_status()
86
87
88 # Pick a spell to use
89 if spell.cost > player.mp:
90 # print 'Not enough mana to cast.'
91 return sys.maxint
92
93 # Disallow repeated effects
94 if spell.effect == 'shielded' and player.shielded:
95 return sys.maxint
96 elif spell.effect == 'poisoned' and boss.poisoned:
97 return sys.maxint
98 elif spell.effect == 'recharge' and player.recharging:
99 return sys.maxint
100
101 # print '> iKevinY casted {}!'.format(spell.name)
102 spent_mana += spell.cost
103 player.mp -= spell.cost
104 boss.hp -= spell.dmg
105
106
107 if spell.heal:
108 # print 'iKevinY healed for {} hit points.'.format(spell.heal)
109 player.hp += spell.heal
110
111 if spell.effect == 'shielded':
112 player.shielded = spell.duration
113 elif spell.effect == 'poisoned':
114 boss.poisoned = spell.duration
115 elif spell.effect == 'recharge':
116 player.recharging = spell.duration
117
118 if boss.hp <= 0:
119 # print 'Boss died. You win! :)'
120 print spent_mana, '@', ' '.join(s.name for s in spell_order)
121 return spent_mana
122
123 # print '-- Boss Turn --'
124 # player.print_player()
125 # boss.print_boss()
126 player.process_status()
127 boss.process_status()
128
129 if boss.hp <= 0:
130 # print 'Boss died. You win! :)'
131 print spent_mana, '@', ' '.join(s.name for s in spell_order)
132 return spent_mana
133
134 # print '> Boss attacks for {} damage!'.format(max(1, boss.dmg - player.armour))
135 player.hp -= max(1, boss.dmg - player.armour)
136 if player.hp <= 0:
137 # print 'Player died. Game over. :('
138 return sys.maxint
139
140 # print
141
142 else:
143 # Fight did not terminate
144 return sys.maxint
145
146 return spent_mana
147
148
149# # HP, Damage
150# boss_data = []
151
152# for line in fileinput.input():
153# boss_data.append(int(line.split(': ')[1]))
154
155# boss = Character('BOSS', 13, 0, 8, 0)
156# player = Character('iKevinY', 10, 250, 0, 0) # 50HP, 500MP
157# boss_fight(player, boss, [SPELLS[3], SPELLS[0]])
158
159# sys.exit()
160
161def simulate(hard_mode):
162 most_efficient = sys.maxint
163
164 for n in range(9, 12):
165 found = False
166 for so in itertools.product(SPELLS, repeat=n):
167 if sum(s.cost for s in so) > most_efficient:
168 continue
169
170 boss = Character('BOSS', 55, 0, 8, 0) # 55hp, 8dmg INPUT
171 player = Character('iKevinY', 50, 500, 0, 0) # 50HP, 500MP
172
173 # boss = Character('BOSS', 13, 0, 8, 0)
174 # player = Character('iKevinY', 10, 250, 0, 0) # 50HP, 500MP
175 a = boss_fight(player, boss, so, hard_mode)
176
177 if a < most_efficient:
178 most_efficient = a
179 found = True
180
181 if found:
182 return most_efficient
183
184print 'Most efficient MP on easy mode: %i' % simulate(hard_mode=False)
185print 'Most efficient MP on hard mode: %i' % simulate(hard_mode=True)