108 lines
2.6 KiB
Python
108 lines
2.6 KiB
Python
#!/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()
|