import re import sys from typing import Callable import numpy as np VOID, EMPTY, WALL = 0, 1, 2 TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL} SCORES = {"E": 0, "S": 1, "W": 2, "N": 3} board_map_s, direction_s = sys.stdin.read().split("\n\n") # board board_lines = board_map_s.splitlines() max_line = max(len(line) for line in board_lines) board = np.array( [ [TILE_FROM_CHAR[c] for c in row] + [VOID] * (max_line - len(row)) for row in board_map_s.splitlines() ] ) directions = [ int(p1) if p2 else p1 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s) ] # find on each row and column the first and last non-void row_first_non_void = np.argmax(board != VOID, axis=1) row_last_non_void = board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1 col_first_non_void = np.argmax(board != VOID, axis=0) col_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1 faces = np.zeros_like(board) size = np.gcd(board.shape[0], board.shape[1]) for row in range(0, board.shape[0], size): for col in range(row_first_non_void[row], row_last_non_void[row], size): faces[row : row + size, col : col + size] = faces.max() + 1 SIZE = np.gcd(*board.shape) # TODO: deduce this from the actual cube... faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]] if board.shape == (12, 16): # example faces_wrap = { 1: { "W": lambda y, x: (4, 4 + y, "S"), # 3N "N": lambda y, x: (4, 11 - x, "S"), # 2N "E": lambda y, x: (11 - y, 15, "W"), # 6E }, 2: { "W": lambda y, x: (11, 19 - y, "N"), # 6S "N": lambda y, x: (0, 11 - y, "S"), # 1N "S": lambda y, x: (11, 11 - x, "N"), # 5S }, 3: { "N": lambda y, x: (x - 4, 8, "E"), # 1W "S": lambda y, x: (15 - x, 8, "E"), # 5W }, 4: {"E": lambda y, x: (8, 19 - y, "S")}, # 6N 5: { "W": lambda y, x: (7, 15 - y, "N"), # 3S "S": lambda y, x: (7, 11 - x, "N"), # 2S }, 6: { "N": lambda y, x: (19 - x, 11, "W"), # 4E "E": lambda y, x: (11 - y, 11, "W"), # 1E "S": lambda y, x: (19 - x, 0, "E"), # 2W }, } else: faces_wrap = { 1: { "W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W "N": lambda y, x: (2 * SIZE + x, 0, "E"), # 6W }, 2: { "N": lambda y, x: (4 * SIZE - 1, x - 2 * SIZE, "N"), # 6S "E": lambda y, x: (3 * SIZE - y - 1, 2 * SIZE - 1, "W"), # 5E "S": lambda y, x: (x - SIZE, 2 * SIZE - 1, "W"), # 3E }, 3: { "W": lambda y, x: (2 * SIZE, y - SIZE, "S"), # 4N "E": lambda y, x: (SIZE - 1, SIZE + y, "N"), # 2S }, 4: { "W": lambda y, x: (3 * SIZE - y - 1, SIZE, "E"), # 1W "N": lambda y, x: (SIZE + x, SIZE, "E"), # 3W }, 5: { "E": lambda y, x: (3 * SIZE - y - 1, 3 * SIZE - 1, "W"), # 2E "S": lambda y, x: (2 * SIZE + x, SIZE - 1, "W"), # 6E }, 6: { "W": lambda y, x: (0, y - 2 * SIZE, "S"), # 1N "E": lambda y, x: (3 * SIZE - 1, y - 2 * SIZE, "N"), # 5S "S": lambda y, x: (0, x + 2 * SIZE, "S"), # 2N }, } def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]: if r0 == "E": return y0, row_first_non_void[y0], r0 elif r0 == "S": return col_first_non_void[x0], x0, r0 elif r0 == "W": return y0, row_last_non_void[y0], r0 elif r0 == "N": return col_last_non_void[x0], x0, r0 assert False def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]: cube = faces[y0, x0] assert r0 in faces_wrap[cube] return faces_wrap[cube][r0](y0, x0) def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int, str]: y0 = 0 x0 = np.where(board[0] == EMPTY)[0][0] r0 = "E" for direction in directions: if isinstance(direction, int): while direction > 0: if r0 == "E": xi = np.where(board[y0, x0 + 1 : x0 + direction + 1] == WALL)[0] if len(xi): x0 = x0 + xi[0] direction = 0 elif ( x0 + direction < board.shape[1] and board[y0, x0 + direction] == EMPTY ): x0 = x0 + direction direction = 0 else: y0_t, x0_t, r0_t = wrap(y0, x0, r0) if board[y0_t, x0_t] == WALL: x0 = row_last_non_void[y0] direction = 0 else: direction = direction - (row_last_non_void[y0] - x0) - 1 y0, x0, r0 = y0_t, x0_t, r0_t elif r0 == "S": yi = np.where(board[y0 + 1 : y0 + direction + 1, x0] == WALL)[0] if len(yi): y0 = y0 + yi[0] direction = 0 elif ( y0 + direction < board.shape[0] and board[y0 + direction, x0] == EMPTY ): y0 = y0 + direction direction = 0 else: y0_t, x0_t, r0_t = wrap(y0, x0, r0) if board[y0_t, x0_t] == WALL: y0 = col_last_non_void[x0] direction = 0 else: direction = direction - (col_last_non_void[x0] - y0) - 1 y0, x0, r0 = y0_t, x0_t, r0_t elif r0 == "W": left = max(x0 - direction - 1, 0) xi = np.where(board[y0, left:x0] == WALL)[0] if len(xi): x0 = left + xi[-1] + 1 direction = 0 elif x0 - direction >= 0 and board[y0, x0 - direction] == EMPTY: x0 = x0 - direction direction = 0 else: y0_t, x0_t, r0_t = wrap(y0, x0, r0) if board[y0_t, x0_t] == WALL: x0 = row_first_non_void[y0] direction = 0 else: direction = direction - (x0 - row_first_non_void[y0]) - 1 y0, x0, r0 = y0_t, x0_t, r0_t elif r0 == "N": top = max(y0 - direction - 1, 0) yi = np.where(board[top:y0, x0] == WALL)[0] if len(yi): y0 = top + yi[-1] + 1 direction = 0 elif y0 - direction >= 0 and board[y0 - direction, x0] == EMPTY: y0 = y0 - direction direction = 0 else: y0_t, x0_t, r0_t = wrap(y0, x0, r0) if board[y0_t, x0_t] == WALL: y0 = col_first_non_void[x0] direction = 0 else: direction = direction - (y0 - col_first_non_void[x0]) - 1 y0, x0, r0 = y0_t, x0_t, r0_t else: r0 = { "E": {"L": "N", "R": "S"}, "N": {"L": "W", "R": "E"}, "W": {"L": "S", "R": "N"}, "S": {"L": "E", "R": "W"}, }[r0][direction] return y0, x0, r0 y1, x1, r1 = run(wrap_part_1) answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1] print(f"answer 1 is {answer_1}") y2, x2, r2 = run(wrap_part_2) answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2] print(f"answer 2 is {answer_2}")