···11+import fileinput
22+from utils import Point, N, S, E, W
33+44+55+DIRS = [S, E, N, W]
66+77+def simulate(board, start, obs=None):
88+ BOARD[obs] = '#'
99+ p = start
1010+ d = 0
1111+ seen = set()
1212+1313+ while True:
1414+ if (p, d) in seen:
1515+ # Loop detected.
1616+ BOARD[obs] = '.'
1717+ return None
1818+1919+ seen.add((p, d))
2020+2121+ np = p + DIRS[d]
2222+ if BOARD.get(np) == '#':
2323+ d = (d + 1) % 4
2424+ elif BOARD.get(np) is None:
2525+ break
2626+ else:
2727+ p += DIRS[d]
2828+2929+ BOARD[obs] = '.'
3030+ return set(p for p, _ in seen)
3131+3232+# Read problem input.
3333+BOARD = {}
3434+for y, line in enumerate(fileinput.input()):
3535+ for x, c in enumerate(line.strip()):
3636+ p = Point(x, y)
3737+ BOARD[p] = c
3838+ if c == '^':
3939+ START = p
4040+4141+# Solve part 1.
4242+seen = simulate(BOARD, START)
4343+print("Part 1:", len(seen))
4444+4545+# Solve part 2.
4646+candidates = set()
4747+for x in seen:
4848+ for n in x.neighbours():
4949+ candidates.add(n)
5050+5151+part_2 = 0
5252+for p in candidates:
5353+ if p not in seen or BOARD.get(p) != '.':
5454+ continue
5555+ if simulate(BOARD, START, obs=p) is None:
5656+ part_2 += 1
5757+5858+print("Part 2:", part_2)
5959+