Compare commits

...

4 Commits

Author SHA1 Message Date
dave 89ee8ffd51 day 24 2018-12-24 20:55:45 -05:00
dave 8bb9e32344 day 23 2018-12-24 20:55:33 -05:00
dave ef01a6e885 day 22 2018-12-24 20:54:00 -05:00
dave be2ccabe7c day 21 2018-12-24 20:52:29 -05:00
11 changed files with 1874 additions and 0 deletions

69
21/a.py Executable file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
#ip 1
0 seti 123 0 4 set reg 4 to 123 a = 123
1 bani 4 456 4 set reg 4 to (register 4) & 456 a = a & 456
2 eqri 4 72 4 set reg 4 to 1 if reg 4 == 72 a = 1 if a == 72 else 0
3 addr 4 1 1 set reg 1 (the IP) to (IP) plus reg 4 IP = IP + a (JUMP to 5 if the above check passes)
4 seti 0 0 1 set IP to 0 restart program if the above check failed
5 seti 0 8 4 set reg 4 = 0 a = 0 <--- reentry point from 27
6 bori 4 65536 3 set reg 3 to reg 4 & (value) b = a | 65536 <--- reentry point from 30
7 seti 16098955 8 4 set reg 4 to (value) a = 16098955
8 bani 3 255 5 set reg 5 to to reg 3 & (value) c = b & 255
9 addr 4 5 4 set reg 4 to (reg 4 plus reg 5) a = a + c
10 bani 4 16777215 4 set reg 4 to (reg 4 & [value]) a = a & 16777215
11 muli 4 65899 4 set reg 4 to (reg 4 * [value]) a = a * 65899
12 bani 4 16777215 4 set reg 4 to (reg 4 & [value]) a = a & 16777215
13 gtir 256 3 5 set reg 5 to 1 if if [value] > reg 3 c = 1 if 256 > b
14 addr 5 1 1 set IP to IP plus reg 5 IP = IP + c (Jump to 16 of the above check passes)
15 addi 1 1 1 set IP to IP plus 1 Jump to 17 (else condition for above check failure)
16 seti 27 3 1 set IP to 27 Jump to 28 if the check in 13 passed
17 seti 0 7 5 set reg 5 = 0 c = 0
18 addi 5 1 2 set reg 2 = reg 5 + (value) d = c + 1
19 muli 2 256 2 set reg 2 = reg 2 * (value) d = d * 256
20 gtrr 2 3 2 set reg 2 = 1 if reg 2 > reg 3 d = 1 if d > b
21 addr 2 1 1 set IP to IP plus reg 2 jump to 23 if above check passed (jumps to 26)
22 addi 1 1 1 set IP = to IP + 1 jump to 24
23 seti 25 1 1 jump to 26
24 addi 5 1 5 set reg 5 to reg 5 + 1 c = c + 1
25 seti 17 6 1 jump to 18
26 setr 5 4 3 set reg 3 = reg 5 b = c
27 seti 7 5 1 jump to 8
28 eqrr 4 0 5 set reg 5 to 1 if reg 4 == reg 0 if a == (MAGIC NUMBER)
29 addr 5 1 1 set IP = IP + reg 5 End program if above check passed
30 seti 5 3 1 Jump to 6 if the check in 28 failed
"""
def main():
while True:
a = 123 & 456
if a == 72:
break
a = 0
while True:
b = a | 65536
a = 16098955
while True:
c = b & 255
a += c
a &= 16777215
a *= 65899
a &= 16777215
if 256 > b:
print(a)
return
b = b // 256
if __name__ == '__main__':
main()

72
21/b.py Executable file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""
#ip 1
0 seti 123 0 4 set reg 4 to 123 a = 123
1 bani 4 456 4 set reg 4 to (register 4) & 456 a = a & 456
2 eqri 4 72 4 set reg 4 to 1 if reg 4 == 72 a = 1 if a == 72 else 0
3 addr 4 1 1 set reg 1 (the IP) to (IP) plus reg 4 IP = IP + a (JUMP to 5 if the above check passes)
4 seti 0 0 1 set IP to 0 restart program if the above check failed
5 seti 0 8 4 set reg 4 = 0 a = 0 <--- reentry point from 27
6 bori 4 65536 3 set reg 3 to reg 4 & (value) b = a | 65536 <--- reentry point from 30
7 seti 16098955 8 4 set reg 4 to (value) a = 16098955
8 bani 3 255 5 set reg 5 to to reg 3 & (value) c = b & 255
9 addr 4 5 4 set reg 4 to (reg 4 plus reg 5) a = a + c
10 bani 4 16777215 4 set reg 4 to (reg 4 & [value]) a = a & 16777215
11 muli 4 65899 4 set reg 4 to (reg 4 * [value]) a = a * 65899
12 bani 4 16777215 4 set reg 4 to (reg 4 & [value]) a = a & 16777215
13 gtir 256 3 5 set reg 5 to 1 if if [value] > reg 3 c = 1 if 256 > b
14 addr 5 1 1 set IP to IP plus reg 5 IP = IP + c (Jump to 16 of the above check passes)
15 addi 1 1 1 set IP to IP plus 1 Jump to 17 (else condition for above check failure)
16 seti 27 3 1 set IP to 27 Jump to 28 if the check in 13 passed
17 seti 0 7 5 set reg 5 = 0 c = 0
18 addi 5 1 2 set reg 2 = reg 5 + (value) d = c + 1
19 muli 2 256 2 set reg 2 = reg 2 * (value) d = d * 256
20 gtrr 2 3 2 set reg 2 = 1 if reg 2 > reg 3 d = 1 if d > b
21 addr 2 1 1 set IP to IP plus reg 2 jump to 23 if above check passed (jumps to 26)
22 addi 1 1 1 set IP = to IP + 1 jump to 24
23 seti 25 1 1 jump to 26
24 addi 5 1 5 set reg 5 to reg 5 + 1 c = c + 1
25 seti 17 6 1 jump to 18
26 setr 5 4 3 set reg 3 = reg 5 b = c
27 seti 7 5 1 jump to 8
28 eqrr 4 0 5 set reg 5 to 1 if reg 4 == reg 0 if a == (MAGIC NUMBER)
29 addr 5 1 1 set IP = IP + reg 5 End program if above check passed
30 seti 5 3 1 Jump to 6 if the check in 28 failed
"""
def main():
while True:
a = 123 & 456
if a == 72:
break
a = 0
aseen = set()
while True:
b = a | 65536
a = 16098955
while True:
c = b & 255
a += c
a &= 16777215
a *= 65899
a &= 16777215
if 256 > b:
if a not in aseen:
aseen.update([a])
print(a) # every time we see a new solution, we've executed the 'most' instructions.
break
b = b // 256
if __name__ == '__main__':
main()

73
21/disassembly.txt Normal file
View File

@ -0,0 +1,73 @@
#ip 1
0 seti 123 0 4 set reg 4 to 123 a = 123
1 bani 4 456 4 set reg 4 to (register 4) & 456 a = a & 456
2 eqri 4 72 4 set reg 4 to 1 if reg 4 == 72 a = 1 if a == 72 else 0
3 addr 4 1 1 set reg 1 (the IP) to (IP) plus reg 4 IP = IP + a (JUMP to 5 if the above check passes)
4 seti 0 0 1 set IP to 0 restart program if the above check failed
5 seti 0 8 4 set reg 4 = 0 a = 0 <--- reentry point from 27
6 bori 4 65536 3 set reg 3 to reg 4 & (value) b = a | 65536 <--- reentry point from 30
7 seti 16098955 8 4 set reg 4 to (value) a = 16098955
8 bani 3 255 5 set reg 5 to to reg 3 & (value) c = b & 255
9 addr 4 5 4 set reg 4 to (reg 4 plus reg 5) a = a + c
10 bani 4 16777215 4 set reg 4 to (reg 4 & [value]) a = a & 16777215
11 muli 4 65899 4 set reg 4 to (reg 4 * [value]) a = a * 65899
12 bani 4 16777215 4 set reg 4 to (reg 4 & [value]) a = a & 16777215
13 gtir 256 3 5 set reg 5 to 1 if if [value] > reg 3 c = 1 if 256 > b
14 addr 5 1 1 set IP to IP plus reg 5 IP = IP + c (Jump to 16 of the above check passes)
15 addi 1 1 1 set IP to IP plus 1 Jump to 17 (else condition for above check failure)
16 seti 27 3 1 set IP to 27 Jump to 28 if the check in 13 passed
17 seti 0 7 5 set reg 5 = 0 c = 0
18 addi 5 1 2 set reg 2 = reg 5 + (value) d = c + 1
19 muli 2 256 2 set reg 2 = reg 2 * (value) d = d * 256
20 gtrr 2 3 2 set reg 2 = 1 if reg 2 > reg 3 d = 1 if d > b
21 addr 2 1 1 set IP to IP plus reg 2 jump to 23 if above check passed (jumps to 26)
22 addi 1 1 1 set IP = to IP + 1 jump to 24
23 seti 25 1 1 jump to 26
24 addi 5 1 5 set reg 5 to reg 5 + 1 c = c + 1
25 seti 17 6 1 jump to 18
26 setr 5 4 3 set reg 3 = reg 5 b = c
27 seti 7 5 1 jump to 8
28 eqrr 4 0 5 set reg 5 to 1 if reg 4 == reg 0 if a == (USER NUMBER)
29 addr 5 1 1 set IP = IP + reg 5 End program if above check passed
30 seti 5 3 1 Jump to 6 if the check in 28 failed
def program():
while True:
a = 123 & 456
if a == 72:
break
while True:
a = 0 # <--- reentry point from 27
while True:
b = a | 65536 # <--- reentry point from 30
a = 16098955 # 111101011010011010001011
LABEL8
c = b & 255 # 8 # 11111111
a = a + c
a = a & 16777215 # 111111111111111111111111
a = a * 65899 # 10000000101101011
a = a & 16777215 # 111111111111111111111111
if b < 256:
if a == USER_NUMBER:
return
else:
# 17-27
c = 0
while True:
d = c + 1
d = d * 256
if d > b:
b = c
else:
c = c + 1
GOTO LABEL8

