| @@ -0,0 +1,219 @@ | |||
| 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() | |||