Skip to content

Commit 3a6c96c

Browse files
committed
Added days 2019-19 and 2019-20
1 parent bb79269 commit 3a6c96c

File tree

2 files changed

+393
-0
lines changed

2 files changed

+393
-0
lines changed

2019/19-Tractor Beam.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# -------------------------------- Input data ---------------------------------------- #
2+
import os, pathfinding, IntCode, math
3+
4+
from complex_utils import *
5+
6+
test_data = {}
7+
8+
test = 1
9+
test_data[test] = {
10+
"input": """""",
11+
"expected": ["Unknown", "Unknown"],
12+
}
13+
14+
test = "real"
15+
input_file = os.path.join(
16+
os.path.dirname(__file__),
17+
"Inputs",
18+
os.path.basename(__file__).replace(".py", ".txt"),
19+
)
20+
test_data[test] = {
21+
"input": open(input_file, "r+").read().strip(),
22+
"expected": ["169", "7001134"],
23+
}
24+
25+
# -------------------------------- Control program execution ------------------------- #
26+
27+
case_to_test = "real"
28+
part_to_test = 2
29+
30+
# -------------------------------- Initialize some variables ------------------------- #
31+
32+
puzzle_input = test_data[case_to_test]["input"]
33+
puzzle_expected_result = test_data[case_to_test]["expected"][part_to_test - 1]
34+
puzzle_actual_result = "Unknown"
35+
36+
37+
# -------------------------------- Actual code execution ----------------------------- #
38+
39+
if part_to_test == 1:
40+
beam = IntCode.IntCode(puzzle_input)
41+
42+
affected = 0
43+
for x in range(50):
44+
for y in range(50):
45+
beam.reset(puzzle_input)
46+
beam.add_input(x)
47+
beam.add_input(y)
48+
beam.run()
49+
affected += beam.outputs.pop()
50+
51+
puzzle_actual_result = affected
52+
53+
54+
else:
55+
beam = IntCode.IntCode(puzzle_input)
56+
known_points = {}
57+
58+
def check_tractor(position):
59+
if position not in known_points:
60+
beam.reset(puzzle_input)
61+
beam.add_input(position.real)
62+
beam.add_input(-position.imag)
63+
beam.run()
64+
known_points[position] = beam.outputs.pop()
65+
return known_points[position] == 1
66+
67+
# If we call alpha the angle from vertical to the lowest part of the beam
68+
# And beta the angle from vertical to the highest part of the beam
69+
# And x, y the target position
70+
# Then we have:
71+
# x + 100 = y*tan(beta)
72+
# x = (y+100)*tan(alpha)
73+
# Therefore:
74+
# y = 100*(tan (alpha) - 1) / (tan(beta) - tan(alpha))
75+
# x = y * tan(beta) - 100
76+
77+
# First, get an approximation of alpha and beta
78+
def search_x(direction):
79+
y = 1000
80+
x = 0 if direction == 1 else 10 ** 4
81+
resolution = 100
82+
while True:
83+
if check_tractor(x + resolution - j * y) == 1:
84+
if resolution == 1:
85+
break
86+
resolution //= 2
87+
else:
88+
x += resolution * direction
89+
return x
90+
91+
alpha = math.atan(search_x(1) / 1000)
92+
beta = math.atan(search_x(-1) / 1000)
93+
94+
# Then, math!
95+
# Note: We look for size 150 as a safety
96+
y = 150 * (math.tan(alpha) + 1) / (math.tan(beta) - math.tan(alpha))
97+
x = y * math.tan(beta) - 150
98+
position = int(x) - int(y) * j
99+
100+
def corners(position):
101+
# We need to check only those 2 positions
102+
return [position + 99, position - 99 * j]
103+
104+
valid_position = 0
105+
checked_positions = []
106+
best_position = position
107+
resolution = 100
108+
109+
while True:
110+
box = corners(position)
111+
checked_positions.append(position)
112+
113+
new_position = position
114+
if check_tractor(box[0]) and check_tractor(box[1]):
115+
if manhattan_distance(0, best_position) > manhattan_distance(0, position):
116+
best_position = position
117+
# If I move the box just by 1, it fails
118+
if (
119+
not check_tractor(box[0] + 1)
120+
and not check_tractor(box[0] + 1 * j)
121+
and not check_tractor(box[1] + 1 * j)
122+
and not check_tractor(box[1] + 1 * j)
123+
):
124+
break
125+
new_position += resolution * j
126+
elif check_tractor(box[0]):
127+
new_position += resolution
128+
elif check_tractor(box[1]):
129+
new_position -= resolution
130+
else:
131+
new_position -= resolution * j
132+
133+
# This means we have already checked the new position
134+
# So, either we reduce the resolution, or we check closer
135+
if new_position in checked_positions:
136+
if resolution != 1:
137+
resolution //= 2
138+
else:
139+
# This means we are close
140+
# So now, check the 10*10 grid closer to the emitter
141+
found = False
142+
for dx in range(10, 0, -1):
143+
for dy in range(10, 0, -1):
144+
test = best_position - dx + dy * j
145+
box = corners(test)
146+
if check_tractor(box[0]) and check_tractor(box[1]):
147+
new_position = test
148+
found = True
149+
break
150+
151+
if not found:
152+
break
153+
position = new_position
154+
puzzle_actual_result = int(best_position.real * 10 ** 4 - best_position.imag)
155+
156+
157+
# -------------------------------- Outputs / results --------------------------------- #
158+
159+
print("Expected result : " + str(puzzle_expected_result))
160+
print("Actual result : " + str(puzzle_actual_result))