32
21/input.txt Normal file
View File

@ -0,0 +1,32 @@
#ip 1
seti 123 0 4
bani 4 456 4
eqri 4 72 4
addr 4 1 1
seti 0 0 1
seti 0 8 4
bori 4 65536 3
seti 16098955 8 4
bani 3 255 5
addr 4 5 4
bani 4 16777215 4
muli 4 65899 4
bani 4 16777215 4
gtir 256 3 5
addr 5 1 1
addi 1 1 1
seti 27 3 1
seti 0 7 5
addi 5 1 2
muli 2 256 2
gtrr 2 3 2
addr 2 1 1
addi 1 1 1
seti 25 1 1
addi 5 1 5
seti 17 6 1
setr 5 4 3
seti 7 5 1
eqrr 4 0 5
addr 5 1 1
seti 5 3 1

87
22/a.py Executable file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
from enum import Enum
X = 0
Y = 1
GI = 0
EL = 1
TYPE = 2
class Tile(Enum):
ROCKY = 0
WET = 1
NARROW = 2
def main():
with open("input.txt") as f:
depth = int(f.readline().split(" ")[1])
target = tuple([int(i) for i in f.readline().split(" ")[1].split(",")])
# print(depth)
# print(target)
# depth = 510
# target = (10, 10)
target2 = (target[X] + 1, target[Y] + 1)
"""
Type is based on erosion level (EL)
EL based on geologic index (GI)
GI based on rules
00 01 03 06 10 15
02 04 07 11 16 21
05 08 12 17 22 26
09 13 18 23 27 30
14 19 24 28 31 33
20 25 29 32 34 35
"""
world = {} # mapping of (x,y) to (GI, EL)
def calcGi(coord):
nonlocal world
if coord == (0, 0):
return 0 # gi hard coded to 0
elif coord == target:
return 0 # gi hard coded to 0
elif coord[Y] == 0:
return coord[X] * 16807 # X coordinate times 16807.
elif coord[X] == 0:
return coord[Y] * 48271 # Y coordinate times 48271.
else:
# result of multiplying the erosion levels of the regions at X-1,Y and X,Y-1.
return world[(coord[X] - 1, coord[Y])][EL] * world[(coord[X], coord[Y] - 1)][EL]
# https://stackoverflow.com/a/20422854
for k in range(0, target2[X] + target2[Y] - 1):
for y in range(0, k + 1):
x = k - y
if x < target2[X] and y < target2[Y]:
gi = calcGi((x, y))
el = (gi + depth) % 20183
world[(x,y)] = (gi, el, Tile(el % 3))
# print((x,y))
# print(world)
total = 0
for y in range(0, target[Y] + 1):
for x in range(0, target[X] + 1):
total += world[(x,y)][TYPE].value
print(total)
# populate world with risk values
if __name__ == '__main__':
main()

