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
Lines changed: 242 additions & 0 deletions
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

Lines changed: 104 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy