148 lines
3.4 KiB
Python
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)))
|