示例#1
0
def portals(m: Matrix) -> Set[Target]:
    def portal_position(p1, p2):
        if p1[0] == p2[0]:  # x
            x = p1[0]
            y1, y2 = p1[1], p2[1]
            if 0 <= y1 - 1 and m.get(x, y1 - 1) == OPEN:
                return x, y1 - 1
            else:
                return x, y2 + 1
        else:  # y
            y = p1[1]
            x1, x2 = p1[0], p2[0]
            if 0 <= x1 - 1 and m.get(x1 - 1, y) == OPEN:
                return x1 - 1, y
            else:
                return x2 + 1, y

    def horizontal_portals_at(y: int):
        xs = [
            x for x in range(m.width)
            if m.get(x, y) in PORTALS and m.get(x, y + 1) in PORTALS
        ]
        return [
            Target(*portal_position((x, y), (x, y + 1)),
                   m.get(x, y) + m.get(x, y + 1)) for x in xs
        ]

    def vertical_portals_at(x: int):
        ys = [
            y for y in range(m.height)
            if m.get(x, y) in PORTALS and m.get(x + 1, y) in PORTALS
        ]
        return [
            Target(*portal_position((x, y), (x + 1, y)),
                   m.get(x, y) + m.get(x + 1, y)) for y in ys
        ]

    donut_width = 0
    mid_y = m.height // 2
    for x in range(2, m.width):
        if m.get(x, mid_y) == " ":
            if m.get(x, mid_y) in PORTALS:
                donut_width = x - 4  # 2 for the outer edge and 2 for the portal we just skipped
            else:
                donut_width = x - 2
            break

    outer_portals = horizontal_portals_at(0) + horizontal_portals_at(m.height - 2) + \
                    vertical_portals_at(0) + vertical_portals_at(m.width - 2)
    inner_portals = horizontal_portals_at(2 + donut_width) + horizontal_portals_at(m.height - 4 - donut_width) + \
                    vertical_portals_at(2 + donut_width) + vertical_portals_at(m.width - 4 - donut_width)

    return set(outer_portals).union(
        set(map(lambda t: Target(t.x, t.y, t.name + "X"), inner_portals)))
示例#2
0
def expand(m: Matrix, target_positions: Set[Tuple[int, int]], x: int, y: int):
    x_expand = list(
        map(lambda x: (x, y),
            filter(lambda x: x >= 0 and x < m.width, [x + 1, x - 1])))
    y_expand = list(
        map(lambda y: (x, y),
            filter(lambda x: x >= 0 and x < m.height, [y + 1, y - 1])))
    return list(
        filter(lambda p: p in target_positions or m.get(*p) == OPEN,
               x_expand + y_expand))
def map_to_graphs(the_map: Matrix):
    graphs = []
    keys_to_collect = all_keys(the_map.values)

    for x, y in [x for x in the_map.index_range() if the_map.get(*x) == "@"]:
        graph = Graph()
        paths_from_start = find_edges_at(the_map, (x, y), keys_to_collect)
        for k, v in paths_from_start.items():
            graph.put("@", k, *v)

        keys_in_graph = set(paths_from_start.keys())
        while len(keys_in_graph) > 0:
            key = keys_in_graph.pop()
            paths = find_edges(the_map, key, keys_in_graph)
            for k, v in paths.items():
                graph.put(key, k, *v)

        graphs.append(graph)

    return graphs
def find_edges_at(the_map: Matrix, start: Tuple[int, int],
                  targets: Set[str]) -> Dict[str, Tuple[int, str]]:
    def legal_moves(pos: Position) -> Set[Position]:
        x, y, passed_doors = pos

        def accessible(tile):
            x, y = tile
            if 0 <= x < the_map.width and 0 <= y < the_map.height:
                tile_value = the_map.get(x, y)
                return tile_value != WALL
            else:
                return False

        moves = [(x, y + 1), (x, y - 1), (x + 1, y), (x - 1, y)]
        positions = set(
            map(lambda x: move_to_position(x[0], x[1], passed_doors),
                filter(accessible, moves)))
        return positions

    def move_to_position(x, y, passed_doors):
        tile = the_map.get(x, y)
        if is_door(tile) and tile not in passed_doors:
            return x, y, ''.join(sorted(passed_doors + tile))
        else:
            return x, y, passed_doors

    # BFS
    starting_position = start + ("", )
    search_front = {starting_position}
    visited = {starting_position}
    steps = 0
    paths = {}
    found = set()

    while len(search_front) > 0 and found != targets:
        for p in search_front:
            xy = coordinates(p)
            tile = the_map.get(*xy)
            if tile in targets:
                if tile not in paths:
                    paths[tile] = (steps, doors(p))
                found.add(tile)

        expanded_front = {
            m
            for pos in search_front for m in legal_moves(pos)
            if m not in visited
        }
        # increment and repeat
        search_front = expanded_front
        visited = visited.union(search_front)
        steps += 1

    return paths
示例#5
0
    def test_bfs_to_graph(self):
        the_map = [
            "########################",
            "#[email protected].#",
            "######################.#",
            "#d.....................#",
            "########################"
        ]
        m = Matrix()
        targets = []
        m.init_from_string('\n'.join(the_map))
        for x, y in m.index_range():
            name = m.get(x, y)
            if name in string.ascii_lowercase:
                targets.append(Target(x, y, name))

        def expand(x, y):
            x_expand = list(map(lambda x: (x, y), filter(lambda x: x >= 0 and x < m.width, [x + 1, x - 1])))
            y_expand = list(map(lambda y: (x, y), filter(lambda x: x >= 0 and x < m.height, [y + 1, y - 1])))
            return list(filter(lambda p: m.get(*p) != "#", x_expand + y_expand))

        g = bfs_to_graph(targets, expand)

        self.assertEqual(10, g.distance('b', 'c'))
        self.assertEqual(44, g.distance('d', 'f'))
        self.assertEqual(len(targets), len(g.__neighbours))
示例#6
0
def number_of_moves_to_collect_all_keys(the_map: Matrix) -> int:
    start_tile = the_map.index_of(START)
    target_keys = all_keys(the_map.values)

    def legal_moves(pos: Position) -> Set[Position]:
        x, y, ks = pos  # todo ks is searched, should be set instead

        def accessible(tile):
            x, y = tile
            if 0 <= x < the_map.width and 0 <= y < the_map.height:
                tile_value = the_map.get(x, y)
                return (tile_value == OPEN or
                        tile_value == START or
                        is_key(tile_value) or
                        is_door(tile_value) and tile_value.lower() in ks
                        )
            else:
                return False

        moves = [
            (x, y + 1),
            (x, y - 1),
            (x + 1, y),
            (x - 1, y)
        ]
        positions = set(map(lambda x: move_to_position(x[0], x[1], ks), filter(accessible, moves)))
        return positions

    def move_to_position(x, y, collected_keys):
        key = the_map.get(x, y)
        if is_key(key) and key not in collected_keys:
            return x, y, ''.join(sorted(collected_keys + key))
        else:
            return x, y, collected_keys

        start = start_tile + ("",)
        search_front = {start}
        visited = {start}
        steps = 0
        while len(search_front) > 0:
            for p in search_front:
                if keys(p) == target_keys:
                    # Found it!
                    return steps

            expanded_front = {m for pos in search_front for m in legal_moves(pos) if m not in visited}
            # increment and repeat
            search_front = expanded_front
            visited = visited.union(search_front)
            steps += 1

        return -1  # not found
示例#7
0
def map_to_matrix(the_map: List[str]) -> Matrix:
    m = Matrix()
    m.init_from_string('\n'.join(the_map))
    return m
