From 3853ccdbd738f8a607ef2fedf4dab8557d7025dc Mon Sep 17 00:00:00 2001 From: Stefan Harmuth Date: Sun, 25 Dec 2022 18:35:42 +0100 Subject: [PATCH] day22 - finally --- day22.py | 385 ++++++++++++++++++----------------------------- day22_restart.py | 104 ------------- 2 files changed, 149 insertions(+), 340 deletions(-) delete mode 100644 day22_restart.py diff --git a/day22.py b/day22.py index 25ee87a..2e945da 100644 --- a/day22.py +++ b/day22.py @@ -1,233 +1,42 @@ import re from tools.aoc import AOCDay from tools.coordinate import Coordinate -from tools.grid import Grid +from tools.grid import Grid, GridTransformation from typing import Any FACING = [ - Coordinate(1, 0), - Coordinate(0, 1), - Coordinate(-1, 0), - Coordinate(0, -1) + Coordinate(1, 0, 0), + Coordinate(0, 1, 0), + Coordinate(-1, 0, 0), + Coordinate(0, -1, 0) ] -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): +def walk(board: Grid, pos: Coordinate, directions: list) -> (Coordinate, int): + face = 0 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] + match face: + case 0: + next_pos = Coordinate(0, next_pos.y, 0) + case 1: + next_pos = Coordinate(next_pos.x, 0, 0) + case 2: + next_pos = Coordinate(board.maxX, next_pos.y, 0) + case 3: + next_pos = Coordinate(next_pos.x, board.maxY, 0) + while board.get(next_pos) is None: + next_pos += FACING[face] next_val = board.get(next_pos) if not next_val: @@ -242,6 +51,99 @@ def walk(board: Grid, pos: Coordinate, directions: list, face: int = 0, connecti return pos, face +def cube_walk(board: Grid, pos: Coordinate, directions: list) -> (Coordinate, int): + face = 0 + for direction in directions: + steps, turn = direction + + for _ in range(steps): + next_pos = pos + FACING[face] + if board.get(next_pos) is False: + break + elif board.get(next_pos) is None: + match face: + case 0: # right + if board.get(Coordinate(pos.x + 1, pos.y, 1)) is True: + board.transform(GridTransformation.COUNTER_ROTATE_Y) + next_pos = Coordinate(1, pos.y, 0) + else: + break + case 1: # down + if board.get(Coordinate(pos.x, pos.y + 1, 1)) is True: + board.transform(GridTransformation.ROTATE_X) + next_pos = Coordinate(pos.x, 1, 0) + else: + break + case 2: # left + if board.get(Coordinate(0, pos.y, 1)) is True: + board.transform(GridTransformation.ROTATE_Y) + next_pos = Coordinate(board.maxX - 1, pos.y, 0) + else: + break + case 3: # up + if board.get(Coordinate(pos.x, 0, 1)) is True: + board.transform(GridTransformation.COUNTER_ROTATE_X) + next_pos = Coordinate(pos.x, board.maxY - 1, 0) + else: + break + + pos = next_pos + + if turn != 'S': + face = (face + (1 if turn == 'R' else -1)) % 4 + + return pos, face + + +def fold(board: Grid, start_pos: Coordinate): + c_size = 4 if board.maxX < 40 else 50 # account for test case + + if board.get(start_pos + Coordinate(-1, 0, 0)) is not None: + for y in board.rangeY(): + for x in range(start_pos.x - 1, board.minX - 1, -1): + from_c = Coordinate(x, y, 0) + to_c = Coordinate(start_pos.x - 1, y, start_pos.x - x) + board.set(to_c, board.get(from_c)) + board.set(from_c, None) + board.recalcBoundaries() + board.transform(GridTransformation.ROTATE_Y) + board.shift(shift_z=-board.minZ) + fold(board, Coordinate(board.maxX - c_size, 1, 0)) + board.transform(GridTransformation.COUNTER_ROTATE_Y) + board.shift_zero() + start_pos = Coordinate(1, 1, 0) + + if board.get(start_pos + Coordinate(c_size, 0, 0)) is not None: + for y in range(board.maxY + 1): + for x in range(start_pos.x + c_size, board.maxX + 1): + from_c = Coordinate(x, y, 0) + to_c = Coordinate(start_pos.x + c_size, y, x - start_pos.x - c_size + 1) + board.set(to_c, board.get(from_c)) + board.set(from_c, None) + board.recalcBoundaries() + board.transform(GridTransformation.COUNTER_ROTATE_Y) + board.shift(shift_z=-board.minZ) + fold(board, Coordinate(1, 1, 0)) + board.transform(GridTransformation.ROTATE_Y) + board.shift_zero() + if board.get(Coordinate(start_pos.x, 0, 0)) is not None: + board.shift(shift_y=1) + if list(board.getActiveCells(x=0, z=0)): + board.shift(shift_x=1) + + if board.get(start_pos + Coordinate(0, c_size, 0)) is not None: + for y in range(start_pos.y + c_size, board.maxY + 1): + for x in board.rangeX(): + from_c = Coordinate(x, y, 0) + to_c = Coordinate(x, start_pos.y + c_size, y - start_pos.y - c_size + 1) + board.set(to_c, board.get(from_c)) + board.set(from_c, None) + board.recalcBoundaries() + board.transform(GridTransformation.ROTATE_X) + fold(board, start_pos) + board.transform(GridTransformation.COUNTER_ROTATE_X) + + class Day(AOCDay): inputs = [ [ @@ -250,21 +152,18 @@ class Day(AOCDay): ], [ (5031, "input22_test"), - (None, "input22"), + (142380, "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 == '.') + board = Grid.from_str("/".join(map_lines), default=None, translate={' ': None, '#': False, '.': True}, mode3d=True) + for x in board.rangeX(): + if board.get(Coordinate(x, 0, 0)) is not None: + start_pos = Coordinate(x, 0, 0) + break if dir_line[0][-1] not in ['R', 'L']: dir_line[0] += 'S' @@ -272,29 +171,43 @@ class Day(AOCDay): for d in re.findall(r'\d+[RLS]', dir_line[0]): directions.append((int(d[:-1]), d[-1])) - return board_map, directions, start_pos + return board, 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) + board, directions, start_position = self.get_map_and_directions() + finish, face = walk(board, 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)) + board, directions, start_position = self.get_map_and_directions() + board.shift(shift_x=1, shift_y=1) + start_position += Coordinate(1, 1, 0) + fold(board, start_position) + finish, face = cube_walk(board, Coordinate(1, 1, 0), directions) + + orig_board, _, _ = self.get_map_and_directions() + c_size = 4 if orig_board.maxX < 50 else 50 # account for test case + for x in range(0, orig_board.maxX + 1, c_size): + for y in range(0, orig_board.maxY + 1, c_size): + check_pos = finish - Coordinate(1, 1, 0) + check_face = face + sub_board = orig_board.sub_grid(x, y, x + c_size - 1, y + c_size - 1, 0, 0) + if len(sub_board.getActiveCells()) == 0: + continue + check_board = board.sub_grid(1, 1, c_size, c_size, 0, 0) + for _ in range(2): + for _ in range(4): + check_board.transform(GridTransformation.ROTATE_Z) + check_pos = Coordinate(-check_pos.y + sub_board.maxY, check_pos.x, 0) + check_face = (check_face + 1) % 4 + if sub_board == check_board: + check_pos += Coordinate(x, y, 0) + return get_password_from_coord(check_pos, check_face) + + check_board.transform(GridTransformation.FLIP_X) + check_pos = Coordinate(abs(check_pos.x - c_size + 1), check_pos.y, 0) + check_face = (check_face + 2) % 4 + return "" diff --git a/day22_restart.py b/day22_restart.py deleted file mode 100644 index f2f6f51..0000000 --- a/day22_restart.py +++ /dev/null @@ -1,104 +0,0 @@ -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) -] - - -def get_password_from_coord(c: Coordinate, face: int) -> int: - return (c.y + 1) * 1000 + (c.x + 1) * 4 + face - - -def walk(board: Grid, pos: Coordinate, directions: list, face: int = 0) -> (Coordinate, int): - for direction in directions: - steps, turn = direction - - 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): - 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 - - -def fold(board: Grid, start_pos: Coordinate): - # find sides and remember the one with the starting_position as it's the initial x, y plane - c_size = 4 if board.maxX < 50 else 50 # account for test case - dim_x, dim_y = ((board.maxX + 1) // c_size, (board.maxY + 1) // c_size) - - # is there something in the front to fold back? - # is there something on the 4 sides to fold in? - # is there something on the 4 sides to fold up? - -class Day(AOCDay): - inputs = [ - [ - (6032, "input22_test"), - (1428, "input22"), - ], - [ - (5031, "input22_test"), - (5031, "input22"), - ] - ] - - def get_map_and_directions(self) -> (Grid, list, Coordinate): - start_pos = None - map_lines, dir_line = self.getMultiLineInputAsArray() - board = Grid.from_str("/".join(map_lines), default=None, translate={' ': None, '#': False, '.': True}) - for x in board.rangeX(): - if board.get(Coordinate(x, 0)) is not None: - start_pos = Coordinate(x, 0) - - 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, directions, start_pos - - def part1(self) -> Any: - board, directions, start_position = self.get_map_and_directions() - finish, face = walk(board, start_position, directions) - return get_password_from_coord(finish, face) - - def part2(self) -> Any: - board, directions, start_position = self.get_map_and_directions() - fold(board, start_position) - #board.print(translate={None: ' ', False: '#', True: '.'}) - return 5031 - - -if __name__ == '__main__': - day = Day(2022, 22) - day.run(verbose=True)