2024 day 15 gif output.
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Mikaël Capelle 2024-12-15 11:21:01 +01:00
parent 7447c7b536
commit 8651884ca6
2 changed files with 102 additions and 57 deletions

View File

@ -1,5 +1,4 @@
import itertools from typing import Any, Callable, Final, Iterator, TypeAlias
from typing import Any, Callable, Iterator, TypeAlias
import numpy as np import numpy as np
from numpy.typing import NDArray from numpy.typing import NDArray
@ -7,24 +6,83 @@ from numpy.typing import NDArray
from ..base import BaseSolver from ..base import BaseSolver
Robot: TypeAlias = tuple[int, int] Robot: TypeAlias = tuple[int, int]
Grid: TypeAlias = list[list[str]] Grid: TypeAlias = list[list[int]]
ImageGrid: TypeAlias = NDArray[np.uint8] ImageGrid: TypeAlias = NDArray[np.uint8]
class BoxUtils:
FREE: Final = 0
BLOCK: Final = 1
@staticmethod
def convert(grid_s: list[str], large: bool):
grid: list[list[int]] = []
robot: tuple[int, int] | None = None
box_counter = 2 if not large else 3
for i_row, row in enumerate(grid_s):
row_u: list[int] = []
for i_col, col in enumerate(row):
if col == "." or col == "@":
row_u.extend(
(BoxUtils.FREE, BoxUtils.FREE) if large else (BoxUtils.FREE,)
)
robot = (i_row, i_col)
elif col == "#":
row_u.extend(
(BoxUtils.BLOCK, BoxUtils.BLOCK) if large else (BoxUtils.BLOCK,)
)
else:
row_u.extend(
(box_counter, -box_counter) if large else (box_counter,)
)
box_counter += 2
grid.append(row_u)
assert robot is not None
return grid, robot
@staticmethod
def is_box(c: int):
return c >= 2 and c % 2 == 0
@staticmethod
def is_open_or_close_box(c: int):
return abs(c) >= 2 and c % 2 == 1
@staticmethod
def is_open_box(c: int):
return c >= 2 and c % 2 == 1
@staticmethod
def is_close_box(c: int):
return c < 0
@staticmethod
def to_char(c: int):
if c == BoxUtils.FREE:
return "."
elif c == BoxUtils.BLOCK:
return "#"
elif BoxUtils.is_box(c):
return "O"
elif BoxUtils.is_open_box(c):
return "["
else:
return "]"
class Solver(BaseSolver): class Solver(BaseSolver):
def print_grid(self, name: str, grid: Grid, robot: Robot): def print_grid(self, name: str, grid: Grid, robot: Robot):
if self.files: if self.files:
grid = [[c for c in row] for row in grid] grid_s = [[BoxUtils.to_char(c) for c in row] for row in grid]
grid[robot[0]][robot[1]] = "\033[31;1m@\033[00m" grid_s[robot[0]][robot[1]] = "\033[31;1m@\033[00m"
self.files.create( self.files.create(
name, "\n".join("".join(row) for row in grid).encode(), True name, "\n".join("".join(row) for row in grid_s).encode(), True
) )
def convert_grid(self, grid: Grid, robot: Robot) -> ImageGrid: def convert_grid(self, grid: Grid, robot: Robot) -> ImageGrid:
import numpy as np import numpy as np
grid = [[c for c in row] for row in grid] # grid[robot[0]][robot[1]] = "@"
grid[robot[0]][robot[1]] = "@"
# return np.array( # return np.array(
# [ # [
# [ # [
@ -43,7 +101,7 @@ class Solver(BaseSolver):
# ], # ],
# dtype=np.uint8, # dtype=np.uint8,
# ) # )
return np.array(grid) return np.abs(np.array(grid))
def step_part1(self, grid: Grid, move: str, robot: Robot): def step_part1(self, grid: Grid, move: str, robot: Robot):
match move: match move:
@ -59,20 +117,20 @@ class Solver(BaseSolver):
assert False assert False
row, col = robot row, col = robot
if grid[row + d_row][col + d_col] == ".": if grid[row + d_row][col + d_col] == BoxUtils.FREE:
robot = (row + d_row, col + d_col) robot = (row + d_row, col + d_col)
elif grid[row + d_row][col + d_col] != "#": elif grid[row + d_row][col + d_col] != BoxUtils.BLOCK:
assert grid[row + d_row][col + d_col] == "O"
n = 1 n = 1
while grid[row + n * d_row][col + n * d_col] == "O": while BoxUtils.is_box(grid[row + n * d_row][col + n * d_col]):
n += 1 n += 1
if grid[row + n * d_row][col + n * d_col] == ".": if grid[row + n * d_row][col + n * d_col] == BoxUtils.FREE:
robot = (row + d_row, col + d_col) robot = (row + d_row, col + d_col)
grid[row + d_row][col + d_col] = "."
for k in range(2, n + 1): for k in range(2, n + 1):
grid[row + k * d_row][col + k * d_col] = "O" grid[row + k * d_row][col + k * d_col] = grid[
row + (k - 1) * d_row
][col + (k - 1) * d_col]
grid[row + d_row][col + d_col] = BoxUtils.FREE
return grid, robot return grid, robot
@ -90,53 +148,47 @@ class Solver(BaseSolver):
assert False assert False
row, col = robot row, col = robot
if grid[row + d_row][col + d_col] == ".": if grid[row + d_row][col + d_col] == BoxUtils.FREE:
robot = (row + d_row, col + d_col) robot = (row + d_row, col + d_col)
elif grid[row + d_row][col + d_col] == "#": elif grid[row + d_row][col + d_col] == BoxUtils.BLOCK:
... ...
elif move in "<>": elif move in "<>":
assert d_row == 0
assert grid[row][col + d_col] in "[]"
n = 1 n = 1
while grid[row][col + n * d_col] in "[]": while BoxUtils.is_open_or_close_box(grid[row][col + n * d_col]):
n += 1 n += 1
if grid[row][col + n * d_col] == ".": if grid[row][col + n * d_col] == BoxUtils.FREE:
robot = (row, col + d_col) robot = (row, col + d_col)
for k in range(n, 1, -1): for k in range(n, 1, -1):
grid[row][col + k * d_col] = grid[row][col + (k - 1) * d_col] grid[row][col + k * d_col] = grid[row][col + (k - 1) * d_col]
grid[row + d_row][col + d_col] = "." grid[row + d_row][col + d_col] = BoxUtils.FREE
elif move in "^v": elif move in "^v":
assert d_col == 0
assert grid[row + d_row][col + d_col] in "[]"
n = 1 n = 1
boxes: list[set[int]] = [{col}] boxes: list[set[int]] = [{col}]
while True: while True:
to_move = boxes[-1] to_move = boxes[-1]
if any(grid[row + n * d_row][c] == "#" for c in to_move): if any(grid[row + n * d_row][c] == BoxUtils.BLOCK for c in to_move):
break break
if all(grid[row + n * d_row][c] == "." for c in to_move): if all(grid[row + n * d_row][c] == BoxUtils.FREE for c in to_move):
break break
as_move: set[int] = set() as_move: set[int] = set()
for c in to_move: for c in to_move:
if grid[row + n * d_row][c] == "]": if BoxUtils.is_close_box(grid[row + n * d_row][c]):
as_move.update({c - 1, c}) as_move.update({c - 1, c})
elif grid[row + n * d_row][c] == "[": elif BoxUtils.is_open_box(grid[row + n * d_row][c]):
as_move.update({c, c + 1}) as_move.update({c, c + 1})
boxes.append(as_move) boxes.append(as_move)
n += 1 n += 1
if all(grid[row + n * d_row][c] == "." for c in boxes[-1]): if all(grid[row + n * d_row][c] == BoxUtils.FREE for c in boxes[-1]):
for k, to_move in zip(range(n, 1, -1), boxes[-1:0:-1], strict=True): for k, to_move in zip(range(n, 1, -1), boxes[-1:0:-1], strict=True):
for c in to_move: for c in to_move:
grid[row + k * d_row][c] = grid[row + (k - 1) * d_row][c] grid[row + k * d_row][c] = grid[row + (k - 1) * d_row][c]
grid[row + (k - 1) * d_row][c] = "." grid[row + (k - 1) * d_row][c] = BoxUtils.FREE
robot = (row + d_row, col + d_col) robot = (row + d_row, col + d_col)
return grid, robot return grid, robot
@ -145,6 +197,7 @@ class Solver(BaseSolver):
self, self,
name: str, name: str,
grid: Grid, grid: Grid,
robot: Robot,
moves: str, moves: str,
fn: Callable[ fn: Callable[
[Grid, str, Robot], [Grid, str, Robot],
@ -152,13 +205,6 @@ class Solver(BaseSolver):
], ],
generate: bool, generate: bool,
) -> tuple[Grid, list[ImageGrid]]: ) -> tuple[Grid, list[ImageGrid]]:
# find robot
n_rows, n_cols = len(grid), len(grid[0])
robot = next(
(i, j) for i in range(n_rows) for j in range(n_cols) if grid[i][j] == "@"
)
grid[robot[0]][robot[1]] = "."
# initialize # initialize
images: list[ImageGrid] = [] images: list[ImageGrid] = []
@ -184,35 +230,37 @@ class Solver(BaseSolver):
grid, images = self.run( grid, images = self.run(
"part1", "part1",
[[c for c in r] for r in grid_s.splitlines()], *BoxUtils.convert(grid_s.splitlines(), False),
moves, moves,
self.step_part1, self.step_part1,
self.files is not None, self.files is not None,
) )
if self.files:
print(images[0].max())
self.files.image(
"anim_part1.gif", np.stack(images, axis=0).astype(np.uint8)
)
yield sum( yield sum(
100 * i_row + i_col 100 * i_row + i_col
for i_row, row in enumerate(grid) for i_row, row in enumerate(grid)
for i_col, col in enumerate(row) for i_col, col in enumerate(row)
if col == "O" if BoxUtils.is_box(col)
) )
grid, images = self.run( grid, images = self.run(
"part2", "part2",
[ *BoxUtils.convert(grid_s.splitlines(), True),
list(
itertools.chain.from_iterable(
{"O": "[]", "@": "@.", "#": "##", ".": ".."}[c] for c in r
)
)
for r in grid_s.splitlines()
],
moves, moves,
self.step_part2, self.step_part2,
self.files is not None, self.files is not None,
) )
if self.files:
self.files.image(
"anim_part2.gif", np.stack(images, axis=0).astype(np.uint8)
)
yield sum( yield sum(
100 * i_row + i_col 100 * i_row + i_col
for i_row, row in enumerate(grid) for i_row, row in enumerate(grid)
for i_col, col in enumerate(row) for i_col, col in enumerate(row)
if col == "[" if BoxUtils.is_open_box(col)
) )

View File

@ -25,11 +25,8 @@ class FileHandler:
import imageio.v3 as iio import imageio.v3 as iio
io = BytesIO() data = iio.imwrite("<bytes>", image, extension=Path(filename).suffix) # type: ignore
iio.imwrite(io, image, extension=Path(filename).suffix) # type: ignore self.create(filename, data, False)
io.seek(0)
self.create(filename, io.read(), False)
class BaseSolver: class BaseSolver: