Ejemplo n.º 1
0
    def part2(self):
        waypoint = Coordinate(10, 1)
        ship = Coordinate(0, 0)

        for line in self.lines:
            value = int(line[1:])
            command = line[0]
            if command == "N":
                waypoint = waypoint.up(value)
            elif command == "S":
                waypoint = waypoint.down(value)
            elif command == "E":
                waypoint = waypoint.right(value)
            elif command == "W":
                waypoint = waypoint.left(value)
            elif command == "L":
                waypoint = waypoint.ccw_around(ship, value // 90)
            elif command == "R":
                waypoint = waypoint.cw_around(ship, value // 90)
            elif command == "F":
                diff_coordinate = waypoint - ship
                ship = ship + (diff_coordinate * value)
                waypoint = ship + diff_coordinate

        result = ship.manhattan(Coordinate(0, 0))

        print("Part 2:", result)
Ejemplo n.º 2
0
    def __init__(self, file_name):
        lines = Input(file_name).lines()
        self.coordinates: Set[Coordinate] = set()
        self.grid = InfiniteGrid[Coordinate]()

        for line in lines:
            x, y = line.split(', ')
            coordinate = Coordinate(int(x),
                                    int(y),
                                    system=CoordinateSystem.X_RIGHT_Y_DOWN)
            self.coordinates.add(coordinate)
            self.grid[coordinate] = coordinate

        self.bounding_box: BoundingBox = self.grid.bounding_box

        for x, y in self.bounding_box:
            test_coordinate = Coordinate(
                x, y, system=CoordinateSystem.X_RIGHT_Y_DOWN)

            distances = dict(
                (coordinate, test_coordinate.manhattan(coordinate))
                for coordinate in self.coordinates)
            _, min_distance = min(distances.items(), key=lambda i: i[1])
            min_coordinates = [
                coordinate for coordinate, distance in distances.items()
                if distance == min_distance
            ]

            if len(min_coordinates) != 1:
                continue

            self.grid[test_coordinate] = min_coordinates[0]
Ejemplo n.º 3
0
 def __init__(self, file_name):
     self._grid: InfiniteGrid[int] = Input(file_name).grid().map(
         lambda x: int(x))
     # self._graph = grid.to_graph(1, 2, 3, 4, 5, 6, 7, 8, 9)
     self.start = Coordinate(x=self._grid.min_x,
                             y=self._grid.min_y,
                             system=CoordinateSystem.X_RIGHT_Y_DOWN)
     self.size = self._grid.max_x + 1
     self.end = Coordinate(x=self.size - 1,
                           y=self.size - 1,
                           system=CoordinateSystem.X_RIGHT_Y_DOWN)
Ejemplo n.º 4
0
    def __init__(self, file_name):
        line = Input(file_name).line()

        target_regex = re.compile(r'target area: x=(-?\d+)..(-?\d+), y=(-?\d+)..(-?\d+)')
        match = target_regex.match(line)
        box = BoundingBox()
        box = box.expand_x(int(match.group(1)))
        box = box.expand_x(int(match.group(2)))
        box = box.expand_y(int(match.group(3)))
        box = box.expand_y(int(match.group(4)))
        self.target = box

        grid: InfiniteGrid[str] = InfiniteGrid[str]()

        start_x = 1
        attempt = FiringAttempt(start_x, 0)
        while attempt.step_until(self.target) is None:
            start_x += 1
            attempt = FiringAttempt(start_x, 0)

        starting_coordinate = Coordinate(start_x, 0)
        queue: Queue = Queue()
        queue.put(starting_coordinate)
        seen: Set[Coordinate] = set()
        seen.add(starting_coordinate)

        max_y = 0

        while not queue.empty():
            item: Coordinate = queue.get()
            attempt = FiringAttempt(item.x, item.y)
            end = attempt.step_until(self.target)
            if end is not None:
                max_y = max(max_y, attempt.max_y)
                grid[item] = '*'
                for dx in range(-5, 20):
                    for dy in range(-20, 20):
                        neighbor = Coordinate(item.x + dx, item.y + dy)
                        if neighbor in seen:
                            continue

                        queue.put(neighbor)
                        seen.add(neighbor)

        for x in range(self.target.min_x, self.target.max_x + 1):
            for y in range(self.target.min_y, self.target.max_y + 1):
                grid[Coordinate(x, y)] = '#'

        grid[Coordinate(0, 0)] = '!'
        # grid.to_grid().print(not_found='.')

        self.max_y = max_y
        self.hitting_velocity_count = len(grid.find(lambda _: _ in '*#'))
Ejemplo n.º 5
0
    def __init__(self, file_name):
        self.instructions = Input(file_name).lines()
        self.grid: InfiniteGrid[bool] = InfiniteGrid[bool]()

        reference_coordinate = Coordinate(0, 0)

        for instruction in self.instructions:
            coordinate = reference_coordinate
            index = 0
            while index < len(instruction):
                if instruction[index] == 'n':
                    if instruction[index + 1] == 'w':
                        coordinate = coordinate.left().up()
                    elif instruction[index + 1] == 'e':
                        coordinate = coordinate.up()
                    index += 2
                elif instruction[index] == 's':
                    if instruction[index + 1] == 'w':
                        coordinate = coordinate.down()
                    elif instruction[index + 1] == 'e':
                        coordinate = coordinate.right().down()
                    index += 2
                elif instruction[index] == 'w':
                    coordinate = coordinate.left()
                    index += 1
                elif instruction[index] == 'e':
                    coordinate = coordinate.right()
                    index += 1

            if coordinate in self.grid:
                self.grid[coordinate] = not self.grid[coordinate]
            else:
                self.grid[coordinate] = True
Ejemplo n.º 6
0
    def part2(self):
        self.grid.clear()
        self.grid[Coordinate(0, 0)] = True
        self._paint()

        print("Part 2:")
        self.grid.to_grid().print(key=lambda is_white: '#' if is_white else ' ')
Ejemplo n.º 7
0
    def to_grid(self) -> Grid[T]:
        data = {}

        min_x = min_y = 4294967296
        max_x = max_y = -4294967296

        for coordinate, item in self._data.items():
            x = coordinate.x
            y = coordinate.y
            if coordinate.system == CoordinateSystem.X_RIGHT_Y_DOWN:
                y = -y

            coordinate = Coordinate(x, y, system=CoordinateSystem.X_RIGHT_Y_UP)
            data[coordinate] = item

            min_x = min(min_x, x)
            max_x = max(max_x, x)
            min_y = min(min_y, y)
            max_y = max(max_y, y)

        width = max_x - min_x + 1
        height = max_y - min_y + 1

        result = Grid[T](width, height)
        for coordinate, item in data.items():
            result[coordinate.x - min_x, max_y - coordinate.y] = item

        return result
Ejemplo n.º 8
0
 def fold_func(coordinate: Coordinate):
     if coordinate.x > value:
         return Coordinate(x=-(coordinate.x - value) + value,
                           y=coordinate.y,
                           system=coordinate.system)
     else:
         return coordinate
Ejemplo n.º 9
0
    def _asteroids_visible_from(self, base: Coordinate):
        seen_angles = {}

        asteroid: Coordinate
        for asteroid in self.asteroids:
            dx = asteroid.x - base.x
            dy = asteroid.y - base.y

            if dx == dy == 0:
                continue

            divider = gcd(dx, dy)
            dx = dx // divider
            dy = dy // divider
            t = (dx, dy,)

            # Asteroid is closer than what we have
            if t in seen_angles and seen_angles[t] > divider:
                seen_angles[t] = divider
            elif t not in seen_angles:
                seen_angles[t] = divider

        result = []
        for position, divider in seen_angles.items():
            coordinate = Coordinate(
                position[0] * divider + base.x,
                position[1] * divider + base.y,
                system=CoordinateSystem.X_RIGHT_Y_DOWN
            )
            result.append(coordinate)

        return result
Ejemplo n.º 10
0
    def part2(self):
        new_grid: InfiniteGrid[int] = self._grid.copy()

        for mult_x in range(0, 5):
            for mult_y in range(0, 5):
                if mult_x == 0 and mult_y == 0:
                    continue
                for x in range(self.size):
                    for y in range(self.size):
                        if mult_y == 0:
                            previous_x = (mult_x - 1) * self.size + x
                            previous_y = y
                        else:
                            previous_x = mult_x * self.size + x
                            previous_y = (mult_y - 1) * self.size + y

                        value = new_grid[previous_x, previous_y] + 1

                        if value > 9:
                            value = 1

                        new_grid[mult_x * self.size + x,
                                 mult_y * self.size + y] = value
                print()

        new_end = Coordinate(x=new_grid.max_x,
                             y=new_grid.max_y,
                             system=CoordinateSystem.X_RIGHT_Y_DOWN)
        path = self.find_path(new_grid, self.start, new_end)
        result = sum(new_grid[x] for x in path if x != self.start)

        print("Part 2:", result)
Ejemplo n.º 11
0
    def part2(self):
        grid: InfiniteGrid[bool] = self.grid.copy()
        cleaner = Turtle(direction=TurtleDirection.NORTH,
                         coordinate=Coordinate(
                             0, 0, system=CoordinateSystem.X_RIGHT_Y_DOWN))
        result = 0

        for _ in range(10_000_000):
            node_type = grid[cleaner.coordinate]
            if node_type is None:
                node_type = NodeType.Clean

            if node_type == NodeType.Clean:
                cleaner = cleaner.turn_left()
                grid[cleaner.coordinate] = NodeType.Weakened
            elif node_type == NodeType.Weakened:
                result += 1
                grid[cleaner.coordinate] = NodeType.Infected
            elif node_type == NodeType.Infected:
                cleaner = cleaner.turn_right()
                grid[cleaner.coordinate] = NodeType.Flagged
            else:
                cleaner = cleaner.turn_left(2)
                grid[cleaner.coordinate] = NodeType.Clean

            cleaner = cleaner.forward()
Ejemplo n.º 12
0
    def _mutate_recursive(cls, grids: Dict[int, Grid[str]]) -> Dict[int, Grid[str]]:
        result: Dict[int, Grid[str]] = {}

        min_level = 2**24
        max_level = -2**24
        for key, grid in grids.items():
            result[key] = grid
            min_level = min(min_level, key)
            max_level = max(max_level, key)

        if len(grids[min_level].find('#')) > 0:
            min_level -= 1
            result[min_level] = grids[0].copy()
            result[min_level].fill('.')

        if len(grids[max_level].find('#')) > 0:
            max_level += 1
            result[max_level] = grids[0].copy()
            result[max_level].fill('.')

        for key, grid in result.items():
            below = grids[key + 1] if key + 1 in grids else None
            above = grids[key - 1] if key - 1 in grids else None
            current = result[key]
            next_grid = result[key].copy()
            for x in range(5):
                for y in range(5):
                    coordinate = Coordinate(x, y, system=CoordinateSystem.X_RIGHT_Y_DOWN)
                    bug_count = cls._get_bug_count(coordinate, current, above, below)
                    is_infected = cls._is_infected(current[coordinate], bug_count)
                    next_grid[coordinate] = '#' if is_infected else '.'
            result[key] = next_grid

        return result
Ejemplo n.º 13
0
    def part1(self):
        end = Coordinate(31, 39, system=CoordinateSystem.X_RIGHT_Y_DOWN)

        path = self.grid.find_path(self.start, end, True)

        result = len(path) - 1

        print("Part 1:", result)
Ejemplo n.º 14
0
    def __init__(self, file_name):
        line = Input(file_name).line()

        start = Coordinate(0, 0)
        grid = self._build_map(start, line)

        walkable = ['X', '.', '|', '-']
        self.flood_map = grid.flood_map(start, *walkable)
Ejemplo n.º 15
0
    def cut(self, bounding_box: BoundingBox) -> Grid[T]:
        new_width = bounding_box.max_x - bounding_box.min_x + 1
        new_height = bounding_box.max_y - bounding_box.min_y + 1
        new_grid = Grid[T](new_width, new_height)

        for row in range(new_height):
            for col in range(new_width):
                old_coordinate = Coordinate(
                    col + bounding_box.min_x,
                    row + bounding_box.min_y,
                    system=CoordinateSystem.X_RIGHT_Y_DOWN)
                new_coordinate = Coordinate(
                    col, row, system=CoordinateSystem.X_RIGHT_Y_DOWN)

                new_grid[new_coordinate] = self[old_coordinate]

        return new_grid
Ejemplo n.º 16
0
    def fill(self, item: T):
        for row in range(self.height):
            for col in range(self.width):
                coordinate = Coordinate(col,
                                        row,
                                        system=CoordinateSystem.X_RIGHT_Y_DOWN)

                self._data[coordinate] = item
Ejemplo n.º 17
0
    def part1(self):
        self._map_out_grid()

        start = Coordinate(0, 0)
        oxygen = self.grid.find(Tile.OXYGEN)[0]
        graph = self.grid.to_graph(Tile.EMPTY, Tile.OXYGEN)
        # -1 is because path includes start and they want "steps to oxygen"
        result = len(graph.find_path(start, oxygen, CoordinateHeuristic())) - 1

        print("Part 1:", result)
Ejemplo n.º 18
0
    def part1(self):
        excluded_coordinates: Set[Coordinate] = set()

        for x in range(self.bounding_box.min_x, self.bounding_box.max_x + 1):
            coordinate = Coordinate(x,
                                    self.bounding_box.min_y,
                                    system=CoordinateSystem.X_RIGHT_Y_DOWN)
            value = self.grid[coordinate]
            if value is not None:
                excluded_coordinates.add(value)

            coordinate = Coordinate(x,
                                    self.bounding_box.max_y,
                                    system=CoordinateSystem.X_RIGHT_Y_DOWN)
            value = self.grid[coordinate]
            if value is not None:
                excluded_coordinates.add(value)

        for y in range(self.bounding_box.min_y, self.bounding_box.max_y + 1):
            coordinate = Coordinate(self.bounding_box.min_x,
                                    y,
                                    system=CoordinateSystem.X_RIGHT_Y_DOWN)
            value = self.grid[coordinate]
            if value is not None:
                excluded_coordinates.add(value)

            coordinate = Coordinate(self.bounding_box.max_x,
                                    y,
                                    system=CoordinateSystem.X_RIGHT_Y_DOWN)
            value = self.grid[coordinate]
            if value is not None:
                excluded_coordinates.add(value)

        result = 0

        for coordinate in self.coordinates:
            if coordinate in excluded_coordinates:
                continue

            result = max(result, len(self.grid.find(coordinate)))

        print("Part 1:", result)
Ejemplo n.º 19
0
 def _to_coordinate(position) -> Coordinate:
     """
     We make the assumption that the grid is x right y down if they're just passing in coordinates. However, the
     user can use any coordinate system they want in an infinite grid.
     """
     if isinstance(position, tuple):
         x, y = position
         position = Coordinate(int(x),
                               int(y),
                               system=CoordinateSystem.X_RIGHT_Y_DOWN)
     return position
Ejemplo n.º 20
0
    def part1(self):
        grid = InfiniteGrid[bool]()

        santa = Coordinate(0, 0)
        grid[santa] = True
        for character in self.path:
            santa = santa.move(character)
            grid[santa] = True

        result = len(grid.find(True))

        print("Part 1:", result)
Ejemplo n.º 21
0
    def _get_keycode(self, keypad: Grid[str]):
        coordinate = Coordinate(1, 1, system=CoordinateSystem.X_RIGHT_Y_DOWN)
        result = ""

        for line in self.lines:
            for character in line:
                new_coordinate = coordinate.move(character)
                if keypad[new_coordinate] is not None:
                    coordinate = new_coordinate
            result += keypad[coordinate]

        return result
Ejemplo n.º 22
0
    def _mutate(grid: Grid[str], moving: Moving) -> (bool, Grid[str]):
        result = grid.copy()
        max_x = grid.max_x
        max_y = grid.max_y

        locations = grid.find('>') if moving == Moving.EAST else grid.find('v')
        something_changed = False

        for location in locations:
            test = location.right(
            ) if moving == Moving.EAST else location.down()
            if test.x > max_x:
                test = Coordinate(0, test.y, system=test.system)
            if test.y > max_y:
                test = Coordinate(test.x, 0, system=test.system)

            if grid[test] == '.':
                result[test] = '>' if moving == Moving.EAST else 'v'
                result[location] = '.'
                something_changed = True

        return something_changed, result
Ejemplo n.º 23
0
    def part2(self):
        result = 0

        for x, y in self.bounding_box:
            coordinate = Coordinate(x,
                                    y,
                                    system=CoordinateSystem.X_RIGHT_Y_DOWN)
            total_distance = sum(
                coordinate.manhattan(i) for i in self.coordinates)

            if total_distance < 10000:
                result += 1

        print("Part 2:", result)
Ejemplo n.º 24
0
    def _to_coordinates(line_string):
        line = line_string.split(',')
        current: Coordinate = Coordinate(0, 0)
        coordinates = []

        for element in line:
            direction = element[0]
            count = int(element[1:])

            for _ in range(count):
                current = current.move(direction)
                coordinates.append(current)

        return coordinates
Ejemplo n.º 25
0
    def __init__(self, file_name):
        self.favorite_number = Input(file_name).int()

        def _grid(_: MagicGrid[bool], coordinate: Coordinate) -> bool:
            x = coordinate.x
            y = coordinate.y

            if x < 0 or y < 0:
                return False

            result = x * x + 3 * x + 2 * x * y + y + y * y
            result += self.favorite_number

            return bin(result).count("1") % 2 == 0

        self.grid = MagicGrid[bool](_grid)
        self.start = Coordinate(1, 1, system=CoordinateSystem.X_RIGHT_Y_DOWN)
Ejemplo n.º 26
0
    def part1(self):
        grid: InfiniteGrid[bool] = self.grid.copy()
        cleaner = Turtle(direction=TurtleDirection.NORTH,
                         coordinate=Coordinate(
                             0, 0, system=CoordinateSystem.X_RIGHT_Y_DOWN))
        result = 0

        for _ in range(10_000):
            clean = grid[cleaner.coordinate] in [None, NodeType.Clean]

            cleaner = cleaner.turn_left() if clean else cleaner.turn_right()
            grid[cleaner.
                 coordinate] = NodeType.Infected if clean else NodeType.Clean

            result += 1 if clean else 0

            cleaner = cleaner.forward()
Ejemplo n.º 27
0
    def part2(self):
        grid = InfiniteGrid[bool]()

        santa = Coordinate(0, 0)
        robot_santa = santa
        grid[santa] = True
        for index, character in enumerate(self.path):
            if index % 2 == 0:
                santa = santa.move(character)
                grid[santa] = True
            else:
                robot_santa = robot_santa.move(character)
                grid[robot_santa] = True

        result = len(grid.find(True))

        print("Part 2:", result)
Ejemplo n.º 28
0
    def print(self, key=None, not_found=' '):
        if key is None:

            def key(item):
                return item[0]

        for row in range(self.height):
            line = ""
            for col in range(self.width):
                coordinate = Coordinate(col,
                                        row,
                                        system=CoordinateSystem.X_RIGHT_Y_DOWN)

                if coordinate in self._data:
                    line += key(self._data[coordinate])
                else:
                    line += not_found

            print(line)
Ejemplo n.º 29
0
    def _map_out_grid(self):
        self.grid.clear()
        self.droid.reset()

        all_directions = [
            TurtleDirection.NORTH, TurtleDirection.SOUTH, TurtleDirection.EAST,
            TurtleDirection.WEST
        ]

        droid_coordinate = Coordinate(0, 0)
        self.grid[droid_coordinate] = Tile.EMPTY
        backtrack = []
        need_to_check = {droid_coordinate: all_directions.copy()}

        self.droid.run()
        while backtrack or need_to_check:
            test_directions = need_to_check[droid_coordinate]

            # Nowhere to move, try to go back
            if not test_directions:
                del need_to_check[droid_coordinate]
                if backtrack:
                    back = backtrack.pop()
                    self.droid.input(self._get_move_input(back))
                    self.droid.output(
                    )  # Read an output, although we know we can move back
                    droid_coordinate = back.move(droid_coordinate)
                continue

            direction = test_directions.pop()
            if backtrack and direction == backtrack[-1]:
                continue  # Don't go back, use backtrack stack for that

            self.droid.input(self._get_move_input(direction))
            output = self.droid.output()

            next_coordinate = direction.move(droid_coordinate)
            self.grid[next_coordinate] = Tile.from_id(output)
            if output != 0:
                droid_coordinate = next_coordinate
                backtrack.append(direction.opposite())
                need_to_check[droid_coordinate] = all_directions.copy()
Ejemplo n.º 30
0
    def __init__(self, file_name):
        lines = Input(file_name).lines()
        self.stars = []

        for line in lines:
            matched = re.match(
                r"position=<\s*(-?\d+),\s*(-?\d+)> velocity=<\s*(-?\d+),\s*(-?\d+)>",
                line)
            self.stars.append(
                Star(coordinate=Coordinate(
                    int(matched.group(1)),
                    int(matched.group(2)),
                    system=CoordinateSystem.X_RIGHT_Y_DOWN),
                     velocity=(int(matched.group(3)), int(matched.group(4)))))

        min_time = 2**24
        max_time = 0

        for star in self.stars:
            if star.velocity[0] != 0:
                time_x = abs(star.coordinate.x // star.velocity[0])
                min_time = min(min_time, time_x)
                max_time = max(max_time, time_x)

            if star.velocity[1] != 0:
                time_y = abs(star.coordinate.y // star.velocity[1])
                min_time = min(min_time, time_y)
                max_time = max(max_time, time_y)

        min_size = 2**24
        self.best_time = 0
        for time in range(min_time, max_time + 1):
            bounding_box = BoundingBox().expand(
                *[star.at(time) for star in self.stars])
            size = abs(bounding_box.max_y -
                       bounding_box.min_y) * abs(bounding_box.max_x -
                                                 bounding_box.min_x)

            if size < min_size:
                min_size = size
                self.best_time = time