···11+import fileinput
22+from collections import deque
33+44+from utils import Point, N, E, S, W
55+66+77+def search(graph, start, goal):
88+ def gen_neighbours(node, depth):
99+ """Returns all valid neighbours for a given node."""
1010+ for n in node.neighbours_4():
1111+ if graph.get(n, ' ') == '.':
1212+ if n not in get_blizzard_locs(depth + 1):
1313+ yield n
1414+1515+ if node not in get_blizzard_locs(depth + 1):
1616+ yield node
1717+1818+ part_1 = None
1919+ part_2 = None
2020+2121+ horizon = deque([(0, 0, start)]) # priority, checkpoint, node
2222+ seen = set()
2323+2424+ while horizon:
2525+ depth, checkpoint, curr = horizon.popleft()
2626+2727+ if (depth, checkpoint, curr) in seen:
2828+ continue
2929+3030+ seen.add((depth, checkpoint, curr))
3131+3232+ # Reached the goal for the second time after making it back to the start.
3333+ if curr == goal and checkpoint == 2:
3434+ part_2 = depth
3535+ print("Part 2:", part_2)
3636+ return part_1, part_2
3737+3838+ # Reached the goal for the first time.
3939+ elif curr == goal and checkpoint == 0:
4040+ if part_1 is None:
4141+ part_1 = depth
4242+ print("Part 1:", part_1)
4343+ checkpoint = 1
4444+4545+ # Reached the start after touching the goal the first time.
4646+ elif curr == start and checkpoint == 1:
4747+ checkpoint = 2
4848+4949+ for n in gen_neighbours(curr, depth):
5050+ horizon.append((depth + 1, checkpoint, n))
5151+5252+5353+# N and S are swapped because of positive-Y direction convention.
5454+MAPPING = {
5555+ 'v': N,
5656+ '>': E,
5757+ '^': S,
5858+ '<': W,
5959+}
6060+6161+BLIZZARDS = {}
6262+BLIZZARD_LOCS = {}
6363+6464+def get_blizzard_locs(step):
6565+ """Returns the set of all blizzard positions for the given time step."""
6666+ if step in BLIZZARDS:
6767+ return BLIZZARD_LOCS[step]
6868+6969+ last_blizzards = BLIZZARDS[step - 1]
7070+ new_blizzards = []
7171+ for p, b in last_blizzards:
7272+ np = p + MAPPING[b]
7373+ if BOARD.get(np) == '#':
7474+ np = p
7575+ while BOARD.get(np) != '#':
7676+ np -= MAPPING[b]
7777+ np += MAPPING[b]
7878+7979+ new_blizzards.append((np, b))
8080+8181+ BLIZZARDS[step] = new_blizzards
8282+ BLIZZARD_LOCS[step] = set(p for p, _ in new_blizzards)
8383+ return BLIZZARD_LOCS[step]
8484+8585+8686+# Read problem input.
8787+BOARD = {}
8888+BLIZZARDS = {0: []}
8989+START = None
9090+GOAL = None
9191+for y, line in enumerate(fileinput.input()):
9292+ for x, c in enumerate(line.strip()):
9393+ p = Point(x, y)
9494+ if c == '.':
9595+ if y == 0:
9696+ START = p
9797+ GOAL = p
9898+ BOARD[p] = '.'
9999+ elif c in MAPPING:
100100+ BLIZZARDS[0].append((p, c))
101101+ BOARD[p] = '.'
102102+ else:
103103+ BOARD[p] = '#'
104104+105105+# Solve problem.
106106+search(BOARD, START, GOAL)