From: Axy Date: Mon, 9 Mar 2026 16:24:50 +0000 (+0100) Subject: Pattern centering X-Git-Url: https://git.uwuaxy.net/?a=commitdiff_plain;h=f9a70b4d079160e138b62a4b56c73229de24c92e;p=axy%2Fft%2Fa-maze-ing.git Pattern centering --- diff --git a/__main__.py b/__main__.py index 851ac1a..0e23b76 100644 --- a/__main__.py +++ b/__main__.py @@ -27,7 +27,9 @@ maze = Maze(dims) maze.outline() -pattern = Pattern(config.maze_pattern).centered_for(dims) +pattern = Pattern(config.maze_pattern).centered_for( + dims, {CellCoord(5, 5), CellCoord(10, 10)} +) pattern.fill(maze) walls_const = set(maze.walls_full()) @@ -41,7 +43,7 @@ backend.set_style(tilemaps.empty) for wall in maze.all_walls(): for tile in wall.tile_coords(): backend.draw_tile(tile) -for cell in CellCoord(*dims.xy()).all_up_to(): +for cell in CellCoord(dims).all_up_to(): backend.draw_tile(cell.tile_coords()) @@ -73,7 +75,8 @@ def display_maze(maze: Maze) -> None: backend.draw_tile(pixel) maze.clear_dirty() backend.present() - poll_events(2) + poll_events(0) + # poll_events(0) def poll_events(timeout_ms: int = -1) -> None: @@ -94,11 +97,11 @@ def poll_events(timeout_ms: int = -1) -> None: maze_make_perfect(maze, callback=display_maze) maze_make_pacman(maze, walls_const, callback=display_maze) -while True: +while False: maze_make_perfect(maze, callback=display_maze) - poll_events(200) - # maze_make_pacman(maze, walls_const, callback=display_maze) - maze_make_empty(maze, walls_const, callback=display_maze) - poll_events(200) + # poll_events(200) + maze_make_pacman(maze, walls_const, callback=display_maze) + # maze_make_empty(maze, walls_const, callback=display_maze) + # poll_events(200) maze._rebuild() poll_events() diff --git a/amazeing/config/config_parser.py b/amazeing/config/config_parser.py index 6ab4387..0b20ed7 100644 --- a/amazeing/config/config_parser.py +++ b/amazeing/config/config_parser.py @@ -14,6 +14,7 @@ from .parser_combinator import ( many, many_count, none_of, + null_parser, one_of, pair, parser_complete, diff --git a/amazeing/maze_class/maze_pattern.py b/amazeing/maze_class/maze_pattern.py index 5c8df9a..ceaaa21 100644 --- a/amazeing/maze_class/maze_pattern.py +++ b/amazeing/maze_class/maze_pattern.py @@ -1,3 +1,4 @@ +from collections.abc import Iterable from amazeing.maze_display.backend import IVec2 from .maze import Maze from .maze_walls import CellCoord @@ -13,8 +14,11 @@ class Pattern: " # ###", ] - def __init__(self, pat: list[str]) -> None: - self.cells: set[CellCoord] = { + def __init__(self, pat: list[str] | set[CellCoord]) -> None: + if isinstance(pat, set): + self.__cells: set[CellCoord] = pat + return + self.__cells: set[CellCoord] = { CellCoord(x, y) for y, line in enumerate(pat) for x, char in enumerate(line) @@ -22,31 +26,79 @@ class Pattern: } def offset(self, by: IVec2) -> "Pattern": - pattern: Pattern = Pattern([]) - pattern.cells = {cell.offset(by) for cell in self.cells} - return pattern + return Pattern({CellCoord(cell + by) for cell in self.__cells}) + + def flood_filled(self) -> "Pattern": + dims = self.dims() + border = {CellCoord(-1, -1)} + reachable = set() + full = { + CellCoord(x, y) for x in range(0, dims.x) for y in range(0, dims.y) + } + + def coord_propagate(coord: CellCoord) -> Iterable[CellCoord]: + return ( + cell + for cell in coord.neighbours_unchecked() + if cell not in self.__cells + and cell not in reachable + and cell not in border + and cell.x >= -1 + and cell.x <= dims.x + and cell.y >= -1 + and cell.y <= dims.y + ) + + while len(border) != 0: + for e in border: + reachable.add(e) + border.remove(e) + for cell in coord_propagate(e): + reachable.add(cell) + border.add(cell) + break + return Pattern(full - reachable) + + def add_cell(self, cell: CellCoord) -> None: + self.__cells.add(cell) + + def remove_cell(self, cell: CellCoord) -> None: + self.__cells.discard(cell) def dims(self) -> IVec2: dim_by: Callable[[Callable[[CellCoord], int]], int] = lambda f: ( - max(map(lambda c: f(c) + 1, self.cells), default=0) - - min(map(f, self.cells), default=0) + max(map(lambda c: f(c) + 1, self.__cells), default=0) + - min(map(f, self.__cells), default=0) ) - return IVec2(dim_by(CellCoord.x), dim_by(CellCoord.y)) + return IVec2(dim_by(lambda e: e.x), dim_by(lambda e: e.y)) def normalized(self) -> "Pattern": min_by: Callable[[Callable[[CellCoord], int]], int] = lambda f: min( - map(f, self.cells), default=0 + map(f, self.__cells), default=0 ) - offset = IVec2(-min_by(CellCoord.x), -min_by(CellCoord.y)) + offset = IVec2(-min_by(lambda e: e.x), -min_by(lambda e: e.y)) return self.offset(offset) - def centered_for(self, canvas: IVec2) -> "Pattern": + def mirrored(self) -> "Pattern": + return Pattern({CellCoord(IVec2.splat(0) - e) for e in self.__cells}) + + def centered_for( + self, canvas: IVec2, excluding: set[CellCoord] = set() + ) -> "Pattern": normalized: Pattern = self.normalized() + negative = normalized.flood_filled().mirrored() dims = normalized.dims() - offset = (canvas - dims) // 2 - return normalized.offset(offset) + slots = set(CellCoord(canvas - dims + 1).all_up_to()) + for excluded in excluding: + slots -= negative.offset(excluded).__cells + if len(slots) == 0: + return Pattern([]) + ideal = (canvas - dims) // 2 + slot = min(slots, key=lambda e: sum(((e := e - ideal) * e).xy())) + + return normalized.offset(slot) def fill(self, maze: "Maze") -> None: - for cell in self.cells: + for cell in self.__cells: for wall in cell.walls(): maze.fill_wall(wall) diff --git a/amazeing/maze_class/maze_walls.py b/amazeing/maze_class/maze_walls.py index 6283ff3..bc06080 100644 --- a/amazeing/maze_class/maze_walls.py +++ b/amazeing/maze_class/maze_walls.py @@ -1,5 +1,5 @@ from enum import Enum, auto -from typing import Iterable, Optional, cast +from typing import Iterable, Optional, cast, overload from ..maze_display import IVec2 @@ -136,23 +136,18 @@ class WallCoord: ] -class CellCoord: - def __init__(self, x: int, y: int) -> None: - self.__x: int = x - self.__y: int = y +class CellCoord(IVec2): + @overload + def __init__(self, val: IVec2, /) -> None: ... - def __members(self) -> tuple[int, int]: - return (self.__x, self.__y) + @overload + def __init__(self, x: int, y: int, /) -> None: ... - def __eq__(self, value: object, /) -> bool: - return ( - self.__members() == cast(CellCoord, value).__members() - if type(self) is type(value) - else False - ) - - def __hash__(self) -> int: - return hash(self.__members()) + def __init__(self, a: IVec2 | int, b: int = 0) -> None: + if isinstance(a, int): + super().__init__(a, b) + else: + super().__init__(a.x, a.y) def walls(self) -> Iterable[WallCoord]: return map( @@ -163,29 +158,26 @@ class CellCoord: def get_wall(self, cardinal: Cardinal) -> WallCoord: match cardinal: case Cardinal.NORTH: - return WallCoord(Orientation.HORIZONTAL, self.__y, self.__x) + return WallCoord(Orientation.HORIZONTAL, self.y, self.x) case Cardinal.SOUTH: - return WallCoord( - Orientation.HORIZONTAL, self.__y + 1, self.__x - ) + return WallCoord(Orientation.HORIZONTAL, self.y + 1, self.x) case Cardinal.EAST: - return WallCoord(Orientation.VERTICAL, self.__x, self.__y) + return WallCoord(Orientation.VERTICAL, self.x, self.y) case Cardinal.WEST: - return WallCoord(Orientation.VERTICAL, self.__x + 1, self.__y) + return WallCoord(Orientation.VERTICAL, self.x + 1, self.y) def tile_coords(self) -> IVec2: - return IVec2(self.__x * 2 + 1, self.__y * 2 + 1) + return IVec2(self.x * 2 + 1, self.y * 2 + 1) def offset(self, by: IVec2) -> "CellCoord": - return CellCoord(self.__x + by.x, self.__y + by.y) - - def x(self) -> int: - return self.__x - - def y(self) -> int: - return self.__y + return CellCoord(self + by) def all_up_to(self) -> Iterable["CellCoord"]: - for x in range(0, self.__x): - for y in range(0, self.__y): + for x in range(0, self.x): + for y in range(0, self.y): yield CellCoord(x, y) + + def neighbours_unchecked(self) -> Iterable["CellCoord"]: + return map( + self.offset, [IVec2(-1, 0), IVec2(1, 0), IVec2(0, -1), IVec2(0, 1)] + ) diff --git a/amazeing/maze_display/backend.py b/amazeing/maze_display/backend.py index 910058f..6a8f9c0 100644 --- a/amazeing/maze_display/backend.py +++ b/amazeing/maze_display/backend.py @@ -20,15 +20,15 @@ class IVec2[T = int]: return f"{self.x, self.y}" @staticmethod - def with_op( - op: Callable[[T, T], T], - ) -> Callable[["IVec2[T]", "T | IVec2[T]"], "IVec2[T]"]: + def with_op[T2]( + op: Callable[[T, T], T2], + ) -> Callable[["IVec2[T]", "T | IVec2[T]"], "IVec2[T2]"]: return lambda self, other: IVec2( op( self.x, ( other - if isinstance(other, type(self)) + if isinstance(other, IVec2) else (other := type(self).splat(cast(T, other))) ).x, ), @@ -58,6 +58,9 @@ class IVec2[T = int]: return False return self.x == value.x and self.y == value.y + def __hash__(self) -> int: + return hash((self.x, self.y)) + def xy(self) -> tuple[T, T]: return (self.x, self.y) diff --git a/example.conf b/example.conf index dc3427d..04227c4 100644 --- a/example.conf +++ b/example.conf @@ -1,5 +1,5 @@ -WIDTH=25 -HEIGHT=25 +WIDTH=20 +HEIGHT=20 ENTRY=2,5 #EXIT=100,100 OUTPUT_FILE=test @@ -7,9 +7,9 @@ PERFECT=False SEED=111 TILEMAP_WALL_SIZE=2,1 TILEMAP_CELL_SIZE=4,2 -TILEMAP_FULL="{1000,100,100:1000,1000,1000}###{1000,1000,1000:1000,1000,1000}###" -TILEMAP_FULL="{100,1000,100:1000,1000,1000}###{1000,1000,1000:1000,1000,1000}###" -TILEMAP_FULL="{100,100,1000:1000,1000,1000}###{1000,1000,1000:1000,1000,1000}###" +TILEMAP_FULL="{1000,1000,1000:1000,1000,1000}######" +TILEMAP_FULL="{1000,1000,1000:1000,1000,1000}######" +TILEMAP_FULL="{1000,1000,1000:1000,1000,1000}######" TILEMAP_EMPTY="{0,0,0:0,0,0} " TILEMAP_EMPTY="{0,0,0:0,0,0} " TILEMAP_EMPTY="{0,0,0:0,0,0} " @@ -18,11 +18,19 @@ TILEMAP_BACKGROUND="{1000,1000,1000:0,0,0}## " TILEMAP_BACKGROUND="{1000,1000,1000:0,0,0}###### " TILEMAP_BACKGROUND="{1000,1000,1000:0,0,0} ## " TILEMAP_BACKGROUND="{1000,1000,1000:0,0,0}## ## " -MAZE_PATTERN=" # # " -MAZE_PATTERN=" # # " -MAZE_PATTERN=" # " -MAZE_PATTERN=" " -MAZE_PATTERN=" # # " -MAZE_PATTERN=" " +#MAZE_PATTERN=" # # " +#MAZE_PATTERN=" # # " +#MAZE_PATTERN=" # " +#MAZE_PATTERN=" " +#MAZE_PATTERN=" # # " +#MAZE_PATTERN=" " +#MAZE_PATTERN="# # #" +#MAZE_PATTERN=" ## ## " +MAZE_PATTERN="#######" +MAZE_PATTERN="# # # #" MAZE_PATTERN="# # #" -MAZE_PATTERN=" ## ## " +MAZE_PATTERN="# #" +MAZE_PATTERN="## ####" +MAZE_PATTERN="# #" +MAZE_PATTERN="# # #" +MAZE_PATTERN="#######"