2019/20-Donut Maze.py

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# -------------------------------- Input data ---------------------------------------- #
2+
import os, pathfinding
3+
4+
from complex_utils import *
5+
6+
test_data = {}
7+
8+
test = 1
9+
test_data[test] = {
10+
"input": """ A
11+
A
12+
#################.#############
13+
#.#...#...................#.#.#
14+
#.#.#.###.###.###.#########.#.#
15+
#.#.#.......#...#.....#.#.#...#
16+
#.#########.###.#####.#.#.###.#
17+
#.............#.#.....#.......#
18+
###.###########.###.#####.#.#.#
19+
#.....# A C #.#.#.#
20+
####### S P #####.#
21+
#.#...# #......VT
22+
#.#.#.# #.#####
23+
#...#.# YN....#.#
24+
#.###.# #####.#
25+
DI....#.# #.....#
26+
#####.# #.###.#
27+
ZZ......# QG....#..AS
28+
###.### #######
29+
JO..#.#.# #.....#
30+
#.#.#.# ###.#.#
31+
#...#..DI BU....#..LF
32+
#####.# #.#####
33+
YN......# VT..#....QG
34+
#.###.# #.###.#
35+
#.#...# #.....#
36+
###.### J L J #.#.###
37+
#.....# O F P #.#...#
38+
#.###.#####.#.#####.#####.###.#
39+
#...#.#.#...#.....#.....#.#...#
40+
#.#####.###.###.#.#.#########.#
41+
#...#.#.....#...#.#.#.#.....#.#
42+
#.###.#####.###.###.#.#.#######
43+
#.#.........#...#.............#
44+
#########.###.###.#############
45+
B J C
46+
U P P """,
47+
"expected": ["58", "Unknown"],
48+
}
49+
50+
test += 1
51+
test_data[test] = {
52+
"input": """ Z L X W C
53+
Z P Q B K
54+
###########.#.#.#.#######.###############
55+
#...#.......#.#.......#.#.......#.#.#...#
56+
###.#.#.#.#.#.#.#.###.#.#.#######.#.#.###
57+
#.#...#.#.#...#.#.#...#...#...#.#.......#
58+
#.###.#######.###.###.#.###.###.#.#######
59+
#...#.......#.#...#...#.............#...#
60+
#.#########.#######.#.#######.#######.###
61+
#...#.# F R I Z #.#.#.#
62+
#.###.# D E C H #.#.#.#
63+
#.#...# #...#.#
64+
#.###.# #.###.#
65+
#.#....OA WB..#.#..ZH
66+
#.###.# #.#.#.#
67+
CJ......# #.....#
68+
####### #######
69+
#.#....CK #......IC
70+
#.###.# #.###.#
71+
#.....# #...#.#
72+
###.### #.#.#.#
73+
XF....#.# RF..#.#.#
74+
#####.# #######
75+
#......CJ NM..#...#
76+
###.#.# #.###.#
77+
RE....#.# #......RF
78+
###.### X X L #.#.#.#
79+
#.....# F Q P #.#.#.#
80+
###.###########.###.#######.#########.###
81+
#.....#...#.....#.......#...#.....#.#...#
82+
#####.#.###.#######.#######.###.###.#.#.#
83+
#.......#.......#.#.#.#.#...#...#...#.#.#
84+
#####.###.#####.#.#.#.#.###.###.#.###.###
85+
#.......#.....#.#...#...............#...#
86+
#############.#.#.###.###################
87+
A O F N
88+
A A D M """,
89+
"expected": ["Unknown", "396"],
90+
}
91+
92+
test = "real"
93+
input_file = os.path.join(
94+
os.path.dirname(__file__),
95+
"Inputs",
96+
os.path.basename(__file__).replace(".py", ".txt"),
97+
)
98+
test_data[test] = {
99+
"input": open(input_file, "r+").read(),
100+
"expected": ["642", "7492"],
101+
}
102+
103+
# -------------------------------- Control program execution ------------------------- #
104+
105+
case_to_test = 2
106+
part_to_test = 2
107+
108+
# -------------------------------- Initialize some variables ------------------------- #
109+
110+
puzzle_input = test_data[case_to_test]["input"]
111+
puzzle_expected_result = test_data[case_to_test]["expected"][part_to_test - 1]
112+
puzzle_actual_result = "Unknown"
113+
114+
115+
# -------------------------------- Actual code execution ----------------------------- #
116+
def grid_to_vertices(self, grid, diagonals_allowed=False, wall="#"):
117+
self.vertices = {}
118+
y = 0
119+
120+
for line in grid.splitlines():
121+
for x in range(len(line)):
122+
if line[x] != wall:
123+
self.vertices[x - y * j] = line[x]
124+
y += 1
125+
126+
for source in self.vertices:
127+
for direction in directions_straight:
128+
target = source + direction
129+
if target in self.vertices:
130+
if source in self.edges:
131+
self.edges[source][target] = 1
132+
else:
133+
self.edges[source] = {target: 1}
134+
135+
return True
136+
137+
138+
pathfinding.WeightedGraph.grid_to_vertices = grid_to_vertices
139+
140+
141+
grid = pathfinding.WeightedGraph()
142+
grid.grid_to_vertices(puzzle_input.replace(" ", "#"))
143+
width, height = max_real(grid.vertices), -min_imag(grid.vertices)
144+
letters = grid.grid_search(puzzle_input, "abcdefghijklmnopqrstuvwxyz".upper())
145+
portals = {}
146+
for letter in letters:
147+
for position in letters[letter]:
148+
# Vertical portal
149+
if (
150+
grid.vertices.get(position + south, "#")
151+
in "abcdefghijklmnopqrstuvwxyz".upper()
152+
):
153+
portal = letter + grid.vertices[position + south]
154+
if grid.vertices.get(position + south * 2, "#") == ".":
155+
portal_position = position + south * 2
156+
else:
157+
portal_position = position - south
158+
159+
# Horizontal portal
160+
elif (
161+
grid.vertices.get(position + east, "#")
162+
in "abcdefghijklmnopqrstuvwxyz".upper()
163+
):
164+
portal = letter + grid.vertices[position + east]
165+
if grid.vertices.get(position + east * 2, "#") == ".":
166+
portal_position = position + east * 2
167+
else:
168+
portal_position = position - east
169+
else:
170+
continue
171+
172+
portal_position = SuperComplex(portal_position)
173+
174+
# Find whether we're at the center or not (I don't care for AA or ZZ)
175+
if portal in ("AA", "ZZ"):
176+
portals[portal] = portal_position
177+
elif portal_position.real == 2 or portal_position.real == width - 2:
178+
portals[(portal, "out")] = portal_position
179+
elif portal_position.imag == -2 or portal_position.imag == -(height - 2):
180+
portals[(portal, "out")] = portal_position
181+
else:
182+
portals[(portal, "in")] = portal_position
183+
184+
185+
if part_to_test == 1:
186+
for portal in portals:
187+
if len(portal) == 2 and portal[1] == "in":
188+
portal_in = portals[portal]
189+
portal_out = portals[(portal[0], "out")]
190+
grid.edges[portal_in][portal_out] = 1
191+
grid.edges[portal_in][portal_out] = 1
192+
193+
grid.dijkstra(portals["AA"], portals["ZZ"])
194+
puzzle_actual_result = grid.distance_from_start[portals["ZZ"]]
195+
196+
197+
else:
198+
edges = {}
199+
for portal in portals:
200+
grid.reset_search()
201+
grid.dijkstra(portals[portal])
202+
for other_portal in portals:
203+
if portal == other_portal:
204+
continue
205+
if not portals[other_portal] in grid.distance_from_start:
206+
continue
207+
distance = grid.distance_from_start[portals[other_portal]]
208+
for level in range(20):
209+
if portal in ("AA", "ZZ") and level != 0:
210+
break
211+
if other_portal in ("AA", "ZZ") and level != 0:
212+
break
213+
if (portal, level) in edges:
214+
edges[(portal, level)].update({(other_portal, level): distance})
215+
else:
216+
edges[(portal, level)] = {(other_portal, level): distance}
217+
218+
if len(portal) == 2 and portal[1] == "in":
219+
portal_out = (portal[0], "out")
220+
edges[(portal, level)].update({(portal_out, level + 1): 1})
221+
elif len(portal) == 2 and portal[1] == "out" and level != 0:
222+
portal_in = (portal[0], "in")
223+
edges[(portal, level)].update({(portal_in, level - 1): 1})
224+
225+
grid = pathfinding.WeightedGraph({}, edges)
226+
227+
grid.dijkstra(("AA", 0), ("ZZ", 0))
228+
puzzle_actual_result = grid.distance_from_start[("ZZ", 0)]
229+
230+
# -------------------------------- Outputs / results --------------------------------- #
231+
232+
print("Expected result : " + str(puzzle_expected_result))
233+
print("Actual result : " + str(puzzle_actual_result))

0 commit comments

Comments
 (0)