day22 - finally

This commit is contained in:
Stefan Harmuth 2022-12-25 18:35:42 +01:00
parent 1cf7be9ee2
commit 3853ccdbd7
2 changed files with 149 additions and 340 deletions

385
day22.py
View File

@ -1,233 +1,42 @@
import re import re
from tools.aoc import AOCDay from tools.aoc import AOCDay
from tools.coordinate import Coordinate from tools.coordinate import Coordinate
from tools.grid import Grid from tools.grid import Grid, GridTransformation
from typing import Any from typing import Any
FACING = [ FACING = [
Coordinate(1, 0), Coordinate(1, 0, 0),
Coordinate(0, 1), Coordinate(0, 1, 0),
Coordinate(-1, 0), Coordinate(-1, 0, 0),
Coordinate(0, -1) 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: def get_password_from_coord(c: Coordinate, face: int) -> int:
return (c.y + 1) * 1000 + (c.x + 1) * 4 + face return (c.y + 1) * 1000 + (c.x + 1) * 4 + face
def get_cube_conn_dict(cube_conn: tuple, c_size: int) -> dict: def walk(board: Grid, pos: Coordinate, directions: list) -> (Coordinate, int):
conn_dict = {} face = 0
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: for direction in directions:
steps, turn = direction steps, turn = direction
print("starting at", pos, "moving", steps, "steps, then turning", turn)
for _ in range(steps): for _ in range(steps):
next_pos = pos + FACING[face] next_pos = pos + FACING[face]
next_face = face next_face = face
if board.get(next_pos) is None or not board.isWithinBoundaries(next_pos): if board.get(next_pos) is None or not board.isWithinBoundaries(next_pos):
if connections is not None: match face:
next_pos, next_face = connections[(pos.x, pos.y, face)] case 0:
print("wrapping from", pos, "to", next_pos, "facing", next_face) next_pos = Coordinate(0, next_pos.y, 0)
else: case 1:
match face: next_pos = Coordinate(next_pos.x, 0, 0)
case 0: case 2:
next_pos = Coordinate(0, next_pos.y) next_pos = Coordinate(board.maxX, next_pos.y, 0)
case 1: case 3:
next_pos = Coordinate(next_pos.x, 0) next_pos = Coordinate(next_pos.x, board.maxY, 0)
case 2: while board.get(next_pos) is None:
next_pos = Coordinate(board.maxX, next_pos.y) next_pos += FACING[face]
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) next_val = board.get(next_pos)
if not next_val: if not next_val:
@ -242,6 +51,99 @@ def walk(board: Grid, pos: Coordinate, directions: list, face: int = 0, connecti
return pos, face 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): class Day(AOCDay):
inputs = [ inputs = [
[ [
@ -250,21 +152,18 @@ class Day(AOCDay):
], ],
[ [
(5031, "input22_test"), (5031, "input22_test"),
(None, "input22"), (142380, "input22"),
] ]
] ]
def get_map_and_directions(self) -> (Grid, list, Coordinate): def get_map_and_directions(self) -> (Grid, list, Coordinate):
board_map = Grid(None)
start_pos = None start_pos = None
map_lines, dir_line = self.getMultiLineInputAsArray() map_lines, dir_line = self.getMultiLineInputAsArray()
for y, map_line in enumerate(map_lines): board = Grid.from_str("/".join(map_lines), default=None, translate={' ': None, '#': False, '.': True}, mode3d=True)
for x, v in enumerate(map_line): for x in board.rangeX():
if v in ['#', '.']: if board.get(Coordinate(x, 0, 0)) is not None:
c = Coordinate(x, y) start_pos = Coordinate(x, 0, 0)
if start_pos is None and v == '.': break
start_pos = c
board_map.set(c, v == '.')
if dir_line[0][-1] not in ['R', 'L']: if dir_line[0][-1] not in ['R', 'L']:
dir_line[0] += 'S' dir_line[0] += 'S'
@ -272,29 +171,43 @@ class Day(AOCDay):
for d in re.findall(r'\d+[RLS]', dir_line[0]): for d in re.findall(r'\d+[RLS]', dir_line[0]):
directions.append((int(d[:-1]), d[-1])) directions.append((int(d[:-1]), d[-1]))
return board_map, directions, start_pos return board, directions, start_pos
def part1(self) -> Any: def part1(self) -> Any:
board_map, directions, start_position = self.get_map_and_directions() board, directions, start_position = self.get_map_and_directions()
finish, face = walk(board_map, start_position, directions) finish, face = walk(board, start_position, directions)
return get_password_from_coord(finish, face) return get_password_from_coord(finish, face)
def part2(self) -> Any: def part2(self) -> Any:
board_map, directions, start_position = self.get_map_and_directions() board, directions, start_position = self.get_map_and_directions()
conn = identify_cube(board_map) board.shift(shift_x=1, shift_y=1)
for x in board_map.rangeX(): start_position += Coordinate(1, 1, 0)
c = Coordinate(x, 0) fold(board, start_position)
if board_map.get(c) is not None: finish, face = cube_walk(board, Coordinate(1, 1, 0), directions)
start_position = c
break orig_board, _, _ = self.get_map_and_directions()
print(board_map.minX, board_map.minY, board_map.maxX, board_map.maxY) c_size = 4 if orig_board.maxX < 50 else 50 # account for test case
print(start_position) for x in range(0, orig_board.maxX + 1, c_size):
print(board_map.get(Coordinate(4, 0))) for y in range(0, orig_board.maxY + 1, c_size):
print(conn) check_pos = finish - Coordinate(1, 1, 0)
board_map.print(true_char='.') check_face = face
finish, face = walk(board_map, start_position, directions, 0, conn) sub_board = orig_board.sub_grid(x, y, x + c_size - 1, y + c_size - 1, 0, 0)
print(finish, face) if len(sub_board.getActiveCells()) == 0:
print(get_password_from_coord(finish, face)) 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 "" return ""

View File

@ -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)