#!/usr/bin/env python # -*- encoding: utf-8; py-indent-offset: 4 -*- # Author: Sebastian Mark # CC-BY-SA (https://creativecommons.org/licenses/by-sa/4.0/deed.de) # pylint: disable=missing-module-docstring,missing-function-docstring,consider-using-f-string from copy import deepcopy def readinput() -> str: with open("input", "r", encoding="utf-8") as file: lines = [list(line.strip()) for line in file] return lines def find_guard(maze: list) -> tuple: for idx, row in enumerate(maze): try: x = row.index("^") y = idx except ValueError: pass else: return (x, y) return tuple(-1, -1) def move_guard(maze: list, guard_pos: tuple) -> tuple[int, bool, dict]: width = len(maze[0]) height = len(maze) path_history = {} # remember the start position as visited visited = set() visited.add(guard_pos) # start upwards direction = (0, -1) while True: # get next position nxt = ( guard_pos[0] + direction[0], guard_pos[1] + direction[1], ) # check borders if not (0 <= nxt[0] < width and 0 <= nxt[1] < height): visited.add(guard_pos) break # check for obstacle and rotate clockwise if maze[nxt[1]][nxt[0]] == "#": direction = (-direction[1], direction[0]) continue # loop detection # (has the guard been here and walking in the same direction?) if nxt not in path_history: path_history[nxt] = (guard_pos, direction) elif path_history[nxt] == (guard_pos, direction): return 0, True, {} # mark spot as visited and move guard visited.add(guard_pos) guard_pos = nxt return len(visited), False, path_history def print_maze(maze: list): for row in maze: print("".join(row)) print("-") def main(): # part 1 maze = readinput() guard_pos = find_guard(maze) count, _, guard_path = move_guard(maze, guard_pos) print("Guard positions: %d" % count) # part 2 count = 0 maze = readinput() guard_pos = find_guard(maze) # place an obstacle at every position in the guards path # then check new maze for loop for col, row in guard_path: if (col, row) == guard_pos: continue new_maze = deepcopy(maze) new_maze[row][col] = "#" _, loop, _ = move_guard(new_maze, guard_pos) if loop: count += 1 print("Loop options: %d" % count) if __name__ == "__main__": main()