示例#8
0
    graph = bfs_to_graph(ps, maze_expand)

    # add teleportation
    for name in list(map(lambda n: n.name, ps)):
        alt_name = name + "X"
        if graph.has_node(alt_name):
            graph.add_node(name, alt_name, 1)
            graph.add_node(alt_name, name, 1)

    return graph


if __name__ == "__main__":
    maze = util.read_input("day20", should_strip=False)
    m = Matrix()
    m.init_from_string('\n'.join(maze))
    ps = portals(m)

    g = construct_graph(ps, m)
    min_distance = dijkstra(g, "AA", "ZZ")

    print("Part Onw: ", min_distance)

# --- Part Two ---
#
# Strangely, the exit isn't open when you reach it. Then, you remember: the ancient Plutonians were famous for building recursive spaces.
#
# The marked connections in the maze aren't portals: they physically connect to a larger or smaller copy of the maze. Specifically, the labeled tiles around the inside edge actually connect to a smaller copy of the same maze, and the smaller copy's inner labeled tiles connect to yet a smaller copy, and so on.
#
# When you enter the maze, you are at the outermost level; when at the outermost level, only the outer labels AA and ZZ function (as the start and end, respectively); all other outer labeled tiles are effectively walls. At any other level, AA and ZZ count as walls, but the other outer labeled tiles bring you one level outward.
示例#9
0
#     The bottom-middle intersection is 6 from the left and 4 from the top, so its alignment parameter is 24.
#     The bottom-right intersection's alignment parameter is 40.
#
# To calibrate the cameras, you need the sum of the alignment parameters. In the above example, this is 76.
#
# Run your ASCII program. What is the sum of the alignment parameters for the scaffold intersections?


SCAFFOLDING = "#"

program = list(map(int, read_input("day17", ",")))
computer = IntcodeComputer(program)
computer.run()
output = ''.join(list(map(chr, computer.output_list)))
elements = list(map(list, output.split("\n")))
matrix = Matrix()
matrix.init_from_values(elements)
# matrix.print_top_left(print_headers=True)
x_points = 0
for x, y in matrix.index_range():
    if 0 < x < matrix.width and 0 < y < matrix.height and matrix.get(x, y) == SCAFFOLDING:
        if matrix.get(x - 1, y) == SCAFFOLDING and \
            matrix.get(x + 1, y) == SCAFFOLDING and \
            matrix.get(x, y - 1) == SCAFFOLDING and \
            matrix.get(x, y + 1) == SCAFFOLDING:
            x_points += x * y

print("Part One:", x_points)


# --- Part Two ---
示例#10
0
def to_matrix(lines):
    return Matrix().init_from_values(list(map(list, lines)))
示例#11
0
#
# In this example, the number of points affected by the tractor beam in the 10x10 area closest to the emitter is 27.
#
# However, you'll need to scan a larger area to understand the shape of the beam. How many points are affected by the tractor beam in the 50x50 area closest to the emitter? (For each of X and Y, this will be 0 through 49.)


def run_program(mem, x, y):
    comp = IntcodeComputer(mem)
    comp.run([x, y])
    return comp.output_list[-1]


if __name__ == '__main__':
    the_input = list(map(int, read_input("day19", ",")))

    m = Matrix().init_from_elem(50, 20, ".")
    for x, y in m.index_range():
        is_pulled = run_program(the_input, x, y)
        if is_pulled == 1:
            m.set(x, y, "#")

    m.print_top_left(print_headers=True)

    beam_area = len(list(filter(lambda i: i == "#", m.values)))

    print()
    print("Part One: ", beam_area)

# --- Part Two ---
#
# You aren't sure how large Santa's ship is. You aren't even sure if you'll need to use this thing on Santa's ship, but it doesn't hurt to be prepared. You figure Santa's ship might fit in a 100x100 square.
def find_edges(the_map: Matrix, start: str,
               targets: Set[str]) -> Dict[str, Tuple[int, str]]:
    return find_edges_at(the_map, the_map.index_of(start), targets)