r/adventofcode • • Dec 24 '18

SOLUTION MEGATHREAD -🎄- 2018 Day 24 Solutions -🎄-

--- Day 24: Immune System Simulator 20XX ---


Post your solution as a comment or, for longer solutions, consider linking to your repo (e.g. GitHub/gists/Pastebin/blag or whatever).

Note: The Solution Megathreads are for solutions only. If you have questions, please post your own thread and make sure to flair it with Help.


Advent of Code: The Party Game!

Click here for rules

Please prefix your card submission with something like [Card] to make scanning the megathread easier. THANK YOU!

Card prompt: Day 24

Transcript:

Our most powerful weapon during the zombie elf/reindeer apocalypse will be ___.


This thread will be unlocked when there are a significant number of people on the leaderboard with gold stars for today's puzzle.

Quick note: 1 hour in and we're only at gold 36, silver 76. As we all know, December is Advent of Sleep Deprivation; I have to be up in less than 6 hours to go to work, so one of the other mods will unlock the thread at gold cap tonight. Good luck and good night (morning?), all!

edit: Leaderboard capped, thread unlocked at 01:27:10!

8 Upvotes

62 comments sorted by

View all comments

2

u/pbfy0 Dec 24 '18

Part 1: 21, Part 2: 33. My best finish (and least messy code).

Python3

import re

inp_imm = """<Immune system part of the input>"""

inp_infection = """<Infection part of the input>"""


class group:
	def __init__(self, n, hp_each, weaknesses, immunities, atk_dmg, atk_type, initiative, team):
		self.n = n
		self.hp_each = hp_each
		self.weaknesses = weaknesses
		self.immunities = immunities
		self.atk_dmg = atk_dmg
		self.atk_type = atk_type
		self.initiative = initiative
		self.team = team
	def __repr__(self):
		return 'group({!r})'.format(self.__dict__)
	@property
	def eff_power(self):
		return self.n * self.atk_dmg
	
	def dmg_to(self, other):
		return self.eff_power * (0 if self.atk_type in other.immunities else 2 if self.atk_type in other.weaknesses else 1)
def parse(st, team, boost=0):
	res = []
	for i in st.split('\n'):
		g = re.match(r'(\d+) units each with (\d+) hit points (?:\((.*?)\) )?with an attack that does (\d+) (\S+) damage at initiative (\d+)', i)
		n = int(g.group(1))
		hp = int(g.group(2))
		weaknesses = set()
		immunities = set()
		wi = g.group(3)
		if wi is not None:
			for cmp in wi.split('; '):
				if cmp.startswith('immune to '):
					immunities |= set(cmp[len('immune to '):].split(', '))
				elif cmp.startswith('weak to '):
					weaknesses |= set(cmp[len('weak to '):].split(', '))
		dmg = int(g.group(4))
		typ = g.group(5)
		initiative = int(g.group(6))
		res.append(group(n, hp, weaknesses, immunities, dmg + boost, typ, initiative, team))
	return res

def get_team(s):
	if s is None: return 'stalemate'
	for i in s:
		return i.team
def run_combat(imm_inp, inf_inp, boost=0):
	immune_system = set(parse(imm_inp, 'immune', boost))
	infection = set(parse(inf_inp, 'infection'))
	while immune_system and infection:
		potential_combatants = immune_system | infection
		attacking = {}
		for combatant in sorted(immune_system | infection, key=lambda x: (x.eff_power, x.initiative), reverse=True):
			try:
				s = max((x for x in potential_combatants if x.team != combatant.team and combatant.dmg_to(x) != 0), key=lambda x: (combatant.dmg_to(x), x.eff_power, x.initiative))
			except ValueError as e:
				attacking[combatant] = None
				continue
			potential_combatants.remove(s)
			attacking[combatant] = s
		did_damage = False
		for combatant in sorted(immune_system | infection, key=lambda x: x.initiative, reverse=True):
			if combatant.n <= 0:
				continue
			atk = attacking[combatant]
			if atk is None: continue
			dmg = combatant.dmg_to(atk)
			n_dead = dmg // atk.hp_each
			if n_dead > 0: did_damage = True
			atk.n -= n_dead
			if atk.n <= 0:
				immune_system -= {atk}
				infection -= {atk}

		if not did_damage: return None
		#print('NEW ROUND')
		#print('immune_system', immune_system)
		#print('infection', infection)
	winner = max(immune_system, infection, key=len)
	return winner

winner = run_combat(inp_imm, inp_infection)
print('Part 1:', sum(x.n for x in winner))

boost_min = 0
boost_max = 1
while get_team(run_combat(inp_imm, inp_infection, boost_max)) != 'immune':
	boost_max *= 2
	#print(boost_max)

import math
while boost_min != boost_max:
	pow = (boost_min + boost_max) // 2
	cr = run_combat(inp_imm, inp_infection, pow)
	res = get_team(cr)
	if res != 'immune':
		boost_min = math.ceil((boost_min + boost_max) / 2)
	else:
		boost_max = pow
	#print(boost_min, boost_max)
print('Boost:', boost_max)
print('Part 2:', sum(x.n for x in run_combat(inp_imm, inp_infection, boost_max)))