Skip to content

Commit 5c462d1

Browse files
committed
Added day 2018-24 and 2018-25
1 parent 53e3385 commit 5c462d1

File tree

2 files changed

+346
-0
lines changed

2 files changed

+346
-0
lines changed
+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# -------------------------------- Input data ---------------------------------------- #
2+
import os, re
3+
4+
test_data = {}
5+
6+
test = 1
7+
test_data[test] = {
8+
"input": """Immune System:
9+
17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2
10+
989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3
11+
12+
Infection:
13+
801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1
14+
4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4""",
15+
"expected": ["5216", "Unknown"],
16+
}
17+
18+
19+
test = "real"
20+
input_file = os.path.join(
21+
os.path.dirname(__file__),
22+
"Inputs",
23+
os.path.basename(__file__).replace(".py", ".txt"),
24+
)
25+
test_data[test] = {
26+
"input": open(input_file, "r+").read().strip(),
27+
"expected": ["22676", "4510"],
28+
}
29+
30+
# -------------------------------- Control program execution ------------------------- #
31+
32+
case_to_test = "real"
33+
part_to_test = 2
34+
verbose = False
35+
36+
# -------------------------------- Initialize some variables ------------------------- #
37+
38+
puzzle_input = test_data[case_to_test]["input"]
39+
puzzle_expected_result = test_data[case_to_test]["expected"][part_to_test - 1]
40+
puzzle_actual_result = "Unknown"
41+
42+
43+
# -------------------------------- Actual code execution ----------------------------- #
44+
45+
46+
def choose_target(opponents, unit, ignore_targets):
47+
targets = []
48+
for opponent in opponents:
49+
# Same team
50+
if opponent[-2] == unit[-2]:
51+
continue
52+
# target is already targetted
53+
if opponent[-2:] in ignore_targets:
54+
continue
55+
56+
# Determine multipliers
57+
if unit[3] in opponent[5]:
58+
multiplier = 0
59+
elif unit[3] in opponent[6]:
60+
multiplier = 2
61+
else:
62+
multiplier = 1
63+
64+
# Order: damage, effective power, initiative
65+
target = (
66+
unit[0] * unit[2] * multiplier,
67+
opponent[0] * opponent[2],
68+
opponent[4],
69+
opponent,
70+
)
71+
targets.append(target)
72+
73+
targets.sort(reverse=True)
74+
75+
if len(targets) > 0:
76+
return targets[0]
77+
78+
79+
def determine_damage(attacker, defender):
80+
# Determine multipliers
81+
if attacker[3] in defender[5]:
82+
multiplier = 0
83+
elif attacker[3] in defender[6]:
84+
multiplier = 2
85+
else:
86+
multiplier = 1
87+
88+
return attacker[0] * attacker[2] * multiplier
89+
90+
91+
def attack_order(units):
92+
# Decreasing order of initiative
93+
units.sort(key=lambda unit: unit[4], reverse=True)
94+
return units
95+
96+
97+
def target_selection_order(units):
98+
# Decreasing order of effective power then initiative
99+
units.sort(key=lambda unit: (unit[0] * unit[2], unit[4]), reverse=True)
100+
return units
101+
102+
103+
def teams(units):
104+
teams = set([unit[-2] for unit in units])
105+
return teams
106+
107+
108+
def team_size(units):
109+
teams = {
110+
team: len([unit for unit in units if unit[-2] == team])
111+
for team in ("Immune System:", "Infection:")
112+
}
113+
return teams
114+
115+
116+
regex = "([0-9]*) units each with ([0-9]*) hit points (?:\((immune|weak) to ([a-z]*)(?:, ([a-z]*))*(?:; (immune|weak) to ([a-z]*)(?:, ([a-z]*))*)?\))? ?with an attack that does ([0-9]*) ([a-z]*) damage at initiative ([0-9]*)"
117+
units = []
118+
for string in puzzle_input.split("\n"):
119+
if string == "":
120+
continue
121+
122+
if string == "Immune System:" or string == "Infection:":
123+
team = string
124+
continue
125+
126+
matches = re.match(regex, string)
127+
if matches is None:
128+
print(string)
129+
items = matches.groups()
130+
131+
# nb_units, hitpoints, damage, damage type, initative, immune, weak, team, number
132+
unit = [
133+
int(items[0]),
134+
int(items[1]),
135+
int(items[-3]),
136+
items[-2],
137+
int(items[-1]),
138+
[],
139+
[],
140+
team,
141+
team_size(units)[team] + 1,
142+
]
143+
for item in items[2:-3]:
144+
if item is None:
145+
continue
146+
if item in ("immune", "weak"):
147+
attack_type = item
148+
else:
149+
if attack_type == "immune":
150+
unit[-4].append(item)
151+
else:
152+
unit[-3].append(item)
153+
154+
units.append(unit)
155+
156+
157+
boost = 0
158+
min_boost = 0
159+
max_boost = 10 ** 9
160+
winner = "Infection:"
161+
base_units = [unit.copy() for unit in units]
162+
while True:
163+
if part_to_test == 2:
164+
# Update boost for part 2
165+
if winner == "Infection:" or winner == "None":
166+
min_boost = boost
167+
if max_boost == 10 ** 9:
168+
boost += 20
169+
else:
170+
boost = (min_boost + max_boost) // 2
171+
else:
172+
max_boost = boost
173+
boost = (min_boost + max_boost) // 2
174+
if min_boost == max_boost - 1:
175+
break
176+
177+
units = [unit.copy() for unit in base_units]
178+
for uid in range(len(units)):
179+
if units[uid][-2] == "Immune System:":
180+
units[uid][2] += boost
181+
print("Applying boost", boost)
182+
183+
while len(teams(units)) > 1:
184+
units_killed = 0
185+
if verbose:
186+
print()
187+
print("New Round")
188+
print([(x[-2:], x[0], "units") for x in units])
189+
order = target_selection_order(units)
190+
targets = {}
191+
for unit in order:
192+
target = choose_target(units, unit, [x[3][-2:] for x in targets.values()])
193+
if target:
194+
if target[0] != 0:
195+
targets[unit[-2] + str(unit[-1])] = target
196+
197+
order = attack_order(units)
198+
for unit in order:
199+
if unit[-2] + str(unit[-1]) not in targets:
200+
continue
201+
target = targets[unit[-2] + str(unit[-1])]
202+
position = units.index(target[3])
203+
damage = determine_damage(unit, target[3])
204+
kills = determine_damage(unit, target[3]) // target[3][1]
205+
units_killed += kills
206+
target[3][0] -= kills
207+
if target[3][0] > 0:
208+
units[position] = target[3]
209+
else:
210+
del units[position]
211+
212+
if verbose:
213+
print(
214+
unit[-2:],
215+
"attacked",
216+
target[3][-2:],
217+
"dealt",
218+
damage,
219+
"damage and killed",
220+
kills,
221+
)
222+
223+
if units_killed == 0:
224+
break
225+
226+
puzzle_actual_result = sum([x[0] for x in units])
227+
if part_to_test == 1:
228+
break
229+
else:
230+
if units_killed == 0:
231+
winner = "None"
232+
else:
233+
winner = units[0][-2]
234+
print("Boost", boost, " - Winner:", winner)
235+
if verbose:
236+
print([unit[0] for unit in units])
237+
238+
239+
# -------------------------------- Outputs / results --------------------------------- #
240+
241+
print("Expected result : " + str(puzzle_expected_result))
242+
print("Actual result : " + str(puzzle_actual_result))

