diff --git a/2022/d22/ex1/ex1.py b/2022/d22/ex1/ex1.py new file mode 100755 index 0000000..d66184e --- /dev/null +++ b/2022/d22/ex1/ex1.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python + +import enum +import itertools +import sys +from collections.abc import Iterable, Iterator +from typing import NamedTuple, TypeVar, Union + +T = TypeVar("T") + + +def take(n: int, iterable: Iterable[T]) -> Iterator[T]: + return itertools.islice(iterable, n) + + +class Point(NamedTuple): + x: int + y: int + + def __add__(self, other): + if not isinstance(other, Point): + return NotImplemented + return Point(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + if not isinstance(other, Point): + return NotImplemented + return Point(self.x - other.x, self.y - other.y) + + +class Tile(str, enum.Enum): + AIR = "." + WALL = "#" + + +class Direction(enum.IntEnum): + EAST = 0 + SOUTH = 1 + WEST = 2 + NORTH = 3 + + def turn(self, rot: "Rotation") -> "Direction": + if rot == Rotation.LEFT: + return Direction((self - 1 + 4) % 4) + if rot == Rotation.RIGHT: + return Direction((self + 1) % 4) + assert False # Sanity check + + def to_delta(self) -> Point: + match self: + case Direction.NORTH: + return Point(-1, 0) + case Direction.SOUTH: + return Point(1, 0) + case Direction.EAST: + return Point(0, 1) + case Direction.WEST: + return Point(0, -1) + + +class Rotation(str, enum.Enum): + LEFT = "L" + RIGHT = "R" + + +Map = dict[Point, Tile] + + +def solve(input: list[str]) -> int: + def parse_map(input: list[str]) -> tuple[Point, Map]: + res: Map = {} + + for i, line in enumerate(input, start=1): + for j, c in enumerate(line, start=1): + if c == " ": + continue + res[Point(i, j)] = Tile(c) + + return min(p for p in res.keys()), res + + def parse_instruction(input: str) -> list[Union[Rotation, int]]: + res: list[Union[Rotation, int]] = [] + i = 0 + while i < len(input): + # Parse direction + if input[i] in list(Rotation): + res.append(Rotation(input[i])) + i += 1 + continue + # Parse int + j = i + 1 + while j < len(input) and input[j] not in list(Rotation): + j += 1 + res.append(int(input[i:j])) + i = j + + return res + + def points_along(start: Point, map: Map, dir: Direction) -> Iterator[Point]: + delta = dir.to_delta() + while True: + start = start + delta + # Wrap around if about to go out-of-bounds + if start not in map: + while (new_start := start - delta) in map: + start = new_start + yield start + + assert input[-2] == "" # Sanity check + + facing = Direction.EAST + start, map = parse_map(input[:-2]) + instructions = parse_instruction(input[-1]) + + for instr in instructions: + if isinstance(instr, Rotation): + facing = facing.turn(instr) + continue + for p in take(instr, points_along(start, map, facing)): + if map[p] == Tile.WALL: + break + start = p + + return 1000 * start.x + 4 * start.y + facing + + +def main() -> None: + input = sys.stdin.read().splitlines() + print(solve(input)) + + +if __name__ == "__main__": + main()