Merge branch 'master' of github.com:Birdulon/AdventOfCode
This commit is contained in:
commit
bc58c8e09e
|
@ -0,0 +1,3 @@
|
||||||
|
Hit Points: 109
|
||||||
|
Damage: 8
|
||||||
|
Armor: 2
|
|
@ -0,0 +1,69 @@
|
||||||
|
with open('day21-input', 'r') as file:
|
||||||
|
data = [l.strip('\n') for l in file]
|
||||||
|
boss_hp = int(data[0].split()[-1])
|
||||||
|
boss_damage = int(data[1].split()[-1])
|
||||||
|
boss_armor = int(data[2].split()[-1])
|
||||||
|
|
||||||
|
player_hp = 100
|
||||||
|
player_damage = 0
|
||||||
|
player_armor = 0
|
||||||
|
|
||||||
|
def attack_damage(damage, armor):
|
||||||
|
return max(damage - armor, 1)
|
||||||
|
|
||||||
|
from math import ceil
|
||||||
|
def time_to_kill(damage, armor, hp):
|
||||||
|
return ceil(hp/attack_damage(damage, armor))
|
||||||
|
|
||||||
|
s_shop_w = """
|
||||||
|
Weapons: Cost Damage Armor
|
||||||
|
Dagger 8 4 0
|
||||||
|
Shortsword 10 5 0
|
||||||
|
Warhammer 25 6 0
|
||||||
|
Longsword 40 7 0
|
||||||
|
Greataxe 74 8 0
|
||||||
|
""".split('\n')[2:-1]
|
||||||
|
|
||||||
|
s_shop_a = """
|
||||||
|
Armor: Cost Damage Armor
|
||||||
|
Leather 13 0 1
|
||||||
|
Chainmail 31 0 2
|
||||||
|
Splintmail 53 0 3
|
||||||
|
Bandedmail 75 0 4
|
||||||
|
Platemail 102 0 5
|
||||||
|
""".split('\n')[2:-1]
|
||||||
|
|
||||||
|
s_shop_r = """
|
||||||
|
Rings: Cost Damage Armor
|
||||||
|
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
|
||||||
|
""".split('\n')[2:-1]
|
||||||
|
|
||||||
|
weapons = [[int(i) for i in w.split()[-3:]] for w in s_shop_w]
|
||||||
|
armors = [[int(i) for i in a.split()[-3:]] for a in s_shop_a] + [[0,0,0]]
|
||||||
|
rings = [[int(i) for i in r.split()[-3:]] for r in s_shop_r] + [[0,0,0], [0,0,0]]
|
||||||
|
|
||||||
|
def strategy_works(items):
|
||||||
|
# cost = sum([i[0] for i in items])
|
||||||
|
damage = sum([i[1] for i in items])
|
||||||
|
armor = sum([i[2] for i in items])
|
||||||
|
return time_to_kill(damage, boss_armor, boss_hp) <= time_to_kill(boss_damage, armor, player_hp)
|
||||||
|
|
||||||
|
from itertools import combinations
|
||||||
|
good_strategies = []
|
||||||
|
bad_strategies = []
|
||||||
|
for w in weapons:
|
||||||
|
for a in armors:
|
||||||
|
for r1, r2 in combinations(rings, 2):
|
||||||
|
strat = (w, a, r1, r2)
|
||||||
|
if strategy_works(strat):
|
||||||
|
good_strategies.append(strat)
|
||||||
|
else:
|
||||||
|
bad_strategies.append(strat)
|
||||||
|
|
||||||
|
print(min([sum([i[0] for i in items]) for items in good_strategies])) # Part 1
|
||||||
|
print(max([sum([i[0] for i in items]) for items in bad_strategies])) # Part 2
|
|
@ -0,0 +1,2 @@
|
||||||
|
Hit Points: 58
|
||||||
|
Damage: 9
|
|
@ -0,0 +1,139 @@
|
||||||
|
with open('day22-input', 'r') as file:
|
||||||
|
data = [l.strip('\n') for l in file]
|
||||||
|
boss_hp = int(data[0].split()[-1])
|
||||||
|
boss_damage = int(data[1].split()[-1])
|
||||||
|
player_hp = 50
|
||||||
|
player_mp = 500
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
State = namedtuple('State', ['player_hp', 'player_mp', 'boss_hp', 'shield', 'poison', 'recharge', 'mp_spent'])
|
||||||
|
|
||||||
|
initial_state = State(player_hp, player_mp, boss_hp, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
def magic_missile(state):
|
||||||
|
if state.player_mp < 53:
|
||||||
|
raise ValueError('Not enough mana')
|
||||||
|
return State(state.player_hp, state.player_mp-53, state.boss_hp-4,
|
||||||
|
state.shield, state.poison, state.recharge, state.mp_spent+53)
|
||||||
|
|
||||||
|
def drain(state):
|
||||||
|
if state.player_mp < 73:
|
||||||
|
raise ValueError('Not enough mana')
|
||||||
|
return State(state.player_hp+2, state.player_mp-73, state.boss_hp-2,
|
||||||
|
state.shield, state.poison, state.recharge, state.mp_spent+73)
|
||||||
|
|
||||||
|
def poison(state):
|
||||||
|
if state.player_mp < 173:
|
||||||
|
raise ValueError('Not enough mana')
|
||||||
|
if state.poison > 0:
|
||||||
|
raise ValueError('Already poisoned')
|
||||||
|
return State(state.player_hp, state.player_mp-173, state.boss_hp,
|
||||||
|
state.shield, state.poison+6, state.recharge, state.mp_spent+173)
|
||||||
|
|
||||||
|
def shield(state):
|
||||||
|
if state.player_mp < 113:
|
||||||
|
raise ValueError('Not enough mana')
|
||||||
|
if state.shield > 0:
|
||||||
|
raise ValueError('Already shielded')
|
||||||
|
return State(state.player_hp, state.player_mp-113, state.boss_hp,
|
||||||
|
state.shield+6, state.poison, state.recharge, state.mp_spent+113)
|
||||||
|
|
||||||
|
def recharge(state):
|
||||||
|
if state.player_mp < 229:
|
||||||
|
raise ValueError('Not enough mana')
|
||||||
|
if state.recharge > 0:
|
||||||
|
raise ValueError('Already recharging')
|
||||||
|
return State(state.player_hp, state.player_mp-229, state.boss_hp,
|
||||||
|
state.shield, state.poison, state.recharge+5, state.mp_spent+229)
|
||||||
|
|
||||||
|
def turn_start(state, hardmode_player_turn=False):
|
||||||
|
s = state._asdict()
|
||||||
|
if hardmode_player_turn:
|
||||||
|
s['player_hp'] -= 1
|
||||||
|
if s['player_hp'] <= 0:
|
||||||
|
return State(**s)
|
||||||
|
if s['recharge'] > 0:
|
||||||
|
s['player_mp'] += 101
|
||||||
|
s['recharge'] -= 1
|
||||||
|
if s['poison'] > 0:
|
||||||
|
s['boss_hp'] -= 3
|
||||||
|
s['poison'] -= 1
|
||||||
|
if s['shield'] > 0:
|
||||||
|
s['shield'] -= 1
|
||||||
|
return State(**s)
|
||||||
|
|
||||||
|
def boss_action(state):
|
||||||
|
damage = boss_damage
|
||||||
|
if state.shield > 0:
|
||||||
|
damage = max(damage-7, 1)
|
||||||
|
return State(state.player_hp-damage, state.player_mp, state.boss_hp,
|
||||||
|
state.shield, state.poison, state.recharge, state.mp_spent)
|
||||||
|
|
||||||
|
spells = [magic_missile, drain, shield, poison, recharge]
|
||||||
|
mana_reqs = [53, 73, 113, 173, 229]
|
||||||
|
def execute_round(state, command, hardmode=False):
|
||||||
|
state = turn_start(state, hardmode)
|
||||||
|
if state.player_hp <= 0:
|
||||||
|
return state
|
||||||
|
state = spells[command](state)
|
||||||
|
state = turn_start(state)
|
||||||
|
if state.boss_hp > 0:
|
||||||
|
state = boss_action(state)
|
||||||
|
return state
|
||||||
|
|
||||||
|
maximum_mana_usage = 100000 # Arbitrarily large
|
||||||
|
winning_commands = []
|
||||||
|
def tree_simulation(state, commands, hardmode=False):
|
||||||
|
global maximum_mana_usage, winning_commands
|
||||||
|
|
||||||
|
state = execute_round(state, commands[-1], hardmode)
|
||||||
|
if state.player_hp <= 0 or state.mp_spent > maximum_mana_usage:
|
||||||
|
return 100000
|
||||||
|
if state.boss_hp <= 0:
|
||||||
|
maximum_mana_usage = state.mp_spent
|
||||||
|
winning_commands = commands
|
||||||
|
return state.mp_spent
|
||||||
|
|
||||||
|
mp_spent = 100000
|
||||||
|
for cmd, mana in enumerate(mana_reqs):
|
||||||
|
if state.player_mp > mana:
|
||||||
|
try:
|
||||||
|
mp_spent = min(tree_simulation(state, commands+[cmd], hardmode), mp_spent)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return mp_spent
|
||||||
|
|
||||||
|
mana = min([tree_simulation(initial_state, [c]) for c in range(len(spells))])
|
||||||
|
print(mana) # Part 1
|
||||||
|
part1_commands = winning_commands
|
||||||
|
|
||||||
|
maximum_mana_usage = 100000
|
||||||
|
mana_hard = min([tree_simulation(initial_state, [c], hardmode=True) for c in range(len(spells))])
|
||||||
|
print(mana_hard) # Part 2
|
||||||
|
part2_commands = winning_commands
|
||||||
|
|
||||||
|
# Bonus: show the winning simulations
|
||||||
|
spell_names = ['Magic Missile', 'Drain', 'Shield', 'Poison', 'Recharge']
|
||||||
|
|
||||||
|
def perform_simulation(command_list, maximum_mana_usage=None, hardmode=False):
|
||||||
|
state = initial_state
|
||||||
|
for i, command in enumerate(command_list):
|
||||||
|
print(state)
|
||||||
|
print(f'Player casts {spell_names[command]}!')
|
||||||
|
try:
|
||||||
|
state = execute_round(state, command, hardmode)
|
||||||
|
except ValueError:
|
||||||
|
return f'Failed at round #{i+1}: Insufficient mana'
|
||||||
|
if maximum_mana_usage and state.mp_spent >= maximum_mana_usage:
|
||||||
|
return f'Failed at round #{i+1}: Exceeded mana budget'
|
||||||
|
if state.player_hp <= 0:
|
||||||
|
return f'Failed at round #{i+1}: Player died'
|
||||||
|
if state.boss_hp <= 0:
|
||||||
|
return f'Succeeded at round #{i+1}', state.mp_spent
|
||||||
|
return f'Ran out of commands but game is not over', state
|
||||||
|
|
||||||
|
print('Ideal normal mode fight:')
|
||||||
|
print(perform_simulation(part1_commands))
|
||||||
|
print('')
|
||||||
|
print('Ideal hard mode fight:')
|
||||||
|
print(perform_simulation(part2_commands, hardmode=True))
|
Loading…
Reference in New Issue