|
|
|
|
|
|
|
|
|
|
|
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() |