Day 20
This commit is contained in:
parent
9b286f12ad
commit
b1108e98a4
109
2019/day20.py
Normal file
109
2019/day20.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
from collections import defaultdict
|
||||
from string import ascii_uppercase
|
||||
|
||||
from grid import Grid
|
||||
|
||||
|
||||
def main():
|
||||
with open("day20.txt") as input_file:
|
||||
lines = [l.rstrip() for l in input_file.readlines()]
|
||||
maze = Maze(lines, value_factory=str)
|
||||
|
||||
maze.find_portals()
|
||||
maze.dumb_print()
|
||||
min_steps = maze.count_min_steps(maze.entry, maze.exit)
|
||||
print("Min steps:", min_steps)
|
||||
|
||||
|
||||
class Maze(Grid):
|
||||
|
||||
TILE_PATH = "."
|
||||
TILE_WALL = "#"
|
||||
|
||||
ENTRY_DG = "AA"
|
||||
EXIT_DG = "ZZ"
|
||||
|
||||
def __init__(self, lines, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.entry = None
|
||||
self.exit = None
|
||||
self.portals = {}
|
||||
for y, line in enumerate(lines):
|
||||
for x, value in enumerate(line):
|
||||
self.setv(x, y, value)
|
||||
|
||||
def find_portals(self):
|
||||
portals = defaultdict(list)
|
||||
for x, y, v in list(self.values_gen()):
|
||||
if v in ascii_uppercase:
|
||||
rv = self.getv(x + 1, y)
|
||||
if rv and rv in ascii_uppercase:
|
||||
rrv = self.getv(x + 2, y)
|
||||
portal_pos = (x + 2, y) if rrv == Maze.TILE_PATH else (x - 1, y)
|
||||
portals[v + rv].append(portal_pos)
|
||||
continue
|
||||
dv = self.getv(x, y + 1)
|
||||
if dv and dv in ascii_uppercase:
|
||||
ddv = self.getv(x, y + 2)
|
||||
portal_pos = (x, y + 2) if ddv == Maze.TILE_PATH else (x, y - 1)
|
||||
portals[v + dv].append(portal_pos)
|
||||
for n, p in portals.items():
|
||||
if n == "AA":
|
||||
self.entry = p[0]
|
||||
elif n == "ZZ":
|
||||
self.exit = p[0]
|
||||
else:
|
||||
self.portals[p[0]] = p[1]
|
||||
self.portals[p[1]] = p[0]
|
||||
|
||||
def dumb_print(self):
|
||||
for y, row in self.g.items():
|
||||
for x, v in row.items():
|
||||
if (x, y) in self.portals:
|
||||
v = "░"
|
||||
if (x, y) in (self.entry, self.exit):
|
||||
v = "▒"
|
||||
print(v, end="")
|
||||
print()
|
||||
|
||||
def bfs(self, start, end):
|
||||
discovered = Grid(value_factory=bool)
|
||||
discovered.setv(start[0], end[1], True)
|
||||
parents = {}
|
||||
q = [start]
|
||||
while q:
|
||||
pos = q.pop(0)
|
||||
if pos == end:
|
||||
return parents
|
||||
nears = self.near_objects(pos)
|
||||
for near_pos, near_tile in nears.items():
|
||||
if near_tile != Maze.TILE_PATH:
|
||||
continue
|
||||
if discovered.getv(near_pos[0], near_pos[1]):
|
||||
continue
|
||||
discovered.setv(near_pos[0], near_pos[1], True)
|
||||
parents[near_pos] = pos
|
||||
q.append(near_pos)
|
||||
return None
|
||||
|
||||
def count_min_steps(self, start, end):
|
||||
parents = self.bfs(start, end)
|
||||
count = 0
|
||||
pos = end
|
||||
while pos != start:
|
||||
pos = parents[pos]
|
||||
count += 1
|
||||
if pos in self.portals: # One more step to traverse portal.
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def near_objects(self, p):
|
||||
nears = super().near_objects(p)
|
||||
for near in list(nears):
|
||||
if near in self.portals:
|
||||
nears[self.portals[near]] = Maze.TILE_PATH
|
||||
return nears
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue