| import argparse | |||||
| import enum | |||||
| import logging | |||||
| from sys import stdout | |||||
| from typing import List, Tuple | |||||
| logger = logging.Logger(__name__) | |||||
| logger_2 = logging.Logger(f"{__name__}_2") | |||||
| formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s') | |||||
| sh = logging.StreamHandler(stdout) | |||||
| sh.setLevel(logging.INFO) | |||||
| sh.setFormatter(formatter) | |||||
| fh = logging.FileHandler("./day02-2.log", mode="w", encoding="utf-8") | |||||
| fh_2 = logging.FileHandler("./day02-2_round2.log", mode="w", encoding="utf-8") | |||||
| fh.setLevel(logging.DEBUG) | |||||
| fh.setFormatter(formatter) | |||||
| fh_2.setLevel(logging.DEBUG) | |||||
| fh_2.setFormatter(formatter) | |||||
| logger.addHandler(sh) | |||||
| logger.addHandler(fh) | |||||
| logger_2.addHandler(fh_2) | |||||
| class Direction(enum.Enum): | |||||
| UP = 0 | |||||
| RIGHT = 1 | |||||
| DOWN = 2 | |||||
| LEFT = 3 | |||||
| class Guard: | |||||
| def __init__(self, grid: List[List[str]], initial_x: int = None, initial_y: int = None, initial_dir: int = None, test_mode: bool = False) -> object: | |||||
| self.grid = grid | |||||
| self.height = len(self.grid) | |||||
| self.width = len(self.grid[0]) | |||||
| logger.info(f"Received a grid with {self.height} lines and {self.width} characters per line.") | |||||
| self.initial_x = initial_x | |||||
| self.x = self.initial_x | |||||
| self.initial_y = initial_y | |||||
| self.y = self.initial_y | |||||
| self.initial_dir = initial_dir | |||||
| self.dir = self.initial_dir | |||||
| self.test_mode = test_mode | |||||
| self.visited = set() | |||||
| self.traveled = 1 | |||||
| def whereami(self) -> Tuple[int,int]: | |||||
| # Identify the guard's initial position and direction in the grid | |||||
| breakout = False | |||||
| x,y,dir = None,None,None | |||||
| for j, line in enumerate(self.grid): | |||||
| if breakout: | |||||
| break | |||||
| for i, char in enumerate(line): | |||||
| if char in ["^",">","<","v"]: | |||||
| x, y = i, j | |||||
| match char: | |||||
| case "^": | |||||
| dir = Direction.UP | |||||
| case ">": | |||||
| dir = Direction.RIGHT | |||||
| case "v": | |||||
| dir = Direction.DOWN | |||||
| case "<": | |||||
| dir = Direction.LEFT | |||||
| case "_": | |||||
| raise ValueError(f"char must be one of '^','>','v','<', received {char}") | |||||
| breakout = True | |||||
| break | |||||
| self.initial_x = x | |||||
| self.x = self.initial_x | |||||
| self.initial_y = y | |||||
| self.y = self.initial_y | |||||
| self.initial_dir = dir | |||||
| self.dir = self.initial_dir | |||||
| return (x,y) | |||||
| def proceed(self): | |||||
| if self.dir is None: | |||||
| logger.error("You can't move until you have a direction set.") | |||||
| raise ValueError | |||||
| logger.info(f"Proceeding {self.dir}") | |||||
| match self.dir: | |||||
| case Direction.UP: | |||||
| return self.go_north() | |||||
| case Direction.RIGHT: | |||||
| return self.go_east() | |||||
| case Direction.DOWN: | |||||
| return self.go_south() | |||||
| case Direction.LEFT: | |||||
| return self.go_west() | |||||
| case _: | |||||
| logger.error(f"Unknown direction! {self.dir}") | |||||
| raise ValueError | |||||
| def go_north(self) -> bool: | |||||
| path = [self.grid[y][self.x] for y in range(self.y-1, -1, -1)] | |||||
| logger.info(f"Path created from {self.x},{self.y} to {self.x},{0}: {path}.") | |||||
| exited = "#" not in path | |||||
| if exited: | |||||
| blocked = 999999 | |||||
| else: | |||||
| blocked = path.index("#") | |||||
| if self.test_mode: | |||||
| logger.info(f"Found a blocker at ({self.x}, {self.y-blocked-1}).") | |||||
| for i, char in enumerate(path[:blocked+1]): | |||||
| j = i + 1 if exited else i | |||||
| if self.test_mode: | |||||
| logger.info(f"Walked to ({self.x}, {self.y-j}).") | |||||
| self.visited.add((self.x, self.y-j)) | |||||
| self.traveled += 1 | |||||
| new_y = self.y - (blocked) | |||||
| self.y = new_y | |||||
| self.dir = Direction.RIGHT | |||||
| return exited | |||||
| def go_east(self) -> bool: | |||||
| path = [self.grid[self.y][x] for x in range(self.x+1, self.width)] | |||||
| logger.info(f"Path created from {self.x},{self.y} to {self.width-1},{self.y}: {path}.") | |||||
| exited = "#" not in path | |||||
| if exited: | |||||
| blocked = 999999 | |||||
| else: | |||||
| blocked = path.index("#") | |||||
| if self.test_mode: | |||||
| logger.info(f"Found a blocker at ({self.x+blocked+1}, {self.y}).") | |||||
| for i, char in enumerate(path[:blocked+1]): | |||||
| j = i + 1 if exited else i | |||||
| if self.test_mode: | |||||
| logger.info(f"Walked to ({self.x+j}, {self.y}).") | |||||
| self.visited.add((self.x+j, self.y)) | |||||
| self.traveled += 1 | |||||
| new_x = self.x + (blocked) | |||||
| self.x = new_x | |||||
| self.dir = Direction.DOWN | |||||
| return exited | |||||
| def go_south(self) -> bool: | |||||
| path = [self.grid[y][self.x] for y in range(self.y+1, self.height)] | |||||
| logger.info(f"Path created from {self.x},{self.y} to {self.x},{self.height}: {path}.") | |||||
| exited = "#" not in path | |||||
| if exited: | |||||
| blocked = 999999 | |||||
| else: | |||||
| blocked = path.index("#") | |||||
| if self.test_mode: | |||||
| logger.info(f"Found a blocker at ({self.x}, {self.y+blocked+1}).") | |||||
| for i, char in enumerate(path[:blocked+1]): | |||||
| j = i + 1 if exited else i | |||||
| if self.test_mode: | |||||
| logger.info(f"Walked to ({self.x}, {self.y+j}).") | |||||
| self.visited.add((self.x, self.y+j)) | |||||
| self.traveled += 1 | |||||
| new_y = self.y + (blocked) | |||||
| self.y = new_y | |||||
| self.dir = Direction.LEFT | |||||
| return exited | |||||
| def go_west(self) -> bool: | |||||
| path = [self.grid[self.y][x] for x in range(self.x-1, -1, -1)] | |||||
| logger.info(f"Path created from {self.x},{self.y} to {0},{self.y}: {path}.") | |||||
| exited = "#" not in path | |||||
| if exited: | |||||
| blocked = 999999 | |||||
| else: | |||||
| blocked = path.index("#") | |||||
| if self.test_mode: | |||||
| logger.info(f"Found a blocker at ({self.x-blocked-1}, {self.y}).") | |||||
| for i, char in enumerate(path[:blocked+1]): | |||||
| j = i + 1 if exited else i | |||||
| if self.test_mode: | |||||
| logger.info(f"Walked to ({self.x-j}, {self.y}).") | |||||
| self.visited.add((self.x-j, self.y)) | |||||
| self.traveled += 1 | |||||
| new_x = self.x - (blocked) | |||||
| self.x = new_x | |||||
| self.dir = Direction.UP | |||||
| return exited | |||||
| def pathfind(self): | |||||
| exited = False | |||||
| while not exited: | |||||
| exited = self.proceed() | |||||
| logger.info(f"Found an exit after {self.traveled} steps!") | |||||
| parser = argparse.ArgumentParser() | |||||
| parser.add_argument("--test", action="store_true", help="Do a test run instead of the full puzzle") | |||||
| def main061(): | |||||
| args = parser.parse_args() | |||||
| if args.test: | |||||
| input_grid = """....#..... | |||||
| .........# | |||||
| .......... | |||||
| ..#....... | |||||
| .......#.. | |||||
| .......... | |||||
| .#..^..... | |||||
| ........#. | |||||
| #......... | |||||
| ......#...""" | |||||
| grid = [list(l) for l in input_grid.split("\n")] | |||||
| print("\n".join(["".join(line) for line in grid])) | |||||
| else: | |||||
| with open("input06.txt", "r", encoding="utf-8") as f: | |||||
| grid = [list(l) for l in f.readlines()] | |||||
| guard = Guard(grid, test_mode=args.test) | |||||
| logger.info(f"Guard has been created: x:{guard.x}, y:{guard.y}, dir:{guard.dir}, test_mode:{guard.test_mode}, width:{guard.width}, height:{guard.height}.") | |||||
| guard_position = guard.whereami() | |||||
| logger.info(f"Starting guard's walk at {guard_position}.") | |||||
| guard.pathfind() | |||||
| logger.info(f"The guard visited {len(guard.visited)} unique positions.") | |||||
| if __name__ == "__main__": | |||||
| main061() |