-
Notifications
You must be signed in to change notification settings - Fork 0
/
24.py
128 lines (97 loc) · 3.31 KB
/
24.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
from lib import *
input = read_input(2018, 24)
@dataclass
class Group:
army: int
units: int
hp: int
ap: int
at: str
init: int
weak: set[str]
immune: set[str]
def __hash__(self):
return id(self)
@staticmethod
def parse(army, line, boost=0):
units, hp, _, extra, ap, at, init = re.match(
r"^(\d+) units each with (\d+) hit points( \((.*)\))? with an attack that does (\d+) (\w+) damage at initiative (\d+)$",
line,
).groups()
weak = set()
immune = set()
for part in extra.split("; ") if extra else []:
t, _, *xs = part.split()
{"weak": weak, "immune": immune}[t].update(x.strip(",") for x in xs)
return Group(army, int(units), int(hp), int(ap) + boost, at, int(init), weak, immune)
@property
def ep(self):
return self.units * self.ap
@property
def dead(self):
return self.units <= 0
def calc_damage(self, target):
if self.at in target.immune:
return 0
mul = 2 if self.at in target.weak else 1
return self.ep * mul
def attack(self, target):
damage = self.calc_damage(target) // target.hp
target.units -= damage
return damage
immune, infect = [
[Group.parse(i, group) for group in army.splitlines()[1:]] for i, army in enumerate(input.split("\n\n"))
]
while immune and infect:
targets = {}
imm_att = set(immune)
inf_att = set(infect)
for group in sorted(immune + infect, key=lambda g: (-g.ep, -g.init)):
attackable = [inf_att, imm_att][group.army]
if not attackable:
continue
target = max(attackable, key=lambda g: (group.calc_damage(g), g.ep, g.init))
if not group.calc_damage(target):
continue
attackable.remove(target)
targets[group] = target
for group, target in sorted(targets.items(), key=lambda a: -a[0].init):
if group.dead:
continue
group.attack(target)
immune, infect = [[g for g in x if not g.dead] for x in [immune, infect]]
print(sum(g.units for g in immune + infect))
def test(boost):
immune, infect = [
[Group.parse(i, group, boost if i == 0 else 0) for group in army.splitlines()[1:]]
for i, army in enumerate(input.split("\n\n"))
]
while immune and infect:
targets = {}
imm_att = set(immune)
inf_att = set(infect)
for group in sorted(immune + infect, key=lambda g: (-g.ep, -g.init)):
attackable = [inf_att, imm_att][group.army]
if not attackable:
continue
target = max(attackable, key=lambda g: (group.calc_damage(g), g.ep, g.init))
if not group.calc_damage(target):
continue
attackable.remove(target)
targets[group] = target
ok = False
for group, target in sorted(targets.items(), key=lambda a: -a[0].init):
if group.dead:
continue
if group.attack(target):
ok = True
if not ok:
break
immune, infect = [[g for g in x if not g.dead] for x in [immune, infect]]
if infect:
return None
return sum(g.units for g in immune)
boost = 0
while not (out := test(boost)):
boost += 1
print(out)