def parse_input(string): points = set() grid = [] for y, row in enumerate(string.strip("\n").splitlines()): grid.append([]) for x, character in enumerate(row): grid[-1].append(character) if character == ".": points.add(Point(x, y)) portal_pairs = collections.defaultdict(list) for y, row in enumerate(grid): for x, character in enumerate(row): if not character.isalpha(): continue character_point = Point(x, y) neighboring_points = points.intersection( character_point.neighbors()) if not neighboring_points: continue neighboring_point = list(neighboring_points)[0] direction_to_other_letter = character_point.direction_to( neighboring_point).inverse_direction() other_letter_coords = character_point.move_by_direction( direction_to_other_letter) other_letter = grid[other_letter_coords.y][other_letter_coords.x] portal_pairs["".join(sorted(other_letter + character))].append(neighboring_point) graph = collections.defaultdict(set) for p in points: graph[p].update(points.intersection(p.neighbors())) for point_pair in portal_pairs.values(): if len(point_pair) < 2: continue p1, p2, = point_pair graph[p1].add(p2) graph[p2].add(p1) graph = dict(graph.items()) start, end = portal_pairs["AA"][0], portal_pairs["ZZ"][0] return graph, start, end
class Explorer: def __init__(self, tape): self.tape = tape self.position = Point(0, 0) self.graph = collections.defaultdict(set) self.oxygen = None def explore(self): places_to_visit = collections.deque([self.position]) visited = {self.position} while places_to_visit: next_point = places_to_visit.pop() self.move_to(next_point) unvisited_neighbors = set(self.position.neighbors()) - visited for neighbor in unvisited_neighbors: status = self.explore_adjacent_point(neighbor) if status == StatusCode.Wall: visited.add(neighbor) if status in [StatusCode.Step, StatusCode.Oxygen]: self.graph[self.position].add(neighbor) self.graph[neighbor].add(self.position) places_to_visit.append(neighbor) if status == StatusCode.Oxygen: self.oxygen = neighbor visited.add(self.position) def find_path(self, start, end): import heapq as h distances = {start: 0} heap = [(0, start)] prev_node = dict() while end not in prev_node: weight, node = h.heappop(heap) unvisited_neighbors = self.graph[node] - distances.keys() for neighbor in unvisited_neighbors: prev_node[neighbor] = node distance = weight + 1 distances[neighbor] = weight h.heappush(heap, (distance, neighbor)) cur_node = end path = [end] while cur_node != start: previous_node = prev_node[cur_node] path.append(previous_node) cur_node = previous_node return list(reversed(path))[1:] def distances_from(self, start): import heapq as h distances = {start: 0} heap = [(0, start)] while heap: weight, node = h.heappop(heap) unvisited_neighbors = self.graph[node] - distances.keys() for neighbor in unvisited_neighbors: distance = weight + 1 distances[neighbor] = weight h.heappush(heap, (distance, neighbor)) return distances def move_direction(self, direction): input_code = direction_to_int(direction) status_code = self.tape.run_until_output(provide_input=input_code)[0] status = StatusCode(status_code) if status != StatusCode.Wall: self.position = self.position.displace(*direction.value) return status def move_to(self, destination): if self.position == destination: return for point in self.find_path(self.position, destination): self.move_direction(self.position.direction_to(point)) def explore_adjacent_point(self, point): direction = self.position.direction_to(point) status = self.move_direction(direction) if status != StatusCode.Wall: self.move_direction(direction.inverse_direction()) return status