···11+import fileinput
22+from string import ascii_uppercase
33+from collections import defaultdict
44+55+from utils import Point, DIRS
66+77+88+MAZE = defaultdict(lambda: '#')
99+PORTALS = defaultdict(set)
1010+PORTAL_MAP = {}
1111+1212+AA = None
1313+ZZ = None
1414+1515+INNERS = set()
1616+OUTERS = set()
1717+1818+MAX_X = 0
1919+MAX_Y = 0
2020+2121+# Read problem input
2222+for y, line in enumerate(fileinput.input()):
2323+ for x, c in enumerate(line[:-1]):
2424+ MAZE[Point(x, y)] = c
2525+ MAX_X = max(MAX_X, x)
2626+ MAX_Y = max(MAX_Y, y)
2727+2828+# Determine the true label name and grid position of portals
2929+for p, c in MAZE.items():
3030+ if c in ascii_uppercase:
3131+ name = ''
3232+ if MAZE[p + Point(-1, 0)] in ascii_uppercase:
3333+ name = MAZE[p + Point(-1, 0)] + c
3434+ op = p + Point(-1, 0)
3535+ elif MAZE[p + Point(1, 0)] in ascii_uppercase:
3636+ name = c + MAZE[p + Point(1, 0)]
3737+ op = p + Point(1, 0)
3838+ elif MAZE[p + Point(0, -1)] in ascii_uppercase:
3939+ name = MAZE[p + Point(0, -1)] + c
4040+ op = p + Point(0, -1)
4141+ elif MAZE[p + Point(0, 1)] in ascii_uppercase:
4242+ name = c + MAZE[p + Point(0, 1)]
4343+ op = p + Point(0, 1)
4444+4545+ for np in p.neighbours_4():
4646+ if MAZE[np] == '.':
4747+ portal_loc = np
4848+ for np in op.neighbours_4():
4949+ if MAZE[np] == '.':
5050+ portal_loc = np
5151+5252+ if name == 'AA':
5353+ AA = portal_loc
5454+ elif name == 'ZZ':
5555+ ZZ = portal_loc
5656+ else:
5757+ PORTALS[name].add(portal_loc)
5858+5959+# Map portal positions and track inner/outer edges
6060+for x in PORTALS:
6161+ a, b = list(PORTALS[x])
6262+ PORTAL_MAP[a] = b
6363+ PORTAL_MAP[b] = a
6464+ if a.x == 2 or a.x == (MAX_X - 2) or a.y == 2 or a.y == (MAX_Y - 2):
6565+ OUTERS.add(a)
6666+ INNERS.add(b)
6767+ else:
6868+ OUTERS.add(b)
6969+ INNERS.add(a)
7070+7171+7272+def bfs(start, end, recursive=False):
7373+ horizon = [(start, 0)]
7474+ seen = set()
7575+ steps = 1
7676+7777+ while horizon:
7878+ new_horizon = []
7979+ for p, level in horizon:
8080+ neighbours = p.neighbours_4()
8181+ if p in PORTAL_MAP:
8282+ if not (p in OUTERS and level == 0) or not recursive:
8383+ neighbours.append(PORTAL_MAP[p])
8484+ for np in neighbours:
8585+ new_level = level
8686+ if recursive:
8787+ if p in INNERS and np in OUTERS:
8888+ new_level += 1
8989+ elif p in OUTERS and np in INNERS and level > 0:
9090+ new_level -= 1
9191+9292+ if (np, new_level) in seen:
9393+ continue
9494+9595+ if MAZE[np] != '.':
9696+ continue
9797+9898+ if np == end and new_level == 0:
9999+ return steps
100100+101101+ seen.add((np, new_level))
102102+ new_horizon.append((np, new_level))
103103+104104+ horizon = new_horizon
105105+ steps += 1
106106+107107+108108+print "Steps in basic maze:", bfs(AA, ZZ)
109109+print "Steps in recursive maze:", bfs(AA, ZZ, recursive=True)