import re from tools.aoc import AOCDay from tools.coordinate import Coordinate from tools.grid import Grid from typing import Any FACING = [ Coordinate(1, 0), Coordinate(0, 1), Coordinate(-1, 0), Coordinate(0, -1) ] CUBE_CONNECTIONS = { '##./.#./.##/..#': ( ((0, 0, 1), (1, 1, 0)), ((0, 0, 2), (1, 2, 0)), ((0, 0, 3), (2, 3, 3)), ((1, 0, 0), (2, 2, 2)), ((1, 0, 3), (2, 3, 2)), ((1, 1, 0), (2, 2, 3)), ((1, 1, 2), (0, 0, 3)), ((1, 2, 2), (0, 0, 0)), ((1, 2, 3), (2, 3, 0)), ((2, 2, 0), (0, 1, 2)), ((2, 2, 3), (1, 1, 2)), ((2, 3, 0), (1, 0, 3)), ((2, 3, 1), (0, 0, 1)), ((2, 3, 2), (1, 2, 3)), ), '..#./###./..##': ( ((2, 0, 0), (3, 2, 2)), ((2, 0, 2), (1, 1, 1)), ((2, 0, 3), (0, 1, 1)), ((0, 1, 1), (2, 2, 3)), ((0, 1, 2), (3, 2, 3)), ((0, 1, 3), (2, 0, 1)), ((1, 1, 1), (2, 2, 0)), ((1, 1, 3), (2, 0, 0)), ((2, 1, 0), (3, 2, 1)), ((2, 2, 1), (0, 1, 3)), ((2, 2, 2), (1, 1, 3)), ((3, 2, 0), (2, 0, 2)), ((3, 2, 1), (0, 1, 0)), ((3, 2, 3), (2, 1, 2)), ), '.#./.#./###/#..': ( ((1, 0, 0), (2, 2, 2)), ((1, 0, 2), (0, 2, 0)), ((1, 0, 3), (0, 3, 0)), ((1, 1, 0), (2, 2, 3)), ((1, 1, 2), (0, 2, 1)), ((0, 2, 2), (1, 0, 0)), ((0, 2, 3), (1, 1, 0)), ((1, 2, 1), (0, 3, 2)), ((2, 2, 0), (1, 0, 2)), ((2, 2, 1), (0, 3, 3)), ((2, 2, 3), (1, 1, 2)), ((0, 3, 0), (1, 2, 3)), ((0, 3, 1), (2, 2, 1)), ((0, 3, 2), (1, 0, 1)), ), } def get_password_from_coord(c: Coordinate, face: int) -> int: return (c.y + 1) * 1000 + (c.x + 1) * 4 + face def get_cube_conn_dict(cube_conn: tuple, c_size: int) -> dict: conn_dict = {} for conn in cube_conn: cur, tar = conn cur_x, cur_y, cur_f = cur tar_x, tar_y, tar_f = tar match cur_f, tar_f: case 0, 1: x = (cur_x + 1) * c_size - 1 ty = tar_y * c_size for d in range(c_size): y = cur_y * c_size + d tx = (tar_x + 1) * c_size - (d + 1) conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 0, 2: x = (cur_x + 1) * c_size - 1 tx = (tar_x + 1) * c_size - 1 for dy in range(c_size): y = cur_y * c_size + dy ty = tar_y * c_size + dy conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 0, 3: x = (cur_x + 1) * c_size - 1 ty = tar_y * c_size for dy in range(c_size): y = cur_y * c_size + dy tx = tar_x * c_size + dy conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 1, 0: y = (cur_y + 1) * c_size - 1 tx = tar_x * c_size for dy in range(c_size): x = cur_x * c_size + dy ty = tar_y * c_size + dy conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 1, 1: y = (cur_y + 1) * c_size - 1 ty = tar_y * c_size for dx in range(c_size): x = cur_x * c_size + dx tx = tar_x * c_size + dx conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 1, 2: y = (cur_y + 1) * c_size - 1 tx = (tar_x + 1) * c_size - 1 for d in range(c_size): x = cur_x * c_size + d ty = tar_y * c_size + d conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 1, 3: y = (cur_y + 1) * c_size - 1 ty = (tar_y + 1) * c_size - 1 for d in range(c_size): x = cur_x * c_size + d tx = tar_x * c_size + d conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 2, 0: x = cur_x * c_size tx = tar_x * c_size for dy in range(c_size): y = cur_y * c_size + dy ty = tar_y * c_size + dy conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 2, 1: x = cur_x * c_size ty = tar_y * c_size for d in range(c_size): y = cur_y * c_size + d tx = tar_x * c_size + d conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 2, 3: x = cur_x * c_size ty = (tar_y + 1) * c_size - 1 for dy in range(c_size): y = cur_y * c_size + dy tx = tar_x * c_size + dy conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 3, 0: y = cur_y * c_size tx = tar_x * c_size for d in range(c_size): x = cur_x * c_size + d ty = tar_y * c_size + d conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 3, 1: y = cur_y * c_size ty = (tar_y + 1) * c_size - 1 for d in range(c_size): x = cur_x * c_size + d tx = tar_x * c_size + d conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 3, 2: y = cur_y * c_size tx = (tar_x + 1) * c_size - 1 for d in range(c_size): x = cur_x * c_size + d ty = (tar_y + 1) * c_size - (d + 1) conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case 3, 3: y = cur_y * c_size ty = tar_y * c_size for d in range(c_size): x = cur_x * c_size + d tx = tar_x * c_size + d conn_dict[(x, y, cur_f)] = (Coordinate(tx, ty), tar_f) case _: print("Missing case:", cur_f, tar_f) return conn_dict def identify_cube(board: Grid) -> dict: for i in ((4, 3), (3, 4), (2, 5), (5, 2)): if (board.maxX + 1) % i[0] != 0 or (board.maxY + 1) % i[1] != 0: continue x_size, y_size = (board.maxX + 1) // i[0], (board.maxY + 1) // i[1] if x_size != y_size: continue # this cannot be a cube x_tiles, y_tiles = (board.maxX + 1) // x_size, (board.maxY + 1) // y_size cube_grid = Grid() for x in range(x_tiles): for y in range(y_tiles): cube_grid.set(Coordinate(x, y), board.get(Coordinate(x * x_size, y * y_size)) is not None) if cube_grid.getOnCount() != 6: continue cube_grid.print(true_char='#') if str(cube_grid) in CUBE_CONNECTIONS: return get_cube_conn_dict(CUBE_CONNECTIONS[str(cube_grid)], x_size) print("Unknown Cube-Shape") def walk(board: Grid, pos: Coordinate, directions: list, face: int = 0, connections: dict = None) -> (Coordinate, int): for direction in directions: steps, turn = direction print("starting at", pos, "moving", steps, "steps, then turning", turn) for _ in range(steps): next_pos = pos + FACING[face] next_face = face if board.get(next_pos) is None or not board.isWithinBoundaries(next_pos): if connections is not None: next_pos, next_face = connections[(pos.x, pos.y, face)] print("wrapping from", pos, "to", next_pos, "facing", next_face) else: match face: case 0: next_pos = Coordinate(0, next_pos.y) case 1: next_pos = Coordinate(next_pos.x, 0) case 2: next_pos = Coordinate(board.maxX, next_pos.y) case 3: next_pos = Coordinate(next_pos.x, board.maxY) while board.get(next_pos) is None: next_pos += FACING[face] next_val = board.get(next_pos) if not next_val: break else: pos = next_pos face = next_face if turn != 'S': face = (face + (1 if turn == 'R' else -1)) % 4 return pos, face class Day(AOCDay): inputs = [ [ (6032, "input22_test"), (1428, "input22"), ], [ (5031, "input22_test"), (None, "input22"), ] ] def get_map_and_directions(self) -> (Grid, list, Coordinate): board_map = Grid(None) start_pos = None map_lines, dir_line = self.getMultiLineInputAsArray() for y, map_line in enumerate(map_lines): for x, v in enumerate(map_line): if v in ['#', '.']: c = Coordinate(x, y) if start_pos is None and v == '.': start_pos = c board_map.set(c, v == '.') if dir_line[0][-1] not in ['R', 'L']: dir_line[0] += 'S' directions = [] for d in re.findall(r'\d+[RLS]', dir_line[0]): directions.append((int(d[:-1]), d[-1])) return board_map, directions, start_pos def part1(self) -> Any: board_map, directions, start_position = self.get_map_and_directions() finish, face = walk(board_map, start_position, directions) return get_password_from_coord(finish, face) def part2(self) -> Any: board_map, directions, start_position = self.get_map_and_directions() conn = identify_cube(board_map) for x in board_map.rangeX(): c = Coordinate(x, 0) if board_map.get(c) is not None: start_position = c break print(board_map.minX, board_map.minY, board_map.maxX, board_map.maxY) print(start_position) print(board_map.get(Coordinate(4, 0))) print(conn) board_map.print(true_char='.') finish, face = walk(board_map, start_position, directions, 0, conn) print(finish, face) print(get_password_from_coord(finish, face)) return "" if __name__ == '__main__': day = Day(2022, 22) day.run(verbose=True)