diff --git a/2016/d17/ex1/ex1.py b/2016/d17/ex1/ex1.py new file mode 100755 index 0000000..d32788d --- /dev/null +++ b/2016/d17/ex1/ex1.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +import enum +import functools +import hashlib +import sys +from collections.abc import Callable, Iterator +from typing import NamedTuple + + +class Point(NamedTuple): + x: int + y: int + + +# Iteration order of `Direction` is relevant to the solution +class Direction(enum.StrEnum): + UP = "U" + DOWN = "D" + LEFT = "L" + RIGHT = "R" + + def apply(self, p: Point) -> Point: + match self: + case Direction.UP: + dx, dy = -1, 0 + case Direction.DOWN: + dx, dy = 1, 0 + case Direction.LEFT: + dx, dy = 0, -1 + case Direction.RIGHT: + dx, dy = 0, 1 + return Point(p.x + dx, p.y + dy) + + +Path = tuple[Direction, ...] + + +def solve(input: str) -> str: + def doors_at_passcode(path: Path, passcode: str) -> Iterator[Direction]: + hashed = hashlib.md5((passcode + "".join(path)).encode()).hexdigest()[:4] + for c, dir in zip(hashed, Direction): + if c.isdigit() or c == "a": + continue + yield Direction(dir) # Satisfy Mypy to use the correct return type... + + def bfs( + start: Point, + end: Point, + doors_at: Callable[[Path], Iterator[Direction]], + ) -> Path: + queue: list[tuple[Point, Path]] = [(start, ())] + + while queue: + new_queue: list[tuple[Point, Path]] = [] + for p, path in queue: + if p == end: + return path + for dir in doors_at(path): + new_pos = dir.apply(p) + if new_pos.x < 0 or new_pos.x > end.x: + continue + if new_pos.y < 0 or new_pos.y > end.y: + continue + new_queue.append((dir.apply(p), path + (dir,))) + queue = new_queue + + assert False # Sanity check + + passcode = input.strip() + doors_at = functools.partial(doors_at_passcode, passcode=passcode) + return "".join(bfs(Point(0, 0), Point(3, 3), doors_at)) + + +def main() -> None: + input = sys.stdin.read() + print(solve(input)) + + +if __name__ == "__main__": + main()