예제 #1
0
    def neighbors(self, point: Point, collected_keys: t.Set[str]):
        """Get neighbors (including open passages) to a point, taking into account collected keys"""
        unopened_doors = {
            point
            for point, door_name in self.doors.items()
            if door_name.lower() not in collected_keys
        }

        return self.points.intersection(point.neighbors()) - unopened_doors
예제 #2
0
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
예제 #3
0
def grid_generator(start: Grid) -> typing.Iterable[Grid]:
    current_grid = start
    while True:
        next_grid = set()
        for x, y in itertools.product(range(5), range(5)):

            point = Point(x, y)
            num_adjacent_bugs = len(current_grid.intersection(point.neighbors()))

            if point not in current_grid and num_adjacent_bugs in [1, 2]:
                next_grid.add(point)

            if point in current_grid and num_adjacent_bugs == 1:
                next_grid.add(point)

        yield frozenset(next_grid)
        current_grid = next_grid
예제 #4
0
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