This commit is contained in:
parent
bc06f86fdc
commit
8308940674
30
poetry.lock
generated
30
poetry.lock
generated
@ -584,6 +584,30 @@ files = [
|
|||||||
{file = "numpy-2.2.0.tar.gz", hash = "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0"},
|
{file = "numpy-2.2.0.tar.gz", hash = "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opencv-python"
|
||||||
|
version = "4.10.0.84"
|
||||||
|
description = "Wrapper package for OpenCV python bindings."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526"},
|
||||||
|
{file = "opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251"},
|
||||||
|
{file = "opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98"},
|
||||||
|
{file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6"},
|
||||||
|
{file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f"},
|
||||||
|
{file = "opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236"},
|
||||||
|
{file = "opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
numpy = [
|
||||||
|
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
|
||||||
|
{version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
|
||||||
|
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
|
||||||
|
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ortools"
|
name = "ortools"
|
||||||
version = "9.11.4210"
|
version = "9.11.4210"
|
||||||
@ -689,9 +713,9 @@ files = [
|
|||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
numpy = [
|
numpy = [
|
||||||
{version = ">=1.22.4", markers = "python_version < \"3.11\""},
|
|
||||||
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
|
|
||||||
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
|
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
|
||||||
|
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
|
||||||
|
{version = ">=1.22.4", markers = "python_version < \"3.11\""},
|
||||||
]
|
]
|
||||||
python-dateutil = ">=2.8.2"
|
python-dateutil = ">=2.8.2"
|
||||||
pytz = ">=2020.1"
|
pytz = ">=2020.1"
|
||||||
@ -1503,4 +1527,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "1b586d7f23aca41e499ce94ec10eacfab3c2e260bb16822638ae1f831b2988be"
|
content-hash = "5b57bccd8dc65a9acecbe187939bae625ef6a259f4188c6587907245bcfa604f"
|
||||||
|
@ -17,6 +17,7 @@ networkx = "^3.4.2"
|
|||||||
pillow = "^11.0.0"
|
pillow = "^11.0.0"
|
||||||
imageio = "^2.36.1"
|
imageio = "^2.36.1"
|
||||||
pygifsicle = "^1.1.0"
|
pygifsicle = "^1.1.0"
|
||||||
|
opencv-python = "^4.10.0.84"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pyright = "^1.1.389"
|
pyright = "^1.1.389"
|
||||||
|
@ -5,108 +5,114 @@ from numpy.typing import NDArray
|
|||||||
|
|
||||||
from ..base import BaseSolver
|
from ..base import BaseSolver
|
||||||
|
|
||||||
Robot: TypeAlias = tuple[int, int]
|
|
||||||
Grid: TypeAlias = list[list[int]]
|
|
||||||
ImageGrid: TypeAlias = NDArray[np.uint8]
|
ImageGrid: TypeAlias = NDArray[np.uint8]
|
||||||
|
|
||||||
|
|
||||||
class BoxUtils:
|
class Grid:
|
||||||
FREE: Final = 0
|
FREE: Final = 0
|
||||||
BLOCK: Final = 1
|
BLOCK: Final = 1
|
||||||
ROBOT: Final = 2
|
ROBOT: Final = 2
|
||||||
|
|
||||||
@staticmethod
|
robot: tuple[int, int]
|
||||||
def convert(grid_s: list[str], large: bool):
|
|
||||||
|
def __init__(self, grid_s: list[str], large: bool):
|
||||||
grid: list[list[int]] = []
|
grid: list[list[int]] = []
|
||||||
robot: tuple[int, int] | None = None
|
robot: tuple[int, int] | None = None
|
||||||
box_counter = 4 if not large else 5
|
box_counter = 4 if not large else 5
|
||||||
for i_row, row in enumerate(grid_s):
|
for i_row, row in enumerate(grid_s):
|
||||||
row_u: list[int] = []
|
row_u: list[int] = []
|
||||||
for i_col, col in enumerate(row):
|
for i_col, col in enumerate(row):
|
||||||
if col == "." or col == "@":
|
if col in ".@":
|
||||||
row_u.extend(
|
row_u.extend((Grid.FREE, Grid.FREE) if large else (Grid.FREE,))
|
||||||
(BoxUtils.FREE, BoxUtils.FREE) if large else (BoxUtils.FREE,)
|
if col == "@":
|
||||||
)
|
robot = (i_row, i_col * 2 if large else i_col)
|
||||||
robot = (i_row, i_col)
|
|
||||||
elif col == "#":
|
elif col == "#":
|
||||||
row_u.extend(
|
row_u.extend((Grid.BLOCK, Grid.BLOCK) if large else (Grid.BLOCK,))
|
||||||
(BoxUtils.BLOCK, BoxUtils.BLOCK) if large else (BoxUtils.BLOCK,)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
row_u.extend(
|
row_u.extend(
|
||||||
(box_counter, -box_counter) if large else (box_counter,)
|
(box_counter, -box_counter) if large else (box_counter,)
|
||||||
)
|
)
|
||||||
box_counter += 2
|
box_counter += 2
|
||||||
grid.append(row_u)
|
grid.append(row_u)
|
||||||
|
|
||||||
|
self.grid = np.array(grid)
|
||||||
|
|
||||||
assert robot is not None
|
assert robot is not None
|
||||||
return grid, robot
|
self.robot = robot
|
||||||
|
|
||||||
@staticmethod
|
@property
|
||||||
def is_box(c: int):
|
def n_rows(self):
|
||||||
return c >= 4 and c % 2 == 0
|
return len(self.grid)
|
||||||
|
|
||||||
@staticmethod
|
@property
|
||||||
def is_open_or_close_box(c: int):
|
def n_columns(self):
|
||||||
return abs(c) >= 4 and c % 2 == 1
|
return len(self.grid[0])
|
||||||
|
|
||||||
@staticmethod
|
def __len__(self):
|
||||||
def is_open_box(c: int):
|
return self.n_rows
|
||||||
return c >= 4 and c % 2 == 1
|
|
||||||
|
|
||||||
@staticmethod
|
def __iter__(self):
|
||||||
def is_close_box(c: int):
|
return iter(self.grid)
|
||||||
return c < 0
|
|
||||||
|
|
||||||
@staticmethod
|
def __getitem__(self, index: tuple[int, int]):
|
||||||
def to_char(c: int):
|
return self.grid[*index]
|
||||||
if c == BoxUtils.FREE:
|
|
||||||
|
def __setitem__(self, index: tuple[int, int], value: int):
|
||||||
|
self.grid[*index] = value
|
||||||
|
|
||||||
|
def is_free(self, row: int, col: int):
|
||||||
|
return self[row, col] == Grid.FREE
|
||||||
|
|
||||||
|
def is_block(self, row: int, col: int):
|
||||||
|
return self[row, col] == Grid.BLOCK
|
||||||
|
|
||||||
|
def is_box(self, row: int, col: int):
|
||||||
|
return (c := self[row, col]) >= 4 and c % 2 == 0
|
||||||
|
|
||||||
|
def is_open_or_close_box(self, row: int, col: int):
|
||||||
|
return abs(c := self[row, col]) >= 4 and c % 2 == 1
|
||||||
|
|
||||||
|
def is_open_box(self, row: int, col: int):
|
||||||
|
return (c := self[row, col]) >= 4 and c % 2 == 1
|
||||||
|
|
||||||
|
def is_close_box(self, row: int, col: int):
|
||||||
|
return self[row, col] < 0
|
||||||
|
|
||||||
|
def _to_char(self, row: int, col: int):
|
||||||
|
if self.is_free(row, col):
|
||||||
return "."
|
return "."
|
||||||
elif c == BoxUtils.BLOCK:
|
elif self.is_block(row, col):
|
||||||
return "#"
|
return "#"
|
||||||
elif BoxUtils.is_box(c):
|
elif self.is_box(row, col):
|
||||||
return "O"
|
return "O"
|
||||||
elif BoxUtils.is_open_box(c):
|
elif self.is_open_box(row, col):
|
||||||
return "["
|
return "["
|
||||||
else:
|
else:
|
||||||
return "]"
|
return "]"
|
||||||
|
|
||||||
|
def as_numpy(self):
|
||||||
class Solver(BaseSolver):
|
arr = self.grid.copy()
|
||||||
def print_grid(self, name: str, grid: Grid, robot: Robot):
|
arr[*self.robot] = Grid.ROBOT
|
||||||
if self.files:
|
|
||||||
grid_s = [[BoxUtils.to_char(c) for c in row] for row in grid]
|
|
||||||
grid_s[robot[0]][robot[1]] = "\033[31;1m@\033[00m"
|
|
||||||
self.files.create(
|
|
||||||
name, "\n".join("".join(row) for row in grid_s).encode(), True
|
|
||||||
)
|
|
||||||
|
|
||||||
def convert_grid(self, grid: Grid, robot: Robot) -> ImageGrid:
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
# grid[robot[0]][robot[1]] = "@"
|
|
||||||
# return np.array(
|
|
||||||
# [
|
|
||||||
# [
|
|
||||||
# # {
|
|
||||||
# # "#": [255, 255, 255],
|
|
||||||
# # "@": [255, 0, 0],
|
|
||||||
# # "O": [0, 255, 0],
|
|
||||||
# # "[": [0, 0, 255],
|
|
||||||
# # "]": [0, 0, 255],
|
|
||||||
# # ".": [0, 0, 0],
|
|
||||||
# # }[col]
|
|
||||||
# ord(col)
|
|
||||||
# for col in row
|
|
||||||
# ]
|
|
||||||
# for row in grid
|
|
||||||
# ],
|
|
||||||
# dtype=np.uint8,
|
|
||||||
# )
|
|
||||||
arr = np.array(grid)
|
|
||||||
arr[*robot] = BoxUtils.ROBOT
|
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
def step_part1(self, grid: Grid, move: str, robot: Robot):
|
def as_printable(self):
|
||||||
|
grid_s = [
|
||||||
|
[self._to_char(row, col) for col in range(self.n_columns)]
|
||||||
|
for row in range(self.n_rows)
|
||||||
|
]
|
||||||
|
grid_s[self.robot[0]][self.robot[1]] = "\033[31;1m@\033[00m"
|
||||||
|
return "\n".join("".join(row) for row in grid_s)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.as_printable()
|
||||||
|
|
||||||
|
|
||||||
|
class Solver(BaseSolver):
|
||||||
|
def save_grid(self, name: str, grid: Grid):
|
||||||
|
if self.files:
|
||||||
|
self.files.create(name, grid.as_printable().encode(), True)
|
||||||
|
|
||||||
|
def step_part1(self, grid: Grid, move: str):
|
||||||
match move:
|
match move:
|
||||||
case "^":
|
case "^":
|
||||||
d_row, d_col = -1, 0
|
d_row, d_col = -1, 0
|
||||||
@ -119,25 +125,25 @@ class Solver(BaseSolver):
|
|||||||
case _:
|
case _:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
row, col = robot
|
row, col = grid.robot
|
||||||
if grid[row + d_row][col + d_col] == BoxUtils.FREE:
|
if grid.is_free(row + d_row, col + d_col):
|
||||||
robot = (row + d_row, col + d_col)
|
grid.robot = (row + d_row, col + d_col)
|
||||||
elif grid[row + d_row][col + d_col] != BoxUtils.BLOCK:
|
elif not grid.is_block(row + d_row, col + d_col):
|
||||||
n = 1
|
n = 1
|
||||||
while BoxUtils.is_box(grid[row + n * d_row][col + n * d_col]):
|
while grid.is_box(row + n * d_row, col + n * d_col):
|
||||||
n += 1
|
n += 1
|
||||||
|
|
||||||
if grid[row + n * d_row][col + n * d_col] == BoxUtils.FREE:
|
if grid.is_free(row + n * d_row, col + n * d_col):
|
||||||
robot = (row + d_row, col + d_col)
|
grid.robot = (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] = grid[
|
grid[row + k * d_row, col + k * d_col] = grid[
|
||||||
row + (k - 1) * d_row
|
row + (k - 1) * d_row, col + (k - 1) * d_col
|
||||||
][col + (k - 1) * d_col]
|
]
|
||||||
grid[row + d_row][col + d_col] = BoxUtils.FREE
|
grid[row + d_row, col + d_col] = Grid.FREE
|
||||||
|
|
||||||
return grid, robot
|
return grid
|
||||||
|
|
||||||
def step_part2(self, grid: Grid, move: str, robot: Robot):
|
def step_part2(self, grid: Grid, move: str):
|
||||||
match move:
|
match move:
|
||||||
case "^":
|
case "^":
|
||||||
d_row, d_col = -1, 0
|
d_row, d_col = -1, 0
|
||||||
@ -150,80 +156,76 @@ class Solver(BaseSolver):
|
|||||||
case _:
|
case _:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
row, col = robot
|
row, col = grid.robot
|
||||||
if grid[row + d_row][col + d_col] == BoxUtils.FREE:
|
if grid.is_free(row + d_row, col + d_col):
|
||||||
robot = (row + d_row, col + d_col)
|
grid.robot = (row + d_row, col + d_col)
|
||||||
elif grid[row + d_row][col + d_col] == BoxUtils.BLOCK:
|
elif grid.is_block(row + d_row, col + d_col):
|
||||||
...
|
...
|
||||||
elif move in "<>":
|
elif move in "<>":
|
||||||
n = 1
|
n = 1
|
||||||
while BoxUtils.is_open_or_close_box(grid[row][col + n * d_col]):
|
while grid.is_open_or_close_box(row, col + n * d_col):
|
||||||
n += 1
|
n += 1
|
||||||
|
|
||||||
if grid[row][col + n * d_col] == BoxUtils.FREE:
|
if grid.is_free(row, col + n * d_col):
|
||||||
robot = (row, col + d_col)
|
grid.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] = BoxUtils.FREE
|
grid[row + d_row, col + d_col] = Grid.FREE
|
||||||
|
|
||||||
elif move in "^v":
|
elif move in "^v":
|
||||||
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] == BoxUtils.BLOCK for c in to_move):
|
if any(grid.is_block(row + n * d_row, c) for c in to_move):
|
||||||
break
|
break
|
||||||
if all(grid[row + n * d_row][c] == BoxUtils.FREE for c in to_move):
|
if all(grid.is_free(row + n * d_row, c) 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 BoxUtils.is_close_box(grid[row + n * d_row][c]):
|
if grid.is_close_box(row + n * d_row, c):
|
||||||
as_move.update({c - 1, c})
|
as_move.update({c - 1, c})
|
||||||
elif BoxUtils.is_open_box(grid[row + n * d_row][c]):
|
elif grid.is_open_box(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] == BoxUtils.FREE for c in boxes[-1]):
|
if all(grid.is_free(row + n * d_row, c) 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] = BoxUtils.FREE
|
grid[row + (k - 1) * d_row, c] = Grid.FREE
|
||||||
robot = (row + d_row, col + d_col)
|
grid.robot = (row + d_row, col + d_col)
|
||||||
|
|
||||||
return grid, robot
|
return grid
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
robot: Robot,
|
|
||||||
moves: str,
|
moves: str,
|
||||||
fn: Callable[
|
fn: Callable[[Grid, str], Grid],
|
||||||
[Grid, str, Robot],
|
|
||||||
tuple[Grid, Robot],
|
|
||||||
],
|
|
||||||
generate: bool,
|
generate: bool,
|
||||||
) -> tuple[Grid, list[ImageGrid]]:
|
) -> tuple[Grid, list[ImageGrid]]:
|
||||||
# initialize
|
# initialize
|
||||||
images: list[ImageGrid] = []
|
images: list[ImageGrid] = []
|
||||||
|
|
||||||
if generate:
|
if generate:
|
||||||
images.append(self.convert_grid(grid, robot))
|
images.append(grid.as_numpy())
|
||||||
|
|
||||||
self.print_grid(f"initial_grid_{name}.txt", grid, robot)
|
self.save_grid(f"initial_grid_{name}.txt", grid)
|
||||||
|
|
||||||
for move in self.progress.wrap(moves):
|
for move in self.progress.wrap(moves):
|
||||||
self.logger.debug(f"Move '{move}'...")
|
self.logger.debug(f"Move '{move}'...")
|
||||||
grid, robot = fn(grid, move, robot)
|
grid = fn(grid, move)
|
||||||
|
|
||||||
if generate:
|
if generate:
|
||||||
images.append(self.convert_grid(grid, robot))
|
images.append(grid.as_numpy())
|
||||||
|
|
||||||
self.print_grid(f"final_grid_{name}.txt", grid, robot)
|
self.save_grid(f"final_grid_{name}.txt", grid)
|
||||||
|
|
||||||
return grid, images
|
return grid, images
|
||||||
|
|
||||||
@ -245,7 +247,7 @@ class Solver(BaseSolver):
|
|||||||
|
|
||||||
grid, images = self.run(
|
grid, images = self.run(
|
||||||
"part1",
|
"part1",
|
||||||
*BoxUtils.convert(grid_s.splitlines(), False),
|
Grid(grid_s.splitlines(), False),
|
||||||
moves,
|
moves,
|
||||||
self.step_part1,
|
self.step_part1,
|
||||||
self.files is not None,
|
self.files is not None,
|
||||||
@ -253,17 +255,17 @@ class Solver(BaseSolver):
|
|||||||
if self.files:
|
if self.files:
|
||||||
images = np.stack(images, axis=0)
|
images = np.stack(images, axis=0)
|
||||||
images[images >= 2] = 1 + images[images >= 2] // 2
|
images[images >= 2] = 1 + images[images >= 2] // 2
|
||||||
self.files.image("anim_part1.gif", colors[images])
|
self.files.video("anim_part1.webm", colors[images])
|
||||||
yield sum(
|
yield sum(
|
||||||
100 * i_row + i_col
|
100 * row + col
|
||||||
for i_row, row in enumerate(grid)
|
for row in range(grid.n_rows)
|
||||||
for i_col, col in enumerate(row)
|
for col in range(grid.n_columns)
|
||||||
if BoxUtils.is_box(col)
|
if grid.is_box(row, col)
|
||||||
)
|
)
|
||||||
|
|
||||||
grid, images = self.run(
|
grid, images = self.run(
|
||||||
"part2",
|
"part2",
|
||||||
*BoxUtils.convert(grid_s.splitlines(), True),
|
Grid(grid_s.splitlines(), True),
|
||||||
moves,
|
moves,
|
||||||
self.step_part2,
|
self.step_part2,
|
||||||
self.files is not None,
|
self.files is not None,
|
||||||
@ -271,10 +273,10 @@ class Solver(BaseSolver):
|
|||||||
if self.files:
|
if self.files:
|
||||||
images = np.abs(np.stack(images, axis=0))
|
images = np.abs(np.stack(images, axis=0))
|
||||||
images[images >= 2] = 1 + images[images >= 2] // 2
|
images[images >= 2] = 1 + images[images >= 2] // 2
|
||||||
self.files.image("anim_part2.gif", colors[images])
|
self.files.video("anim_part2.webm", colors[images])
|
||||||
yield sum(
|
yield sum(
|
||||||
100 * i_row + i_col
|
100 * row + col
|
||||||
for i_row, row in enumerate(grid)
|
for row in range(grid.n_rows)
|
||||||
for i_col, col in enumerate(row)
|
for col in range(grid.n_columns)
|
||||||
if BoxUtils.is_open_box(col)
|
if grid.is_open_box(row, col)
|
||||||
)
|
)
|
||||||
|
@ -27,20 +27,53 @@ class ProgressHandler(Protocol):
|
|||||||
|
|
||||||
class FileHandler:
|
class FileHandler:
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create(
|
def make_path(self, filename: str) -> Path: ...
|
||||||
self, filename: str, content: bytes, text: bool = False
|
|
||||||
|
@abstractmethod
|
||||||
|
def notify_created(self, path: Path): ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _create(
|
||||||
|
self, path: Path, content: bytes, text: bool = False
|
||||||
) -> Path | None: ...
|
) -> Path | None: ...
|
||||||
|
|
||||||
|
def create(self, filename: str, content: bytes, text: bool = False):
|
||||||
|
path = self._create(self.make_path(filename), content, text)
|
||||||
|
|
||||||
|
if path is not None:
|
||||||
|
self.notify_created(path)
|
||||||
|
|
||||||
def image(self, filename: str, image: NDArray[Any]):
|
def image(self, filename: str, image: NDArray[Any]):
|
||||||
import imageio.v3 as iio
|
import imageio.v3 as iio
|
||||||
from pygifsicle import optimize # type: ignore
|
from pygifsicle import optimize # type: ignore
|
||||||
|
|
||||||
data = iio.imwrite("<bytes>", image, extension=Path(filename).suffix) # type: ignore
|
path = self.make_path(filename)
|
||||||
path = self.create(filename, data, False)
|
|
||||||
|
|
||||||
assert path is not None
|
iio.imwrite(path, image) # type: ignore
|
||||||
optimize(path, options=["--no-warnings"])
|
optimize(path, options=["--no-warnings"])
|
||||||
|
|
||||||
|
self.notify_created(path)
|
||||||
|
|
||||||
|
def video(self, filename: str, video: NDArray[Any]):
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
path = self.make_path(filename)
|
||||||
|
fps = 5
|
||||||
|
out = cv2.VideoWriter(
|
||||||
|
path.as_posix(),
|
||||||
|
cv2.VideoWriter_fourcc(*"vp80"), # type: ignore
|
||||||
|
fps,
|
||||||
|
(video.shape[2], video.shape[1]),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
|
for picture in video:
|
||||||
|
out.write(picture)
|
||||||
|
|
||||||
|
out.release()
|
||||||
|
|
||||||
|
self.notify_created(path)
|
||||||
|
|
||||||
|
|
||||||
class BaseSolver:
|
class BaseSolver:
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
@ -9,9 +10,14 @@ class FileHandlerAPI(FileHandler):
|
|||||||
def __init__(self, folder: Path):
|
def __init__(self, folder: Path):
|
||||||
self.folder: Final = folder
|
self.folder: Final = folder
|
||||||
|
|
||||||
def create(self, filename: str, content: bytes, text: bool = False):
|
def make_path(self, filename: str) -> Path:
|
||||||
self.folder.mkdir(exist_ok=True)
|
|
||||||
with open(self.folder.joinpath(filename), "wb") as fp:
|
|
||||||
fp.write(content)
|
|
||||||
dump_api_message("file", {"filename": filename, "size": len(content)})
|
|
||||||
return self.folder.joinpath(filename)
|
return self.folder.joinpath(filename)
|
||||||
|
|
||||||
|
def notify_created(self, path: Path):
|
||||||
|
dump_api_message("file", {"filename": path.name, "size": os.stat(path).st_size})
|
||||||
|
|
||||||
|
def _create(self, path: Path, content: bytes, text: bool = False):
|
||||||
|
self.folder.mkdir(exist_ok=True)
|
||||||
|
with open(path, "wb") as fp:
|
||||||
|
fp.write(content)
|
||||||
|
return path
|
||||||
|
@ -10,12 +10,17 @@ class SimpleFileHandler(FileHandler):
|
|||||||
self.logger: Final = logger
|
self.logger: Final = logger
|
||||||
self.folder: Final = folder
|
self.folder: Final = folder
|
||||||
|
|
||||||
def create(self, filename: str, content: bytes, text: bool = False):
|
def make_path(self, filename: str) -> Path:
|
||||||
|
return self.folder.joinpath(filename)
|
||||||
|
|
||||||
|
def notify_created(self, path: Path): ...
|
||||||
|
|
||||||
|
def _create(self, path: Path, content: bytes, text: bool = False):
|
||||||
if text:
|
if text:
|
||||||
for line in content.decode("utf-8").splitlines():
|
for line in content.decode("utf-8").splitlines():
|
||||||
self.logger.info(line)
|
self.logger.info(line)
|
||||||
else:
|
else:
|
||||||
self.folder.mkdir(exist_ok=True)
|
self.folder.mkdir(exist_ok=True)
|
||||||
with open(self.folder.joinpath(filename), "wb") as fp:
|
with open(path, "wb") as fp:
|
||||||
fp.write(content)
|
fp.write(content)
|
||||||
return self.folder.joinpath(filename)
|
return path
|
||||||
|
Loading…
Reference in New Issue
Block a user