Compare commits
4 Commits
b0a5826b3f
...
89ee8ffd51
Author | SHA1 | Date |
---|---|---|
dave | 89ee8ffd51 | |
dave | 8bb9e32344 | |
dave | ef01a6e885 | |
dave | be2ccabe7c |
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
|
@ -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()
|
|
@ -0,0 +1,2 @@
|
|||
depth: 8112
|
||||
target: 13,743
|
|
@ -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()
|
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
Loading…
Reference in New Issue