2
22/input.txt Normal file
View File

@ -0,0 +1,2 @@
depth: 8112
target: 13,743

78
23/a.py Executable file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env python3
import re
import pdb
RE_LINE = re.compile(r'pos=<(-?[0-9]+),(-?[0-9]+),(-?[0-9]+)>, r=([0-9]+)')
class Bot(object):
def __init__(self, x, y, z, r):
self.x = x
self.y = y
self.z = z
self.r = r
def __repr__(self):
return "<Bot ({},{},{}) r={}>".format(self.x, self.y, self.z, self.r)
def getdist(bot1, x, y, z):
return abs(bot1.x - x) + abs(bot1.y - y) + abs(bot1.z - z)
def main():
bots = []
with open("input.txt") as f:
for line in f.readlines():
bot = Bot(*[int(i) for i in RE_LINE.search(line).groups()])
bots.append(bot)
xs = [bot.x for bot in bots]
ys = [bot.y for bot in bots]
zs = [bot.z for bot in bots]
dist = 1
while dist < max(xs) - min(xs):
dist *= 2
while True:
target_count = 0
best = None
best_val = None
for x in range(min(xs), max(xs) + 1, dist):
for y in range(min(ys), max(ys) + 1, dist):
for z in range(min(zs), max(zs) + 1, dist):
count = 0
for bot in bots:
calc = getdist(bot, x, y, z)
if (calc - bot.r) / dist <= 0:
count += 1
if count > target_count:
target_count = count
best_val = abs(x) + abs(y) + abs(z)
best = (x, y, z)
elif count == target_count:
if not best_val or abs(x) + abs(y) + abs(z) < best_val:
best_val = abs(x) + abs(y) + abs(z)
best = (x, y, z)
if dist == 1:
print(best_val)
return
else:
xs = [best[0] - dist, best[0] + dist]
ys = [best[1] - dist, best[1] + dist]
zs = [best[2] - dist, best[2] + dist]
dist //= 2
if __name__ == '__main__':
main()

1000
23/input.txt Normal file

File diff suppressed because it is too large Load Diff

256
24/a.py Executable file
View File

@ -0,0 +1,256 @@
#!/usr/bin/env python3
"""
The immune system and the infection each have an army made up of several groups;
Each group consists of one or more identical units.
The armies repeatedly fight until only one army has units remaining.
Units within a group all have the same hit points, attack damage, attack type, initiative, and sometimes weaknesses or immunities.
(higher initiative units attack first and win ties)
Each group also has an effective power: the number of units in that group multiplied by their attack damage.
Each fight consists of two phases: target selection and attacking.
Target selection:
In decreasing order of effective power,
in a tie, the group with the higher initiative chooses first.
The attacking group chooses to target the group in the enemy army to which it would deal the most damage
(after accounting for weaknesses and immunities,
but not accounting for whether the defending group has enough units to actually receive all of that damage).
If an attacking group is considering two defending groups to which it would deal equal damage,
it chooses to target the defending group with the largest effective power;
if there is still a tie, it chooses the defending group with the highest initiative.
If it cannot deal any defending groups damage, it does not choose a target.
Defending groups can only be chosen as a target by one attacking group.
Attacking phase:
each group deals damage to the target it selected, if any. Groups attack in decreasing order of initiative,
regardless of whether they are part of the infection or the immune system.
By default, an attacking group would deal damage equal to its effective power to the defending group.
Immune means no damage
Weak means 2x damage
The defending group only loses whole units from damage;
damage is always dealt in such a way that it kills the most units possible,
Any remaining damage to a unit that does not immediately kill it is ignored
After the fight is over, if both armies still contain units, a new fight begins;
combat only ends once one army has lost all of its units.
"""
from pprint import pprint
import pdb
import re
RE_ARMY = re.compile(r'([0-9]+) units each with ([0-9]+) hit points (\(([^\)]+)\) )?with an attack that does ([0-9]+) ([a-z]+) damage at initiative ([0-9]+)')
RE_MODIFIER = re.compile(r'(weak|immune) to ([a-z, ]+)')
class Army(object):
def __init__(self, tag, team, units, hp, weaknesses, immunes, damage, damage_type, initiative):
# Group number for debugging purposes
self.tag = tag
# Team string name
self.team = team
# Number of units
self.units = units
# Health per unit
self.hp = hp
# set() of weaknesses (string names)
self.weak = weaknesses
# set() of immunities (string names)
self.immune = immunes
# attack power
self.damage = damage
# attack type (string name)
self.damtype = damage_type
# initiative level
self.initiative = initiative
@property
def effpower(self):
return self.units * self.damage
def __repr__(self):
mods = []
modstr = ""
if self.immune:
mods.append("immunte:{}".format('|'.join(self.immune)))
if self.weak:
mods.append("weak:{}".format('|'.join(self.weak)))
if mods:
modstr = " {}".format(' '.join(mods))
return "<Army tag:{} team:'{}' eff:{} units:{} hp:{} damage:{} damtype:{} init:{}{}>" \
.format(self.tag, self.team, self.effpower, self.units, self.hp, self.damage, self.damtype, self.initiative, modstr)
def attack(self, target, simulate=False):
"""
Perform a battle
:param target: army that self will attack
:param simulate: if true, don't modify the target army.
:return: maximum damage dealt if simulating
if not simulating, True if the target army was wiped out. False otherwise
"""
"""
Attacking phase:
each group deals damage to the target it selected, if any. Groups attack in decreasing order of initiative,
regardless of whether they are part of the infection or the immune system.
By default, an attacking group would deal damage equal to its effective power to the defending group.
Immune means no damage
Weak means 2x damage
The defending group only loses whole units from damage;
damage is always dealt in such a way that it kills the most units possible,
Any remaining damage to a unit that does not immediately kill it is ignored
After the fight is over, if both armies still contain units, a new fight begins;
combat only ends once one army has lost all of its units.
"""
# Determine weakness/immunity damage multiplier
if self.damtype in target.immune:
multiplier = 0
elif self.damtype in target.weak:
multiplier = 2
else:
multiplier = 1
damage = multiplier * self.effpower
# if multiplier == 0:
# return False, 0, 0
dead = damage // target.hp
killed = min(target.units, dead)
if simulate:
return damage
target.units -= killed
return target.units == 0, damage, killed
def parsearmies(fname):
armies = []
with open(fname) as f:
teamname = None
groupnum = 1
for line in f.readlines():
line = line.strip()
if not line:
continue
if ":" in line: # Found a new army like "Infection:"
teamname = line[0:-1]
groupnum = 1
else: # an army line to parse
units, hp, _, modifiers, atkpwr, atktype, initia = RE_ARMY.search(line).groups()
mods = {"weak": set(),
"immune": set()}
if modifiers:
for prop in RE_MODIFIER.findall(modifiers):
mods[prop[0]].update(prop[1].split(', '))
armies.append(Army(groupnum, teamname, int(units), int(hp), mods["weak"], mods["immune"],
int(atkpwr), atktype, int(initia)))
groupnum +=1
return armies
def assignbattles(armies):
"""
Target selection:
In decreasing order of effective power,
in a tie, the group with the higher initiative chooses first.
The attacking group chooses to target the group in the enemy army to which it would deal the most damage
(after accounting for weaknesses and immunities,
but not accounting for whether the defending group has enough units to actually receive all of that damage).
If an attacking group is considering two defending groups to which it would deal equal damage,
it chooses to target the defending group with the largest effective power;
if there is still a tie, it chooses the defending group with the highest initiative.
If it cannot deal any defending groups damage, it does not choose a target.
Defending groups can only be chosen as a target by one attacking group.
"""
battles = [] # list of tuples (attacker, defender)
# armies in this list need to be assigned a target
assign = sorted(armies,
key=lambda a: a.effpower * 100000000 + a.initiative, # strongest armies pick a target first
reverse=True)
# print("Assign order")
# for ass in assign:
# print("- {} group {} (eff {})".format(ass.team, ass.tag, ass.effpower))
# print()
targets = set(assign) # these armies can still be attacked
while assign:
army = assign.pop(0) # we're finding a target for this army
best = None # best target
best_dmg = -1 # damage inflicted to best target
best_eff = -1
best_init = -1
considered = False
for target in targets:
if army.team == target.team or army == target: # don't attack same team
continue
considered = True
dmg = army.attack(target, simulate=True)
# print("{} {} -> {} {} damage: {} (target eff={}, init={})".format(army.team, army.tag, target.team, target.tag, dmg, target.effpower, target.initiative))
if (dmg > best_dmg) or \
(dmg == best_dmg and target.effpower > best_eff) or \
(dmg == best_dmg and target.effpower == best_eff and target.initiative > best_init):
best = target
best_dmg = dmg
best_init = target.initiative
best_eff = target.effpower
if not considered: # no targets available
continue
if best_dmg == 0:
continue
targets.remove(best)
battles.append((army, best))
# print("\nBest target for ({} group {} (effpwr {}) :: dmg={})\n\t{} is \n\t{}\n\n".format(army.team, army.tag, army.effpower, best_dmg, army, best))
return sorted(battles, key=lambda b: b[0].initiative, reverse=True)
def main():
armies = parsearmies("input.txt")
while True: # each loop is one round of fighting
# print("\n========== Round ==========")
battles = assignbattles(armies)
if not battles: # one side has been wiped out
break
# print()
for attacker, defender in battles:
if attacker.units > 0:
wiped, dmg, killed = attacker.attack(defender)
# print("{} group {} (i{}) attacks {} group {}, dealing {}, killing {}".format(attacker.team, attacker.tag, attacker.initiative, defender.team, defender.tag, dmg, killed))
if wiped:
# print("{} group {} is wiped out! ({})".format(defender.team, defender.tag, defender.units))
armies.remove(defender)
if len(battles) == 1:
break
# pdb.set_trace()
# input()
# print()
# print()
# pprint(armies)
print(sum([i.units for i in armies]))
if __name__ == '__main__':
main()

