Files
adventofcode/2024/06/part1.py

148 lines
3.4 KiB
Python

import numpy as np
from enum import Enum
verbose = False
class Map:
directions = ["^", ">", "v", "<"]
#step_per_direction = [(0, -1), (1, 0), (0, 1), (-1, 0)]
step_per_direction = [(-1, 0), (0, 1), (1, 0), (0, -1)]
class Tiles(Enum):
BORDER = "+"
FREE = "."
OBSTACLE = "#"
def __init__(self):
self.trace = []
self.pos = None
self.direction = None
def next_pos(self):
"""
Returns next position given the state of this Map.
"""
next_pos = tuple([self.pos[i] + self.step_per_direction[self.directions.index(self.direction)][i] for i in range(len(self.pos))])
if verbose:
print("next_pos", next_pos)
return next_pos
def pos_in_map(self, pos):
"""
Returns whether pos is in the map.
"""
if verbose:
print("pos_in_map", pos)
return pos[0] >= 0 and pos[0] < self.map.shape[0] \
and pos[1] >= 0 and pos[0] < self.map.shape[1]
def look_ahead(self):
next_step = self.next_pos()
if verbose:
print("look_ahead.next_step", next_step)
if not self.pos_in_map(next_step):
return self.Tiles.BORDER
if self.map[next_step] == "#":
return self.Tiles.OBSTACLE
if self.map[next_step] == ".":
return self.Tiles.FREE
def next_direction(self, direction: str):
"""
Return the direction turning right given direction.
"""
assert direction in self.directions, "Invalid direction given"
if self.directions[-1] == direction:
return self.directions[0]
else:
return self.directions[self.directions.index(direction) + 1]
def seek_guard_in_map(self):
"""
Looks for the guard in the map and returns its position.
Will raise an exception if multiple or none are found.
"""
guard_locations = []
for direction in self.directions:
guard_locations.extend(list(zip(*np.where(self.map == direction))))
return guard_locations
def load_map(self, filename="input"):
"""
Reads a map from a file without verifying its contents.
"""
with open(filename, "r") as fp:
data = fp.read().splitlines()
chararray = np.array(data, dtype=str)\
.view("U1")\
.reshape((len(data), -1))
self.map = chararray
# Find the location of the guard in the map.
guards = self.seek_guard_in_map()
if verbose:
print(guards)
assert len(guards) == 1, "There should only be one guard in the map."
self.pos = guards[0]
self.trace.append(self.pos)
self.direction = self.map[self.pos]
# TODO: Use enum.
#self.map[self.pos] = self.Tiles.FREE
self.map[self.pos] = "."
def step(self):
"""
Take a step, turn or stop.
"""
next_tile = self.look_ahead()
if verbose:
print("step.next_tile", next_tile)
if next_tile == self.Tiles.FREE:
# TODO: Really take a step! Do not only turn.
self.pos = self.next_pos()
self.trace.append(self.pos)
return True
elif next_tile == self.Tiles.OBSTACLE:
self.direction = self.next_direction(self.direction)
return True
elif next_tile == self.Tiles.BORDER:
return False
assert False, "Should not come here :')"
def show(self):
"""Prints the current map to stdout."""
for i in range(len(self.map)):
for j in range(len(self.map[0])):
if self.pos == (i, j):
print(self.direction, end="")
else:
print(self.map[i][j], end="")
print()
if __name__ == "__main__":
m = Map()
#m.load_map("testinput")
m.load_map("input")
while m.step():
if verbose:
print(m.pos, m.direction)
#m.show()
#print(len(m.trace), m.trace)
print(len(set(m.trace)))