advent-of-code/2024/06/code.py
2025-02-21 10:15:46 +01:00

163 lines
4.5 KiB
Python

from pathlib import Path
from itertools import cycle
import numpy as np
filepath = Path("./data/6_example")
filepath = Path("./data/6_input")
UP = ord("^")
RIGHT = ord(">")
DOWN = ord("v")
LEFT = ord("<")
EMPTY = ord(".")
FULL = ord("#")
WALKED_X = ord("|")
WALKED_Y = ord("-")
WALKED_B = ord("+")
BLOCKED = ord("O")
def eval_pos(state):
for i, line in enumerate(state):
for j, tile in enumerate(line):
if tile in "^v><":
return np.array([i, j]), tile
class surveillance:
dirs = cycle(
[
np.array([-1, 0]),
np.array([0, 1]),
np.array([1, 0]),
np.array([0, -1]),
]
)
def __init__(self, state):
map = [[ord(c) for c in line.replace("^", ".")] for line in state]
self.map = np.array(list(map))
self.history = np.copy(self.map)
self.pos, dir = eval_pos(state)
self.heading = next(self.dirs)
self.next_heading = next(self.dirs)
self.present = True
self.path = list()
self.potential_loops = list()
self.ignore_next = False
def __str__(self):
x, y = self.pos
map = self.history[:]
n, m = map.shape
if (x >= 0 and x < n) and (y >= 0 and y < m):
map[x, y] = self.view
for x, y in self.potential_loops:
map[x, y] = BLOCKED
return "\n".join(["".join([chr(i) for i in line]) for line in map])
def advance(self):
self.update_history()
if self.path_clear():
self.loop_check()
self.path.append(tuple([*self.pos, self.view]))
self.pos += self.heading
else:
self.heading = self.next_heading
self.next_heading = next(self.dirs)
self.ignore_next = True
def update_history(self):
x, y = self.pos
trail = WALKED_X if self.heading[0] else WALKED_Y
self.history[x, y] = trail if self.history[x, y] == EMPTY else WALKED_B
def path_clear(self):
n, m = self.map.shape
u, v = self.pos + self.heading
if (u < 0 or u >= n) or (v < 0 or v >= m):
self.present = False
return True
if self.map[u, v] == FULL:
return False
return True
def loop_check(self):
if self.ignore_next:
self.ignore_next = False
return
x, y = self.pos
u, v = self.next_heading
x_slice = slice(x, None, u) if u else x
y_slice = slice(y, None, v) if v else y
line = self.history[x_slice, y_slice]
try:
first_block = np.argwhere(line == FULL).flatten()[0]
except IndexError:
first_block = len(line)
line = line[:first_block]
walked_2 = WALKED_X if u else WALKED_Y
qualified = [WALKED_B, walked_2]
in_line = any([np.any(line == w) for w in qualified])
if not in_line:
return
idx1 = np.argwhere(line == walked_2).flatten()
idx1 = idx1[0] if len(idx1) else len(line)
idx2 = np.argwhere(line == WALKED_B).flatten()
idx2 = idx2[0] if len(idx2) else len(line)
idx = min(idx1, idx2)
p = self.pos + self.next_heading * idx
if (*p, self.next_view) in self.path:
self.potential_loops.append(self.pos + self.heading)
def _view(self, heading):
view = {
(-1, 0): UP,
(1, 0): DOWN,
(0, 1): RIGHT,
(0, -1): LEFT,
}
return view[tuple(heading)]
@property
def view(self):
return self._view(self.heading)
@property
def next_view(self):
return self._view(self.next_heading)
@property
def walked_tiles(self):
return {tuple([x, y]) for x, y, _ in self.path}
def tick(self, n, plot=True):
for _ in range(n):
if not self.present:
break
self.advance()
if plot:
print(self)
print(len({tuple([x, y]) for x, y, _ in self.path}))
print(len(self.potential_loops))
def walk(self, plot=True):
while self.present:
self.advance()
if plot:
print(self)
print(len({tuple([x, y]) for x, y, _ in self.path}))
print(len(self.potential_loops))
if __name__ == "__main__":
with open(filepath) as filein:
state_0 = [line.rstrip() for line in filein.readlines()]
nppsm_lab = surveillance(state_0)
nppsm_lab.walk(plot=True)
# nppsm_lab.tick(14)