182
24/b.py Executable file
View File

@ -0,0 +1,182 @@
#!/usr/bin/env python3
from sys import exit
import re
RE_ARMY = re.compile(r'([0-9]+) units each with ([0-9]+) hit points (\(([^\)]+)\) )?with an attack that does ([0-9]+) ([a-z]+) damage at initiative ([0-9]+)')
RE_MODIFIER = re.compile(r'(weak|immune) to ([a-z, ]+)')
class Army(object):
def __init__(self, tag, team, units, hp, weaknesses, immunes, damage, damage_type, initiative):
# Group number for debugging purposes
self.tag = tag
# Team string name
self.team = team
# Number of units
self.units = units
# Health per unit
self.hp = hp
# set() of weaknesses (string names)
self.weak = weaknesses
# set() of immunities (string names)
self.immune = immunes
# attack power
self.damage = damage
# attack type (string name)
self.damtype = damage_type
# initiative level
self.initiative = initiative
@property
def effpower(self):
return self.units * self.damage
def __repr__(self):
mods = []
modstr = ""
if self.immune:
mods.append("immunte:{}".format('|'.join(self.immune)))
if self.weak:
mods.append("weak:{}".format('|'.join(self.weak)))
if mods:
modstr = " {}".format(' '.join(mods))
return "<Army tag:{} team:'{}' eff:{} units:{} hp:{} damage:{} damtype:{} init:{}{}>" \
.format(self.tag, self.team, self.effpower, self.units, self.hp, self.damage, self.damtype, self.initiative, modstr)
def attack(self, target, simulate=False):
"""
Perform a battle
:param target: army that self will attack
:param simulate: if true, don't modify the target army.
:return: maximum damage dealt if simulating
if not simulating, True if the target army was wiped out. False otherwise
"""
if self.damtype in target.immune:
multiplier = 0
elif self.damtype in target.weak:
multiplier = 2
else:
multiplier = 1
damage = multiplier * self.effpower
dead = damage // target.hp
killed = min(target.units, dead)
if simulate:
return damage
target.units -= killed
return target.units == 0, damage, killed
def parsearmies(fname):
armies = []
with open(fname) as f:
teamname = None
groupnum = 1
for line in f.readlines():
line = line.strip()
if not line:
continue
if ":" in line: # Found a new army like "Infection:"
teamname = line[0:-1]
groupnum = 1
else: # an army line to parse
units, hp, _, modifiers, atkpwr, atktype, initia = RE_ARMY.search(line).groups()
mods = {"weak": set(),
"immune": set()}
if modifiers:
for prop in RE_MODIFIER.findall(modifiers):
mods[prop[0]].update(prop[1].split(', '))
armies.append(Army(groupnum, teamname, int(units), int(hp), mods["weak"], mods["immune"],
int(atkpwr), atktype, int(initia)))
groupnum +=1
return armies
def assignbattles(armies):
battles = [] # list of tuples (attacker, defender)
# armies in this list need to be assigned a target
assign = sorted(armies,
key=lambda a: a.effpower * 100000000 + a.initiative, # strongest armies pick a target first
reverse=True)
targets = set(assign) # these armies can still be attacked
while assign:
army = assign.pop(0) # we're finding a target for this army
best = None # best target
best_dmg = -1 # damage inflicted to best target
best_eff = -1
best_init = -1
considered = False
for target in targets:
if army.team == target.team or army == target: # don't attack same team
continue
considered = True
dmg = army.attack(target, simulate=True)
if (dmg > best_dmg) or \
(dmg == best_dmg and target.effpower > best_eff) or \
(dmg == best_dmg and target.effpower == best_eff and target.initiative > best_init):
best = target
best_dmg = dmg
best_init = target.initiative
best_eff = target.effpower
if not considered: # no targets available
continue
if best_dmg == 0:
continue
targets.remove(best)
battles.append((army, best))
return sorted(battles, key=lambda b: b[0].initiative, reverse=True)
def runsim(armies):
while True: # each loop is one round of fighting
battles = assignbattles(armies)
if not battles: # one side has been wiped out
break
had_casualties = False
for attacker, defender in battles:
if attacker.units > 0:
wiped, dmg, killed = attacker.attack(defender)
had_casualties = had_casualties or killed > 0
if wiped:
armies.remove(defender)
if len(battles) == 1:
break
if not had_casualties:
return
if armies[0].team == "Immune System":
print(sum([i.units for i in armies]))
exit(0)
def main():
boost = 80 # semi-arbitrary starting point to save time. Tweak if it doesn't fit your input
while True:
armies = parsearmies("input.txt")
for unit in armies:
if unit.team == "Immune System":
unit.damage += boost
print(boost)
runsim(armies)
boost += 1
if __name__ == '__main__':
main()