2018/25-Four-Dimensional Adventure.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# -------------------------------- Input data ---------------------------------------- #
2+
import os, pathfinding
3+
4+
test_data = {}
5+
6+
test = 1
7+
test_data[test] = {
8+
"input": """0,0,0,0
9+
3,0,0,0
10+
0,3,0,0
11+
0,0,3,0
12+
0,0,0,3
13+
0,0,0,6
14+
9,0,0,0
15+
12,0,0,0""",
16+
"expected": ["2", "Unknown"],
17+
}
18+
19+
test += 1
20+
test_data[test] = {
21+
"input": """-1,2,2,0
22+
0,0,2,-2
23+
0,0,0,-2
24+
-1,2,0,0
25+
-2,-2,-2,2
26+
3,0,2,-1
27+
-1,3,2,2
28+
-1,0,-1,0
29+
0,2,1,-2
30+
3,0,0,0""",
31+
"expected": ["4", "Unknown"],
32+
}
33+
34+
test = "real"
35+
input_file = os.path.join(
36+
os.path.dirname(__file__),
37+
"Inputs",
38+
os.path.basename(__file__).replace(".py", ".txt"),
39+
)
40+
test_data[test] = {
41+
"input": open(input_file, "r+").read().strip(),
42+
"expected": ["420", "Unknown"],
43+
}
44+
45+
# -------------------------------- Control program execution ------------------------- #
46+
47+
case_to_test = "real"
48+
part_to_test = 1
49+
50+
# -------------------------------- Initialize some variables ------------------------- #
51+
52+
puzzle_input = test_data[case_to_test]["input"]
53+
puzzle_expected_result = test_data[case_to_test]["expected"][part_to_test - 1]
54+
puzzle_actual_result = "Unknown"
55+
56+
57+
# -------------------------------- Actual code execution ----------------------------- #
58+
59+
60+
def manhattan_distance(source, target):
61+
dist = 0
62+
for i in range(len(source)):
63+
dist += abs(target[i] - source[i])
64+
return dist
65+
66+
67+
if part_to_test == 1:
68+
69+
distances = {}
70+
stars = []
71+
for string in puzzle_input.split("\n"):
72+
if string == "":
73+
continue
74+
stars.append(tuple(map(int, string.split(","))))
75+
76+
graph = pathfinding.Graph(list(range(len(stars))))
77+
78+
merges = []
79+
for star_id in range(len(stars)):
80+
for star2_id in range(len(stars)):
81+
if star_id == star2_id:
82+
continue
83+
if manhattan_distance(stars[star_id], stars[star2_id]) <= 3:
84+
if star_id in graph.edges:
85+
graph.edges[star_id].append(star2_id)
86+
else:
87+
graph.edges[star_id] = [star2_id]
88+
89+
groups = graph.dfs_groups()
90+
91+
print(groups)
92+
puzzle_actual_result = len(groups)
93+
94+
95+
else:
96+
for string in puzzle_input.split("\n"):
97+
if string == "":
98+
continue
99+
100+
101+
# -------------------------------- Outputs / results --------------------------------- #
102+
103+
print("Expected result : " + str(puzzle_expected_result))
104+
print("Actual result : " + str(puzzle_actual_result))

0 commit comments

Comments
 (0)