23
24/input.txt Normal file
View File

@ -0,0 +1,23 @@
Immune System:
1514 units each with 8968 hit points (weak to cold) with an attack that does 57 bludgeoning damage at initiative 9
2721 units each with 6691 hit points (weak to cold) with an attack that does 22 slashing damage at initiative 15
1214 units each with 10379 hit points (immune to bludgeoning) with an attack that does 69 fire damage at initiative 16
2870 units each with 4212 hit points with an attack that does 11 radiation damage at initiative 12
1239 units each with 5405 hit points (weak to cold) with an attack that does 37 cold damage at initiative 18
4509 units each with 4004 hit points (weak to cold; immune to radiation) with an attack that does 8 slashing damage at initiative 20
3369 units each with 10672 hit points (weak to slashing) with an attack that does 29 cold damage at initiative 11
2890 units each with 11418 hit points (weak to fire; immune to bludgeoning) with an attack that does 30 cold damage at initiative 8
149 units each with 7022 hit points (weak to slashing) with an attack that does 393 radiation damage at initiative 13
2080 units each with 5786 hit points (weak to fire; immune to slashing, bludgeoning) with an attack that does 20 fire damage at initiative 7
Infection:
817 units each with 47082 hit points (immune to slashing, radiation, bludgeoning) with an attack that does 115 cold damage at initiative 3
4183 units each with 35892 hit points with an attack that does 16 bludgeoning damage at initiative 1
7006 units each with 11084 hit points with an attack that does 2 fire damage at initiative 2
4804 units each with 25411 hit points with an attack that does 10 cold damage at initiative 14
6262 units each with 28952 hit points (weak to fire) with an attack that does 7 slashing damage at initiative 10
628 units each with 32906 hit points (weak to slashing) with an attack that does 99 radiation damage at initiative 4
5239 units each with 46047 hit points (immune to fire) with an attack that does 14 bludgeoning damage at initiative 6
1173 units each with 32300 hit points (weak to cold, slashing) with an attack that does 53 bludgeoning damage at initiative 19
3712 units each with 12148 hit points (immune to cold; weak to slashing) with an attack that does 5 slashing damage at initiative 17
334 units each with 43582 hit points (weak to cold, fire) with an attack that does 260 cold damage at initiative 5