From: Lucas Flores Date: Tue, 10 Feb 2026 11:10:34 +0000 (+0100) Subject: A peu pres nettoye sah X-Git-Url: https://git.uwuaxy.net/sitemap.xml?a=commitdiff_plain;h=ea67a3ceca859091ef773636910f580075ecc5a7;p=axy%2Fft%2Fa-maze-ing.git A peu pres nettoye sah --- diff --git a/__main__.py b/__main__.py index 949c77f..a4a3217 100644 --- a/__main__.py +++ b/__main__.py @@ -1,10 +1,10 @@ -from amazeing import Maze, TTYBackend, Pattern -import random +from amazeing import (Maze, TTYBackend, Pattern, + perfect_to_imperfect, make_perfect) from time import sleep # random.seed(42) -dims = (25, 25) +dims = (10, 10) maze = Maze(dims) @@ -26,35 +26,8 @@ def display_maze(maze: Maze) -> None: sleep(0.05) -empty = list(maze.walls_empty()) -random.shuffle(empty) -for wall in empty: - if maze.wall_bisects(wall): - continue - maze.fill_wall(wall) - # display_maze(maze) - -iterations = 0 -for _ in range(0, iterations): - walls = [wall for wall in maze.walls_full() if wall not in walls_const] - random.shuffle(walls) - for wall in walls: - if not maze.wall_cuts_cycle(wall): - continue - if maze.wall_leaf_neighbours(wall): - continue - maze._remove_wall(wall) - # display_maze(maze) - walls = [wall for wall in maze.walls_full() if wall not in walls_const] - random.shuffle(walls) - for wall in walls: - if not maze.wall_cuts_cycle(wall): - continue - leaf_neighbours = maze.wall_leaf_neighbours(wall) - if len(leaf_neighbours) == 0: - continue - maze._remove_wall(wall) - maze.fill_wall(random.choice(leaf_neighbours)) - # display_maze(maze) +make_perfect(maze) +display_maze(maze) +perfect_to_imperfect(maze, walls_const) maze._rebuild() display_maze(maze) diff --git a/amazeing/__init__.py b/amazeing/__init__.py index 6a7d189..7569db1 100644 --- a/amazeing/__init__.py +++ b/amazeing/__init__.py @@ -1,13 +1,18 @@ __version__ = "0.0.0" +__author__ = "luflores & agilliar" -from .display import Backend, PixelCoord, TTYBackend -from .maze import WallCoord, Maze, Pattern +from amazeing.maze_class import WallCoord, Maze, Pattern +from amazeing.maze_display import Backend, PixelCoord, TTYBackend +from .perfect_to_imperfect import perfect_to_imperfect +from .prototype_perfect import make_perfect __all__ = [ - "Backend", - "PixelCoord", - "TTYBackend", "WallCoord", "Maze", "Pattern", + "Backend", + "PixelCoord", + "TTYBackend", + "perfect_to_imperfect", + "make_perfect" ] diff --git a/amazeing/maze.py b/amazeing/maze.py deleted file mode 100644 index e0c1174..0000000 --- a/amazeing/maze.py +++ /dev/null @@ -1,364 +0,0 @@ -from enum import Enum, auto -from typing import Callable, Generator, Iterable, Optional, cast -import random - -from amazeing.display import PixelCoord - - -class NetworkID: - __uuid_gen: int = 0 - - def __init__(self) -> None: - self.uuid: int = NetworkID.__uuid_gen - NetworkID.__uuid_gen += 1 - - -class MazeWall: - def __init__(self, network_id: Optional[NetworkID] = None) -> None: - self.network_id: Optional[NetworkID] = network_id - - def is_full(self) -> bool: - return self.network_id is not None - - -class Orientation(Enum): - HORIZONTAL = auto() - VERTICAL = auto() - - def opposite(self) -> "Orientation": - if self == Orientation.HORIZONTAL: - return Orientation.VERTICAL - return Orientation.HORIZONTAL - - -class WallCoord: - def __init__(self, orientation: Orientation, a: int, b: int) -> None: - self.orientation: Orientation = orientation - self.a: int = a - self.b: int = b - - def __members(self) -> tuple[int, int, Orientation]: - return (self.a, self.b, self.orientation) - - def __eq__(self, value: object, /) -> bool: - return ( - self.__members() == cast(WallCoord, value).__members() - if type(self) is type(value) - else False - ) - - def __hash__(self) -> int: - return hash(self.__members()) - - def a_neighbours(self) -> list["WallCoord"]: - return [ - WallCoord(self.orientation.opposite(), self.b, self.a - 1), - WallCoord(self.orientation, self.a, self.b - 1), - WallCoord(self.orientation.opposite(), self.b, self.a), - ] - - def b_neighbours(self) -> list["WallCoord"]: - return [ - WallCoord(self.orientation.opposite(), self.b + 1, self.a - 1), - WallCoord(self.orientation, self.a, self.b + 1), - WallCoord(self.orientation.opposite(), self.b + 1, self.a), - ] - - def neighbours(self) -> list["WallCoord"]: - return self.a_neighbours() + self.b_neighbours() - - def pixel_coords(self) -> Iterable[PixelCoord]: - a: Iterable[int] = [self.a * 2] - b: Iterable[int] = [self.b * 2, self.b * 2 + 1, self.b * 2 + 2] - x_iter: Iterable[int] = ( - a if self.orientation == Orientation.VERTICAL else b - ) - y_iter: Iterable[int] = ( - a if self.orientation == Orientation.HORIZONTAL else b - ) - return (PixelCoord(x, y) for x in x_iter for y in y_iter) - - def neighbour_cells(self) -> list["CellCoord"]: - if self.orientation == Orientation.HORIZONTAL: - return [ - CellCoord(self.b, self.a), - CellCoord(self.b, self.a - 1), - ] - return [ - CellCoord(self.a, self.b), - CellCoord(self.a - 1, self.b), - ] - - -class CellCoord: - def __init__(self, x: int, y: int) -> None: - self.__x: int = x - self.__y: int = y - - def __members(self) -> tuple[int, int]: - return (self.__x, self.__y) - - 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 walls(self) -> Iterable[WallCoord]: - return [ - WallCoord(Orientation.HORIZONTAL, self.__y, self.__x), - WallCoord(Orientation.HORIZONTAL, self.__y + 1, self.__x), - WallCoord(Orientation.VERTICAL, self.__x, self.__y), - WallCoord(Orientation.VERTICAL, self.__x + 1, self.__y), - ] - - def pixel_coords(self) -> Iterable[PixelCoord]: - return [PixelCoord(self.__x * 2 + 1, self.__y * 2 + 1)] - - def offset(self, by: tuple[int, int]) -> "CellCoord": - return CellCoord(self.__x + by[0], self.__y + by[1]) - - def x(self) -> int: - return self.__x - - def y(self) -> int: - return self.__y - - -class Pattern: - FT_PATTERN: list[str] = [ - "# ###", - "# #", - "### ###", - " # # ", - " # ###", - ] - - def __init__(self, pat: list[str]) -> None: - self.cells: set[CellCoord] = { - CellCoord(x, y) - for y, line in enumerate(pat) - for x, char in enumerate(line) - if char != " " - } - - def offset(self, by: tuple[int, int]) -> "Pattern": - pattern: Pattern = Pattern([]) - pattern.cells = {cell.offset(by) for cell in self.cells} - return pattern - - def dims(self) -> tuple[int, int]: - 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) - ) - return (dim_by(CellCoord.x), dim_by(CellCoord.y)) - - def normalized(self) -> "Pattern": - min_by: Callable[[Callable[[CellCoord], int]], int] = lambda f: min( - map(f, self.cells), default=0 - ) - offset: tuple[int, int] = (-min_by(CellCoord.x), -min_by(CellCoord.y)) - return self.offset(offset) - - def centered_for(self, canvas: tuple[int, int]) -> "Pattern": - normalized: Pattern = self.normalized() - dims: tuple[int, int] = normalized.dims() - offset: tuple[int, int] = ( - (canvas[0] - dims[0]) // 2, - (canvas[1] - dims[1]) // 2, - ) - return normalized.offset(offset) - - def fill(self, maze: "Maze") -> None: - for cell in self.cells: - for wall in cell.walls(): - maze.fill_wall(wall) - - -class WallNetwork: - def __init__(self) -> None: - self.walls: set[WallCoord] = set() - - def size(self) -> int: - return len(self.walls) - - def add_wall(self, id: WallCoord) -> None: - self.walls.add(id) - - def remove_wall(self, id: WallCoord) -> None: - self.walls.remove(id) - - -class Maze: - def __init__(self, dims: tuple[int, int]) -> None: - self.__width: int = dims[0] - self.__height: int = dims[1] - self._clear() - - def _clear(self) -> None: - # list of lines - self.horizontal: list[list[MazeWall]] = [ - [MazeWall() for _ in range(0, self.__width)] - for _ in range(0, self.__height + 1) - ] - # list of lines - self.vertical: list[list[MazeWall]] = [ - [MazeWall() for _ in range(0, self.__height)] - for _ in range(0, self.__width + 1) - ] - self.networks: dict[NetworkID, WallNetwork] = {} - - def _rebuild(self) -> None: - """ - rebuilds the maze to recompute proper connectivity values - """ - walls: set[WallCoord] = {wall for wall in self.walls_full()} - self._clear() - for wall in walls: - self.fill_wall(wall) - - def __get_wall(self, coord: WallCoord) -> MazeWall: - if coord.orientation == Orientation.HORIZONTAL: - return self.horizontal[coord.a][coord.b] - return self.vertical[coord.a][coord.b] - - def _remove_wall(self, coord: WallCoord) -> None: - """ - removes the wall, without updating network connectivity - """ - wall: MazeWall = self.__get_wall(coord) - if wall.network_id is not None: - self.networks[wall.network_id].remove_wall(coord) - wall.network_id = None - - def all_walls(self) -> Generator[WallCoord]: - for orientation, a_count, b_count in [ - (Orientation.HORIZONTAL, self.__height + 1, self.__width), - (Orientation.VERTICAL, self.__width + 1, self.__height), - ]: - for a in range(0, a_count): - for b in range(0, b_count): - yield WallCoord(orientation, a, b) - - def _check_coord(self, coord: WallCoord) -> bool: - if coord.a < 0 or coord.b < 0: - return False - (a_max, b_max) = ( - (self.__height, self.__width - 1) - if coord.orientation == Orientation.HORIZONTAL - else (self.__width, self.__height - 1) - ) - if coord.a > a_max or coord.b > b_max: - return False - return True - - def get_walls_checked(self, ids: list[WallCoord]) -> list[MazeWall]: - return [self.__get_wall(id) for id in ids if self._check_coord(id)] - - def get_neighbours(self, id: WallCoord) -> list[MazeWall]: - return self.get_walls_checked(id.neighbours()) - - def _fill_wall_alone(self, id: WallCoord, wall: MazeWall) -> None: - network_id: NetworkID = NetworkID() - wall.network_id = network_id - network = WallNetwork() - network.add_wall(id) - self.networks[network_id] = network - - def fill_wall(self, id: WallCoord) -> None: - wall: MazeWall = self.__get_wall(id) - - if wall.is_full(): - return - - networks = { - cast(NetworkID, neighbour.network_id) - for neighbour in self.get_neighbours(id) - if neighbour.is_full() - } - - if len(networks) == 0: - return self._fill_wall_alone(id, wall) - - dest_id = max(networks, key=lambda n: self.networks[n].size()) - dest = self.networks[dest_id] - - wall.network_id = dest_id - dest.add_wall(id) - - for to_merge in filter(lambda n: n != dest_id, networks): - for curr in self.networks[to_merge].walls: - self.__get_wall(curr).network_id = dest_id - dest.add_wall(curr) - - del self.networks[to_merge] - - def outline(self) -> None: - if self.__width < 1 or self.__height < 1: - return - for orientation, a_iter, b_iter in [ - (Orientation.VERTICAL, (0, self.__width), range(0, self.__height)), - ( - Orientation.HORIZONTAL, - (0, self.__height), - range(0, self.__width), - ), - ]: - for a in a_iter: - for b in b_iter: - self.fill_wall(WallCoord(orientation, a, b)) - - def walls_full(self) -> Iterable[WallCoord]: - return filter(lambda w: self.__get_wall(w).is_full(), self.all_walls()) - - def walls_empty(self) -> Iterable[WallCoord]: - return filter( - lambda w: not self.__get_wall(w).is_full(), self.all_walls() - ) - - def wall_bisects(self, wall: WallCoord) -> bool: - a = { - cast(NetworkID, neighbour.network_id) - for neighbour in self.get_walls_checked(wall.a_neighbours()) - if neighbour.is_full() - } - b = { - cast(NetworkID, neighbour.network_id) - for neighbour in self.get_walls_checked(wall.b_neighbours()) - if neighbour.is_full() - } - return len(a & b) != 0 - - def wall_cuts_cycle(self, wall: WallCoord) -> bool: - return any( - ( - len( - [ - () - for wall in self.get_walls_checked(list(cell.walls())) - if wall.is_full() - ] - ) - >= 3 - if self.__get_wall(wall).is_full() - else 2 - ) - for cell in wall.neighbour_cells() - ) - - def wall_leaf_neighbours(self, coord: WallCoord) -> list[WallCoord]: - leaf_f: Callable[ - [Callable[[WallCoord], list[WallCoord]]], list[WallCoord] - ] = lambda f: ( - list(filter(lambda c: self._check_coord(c), f(coord))) - if all( - not wall.is_full() for wall in self.get_walls_checked(f(coord)) - ) - else [] - ) - return leaf_f(WallCoord.a_neighbours) + leaf_f(WallCoord.b_neighbours) diff --git a/amazeing/maze_class/__init__.py b/amazeing/maze_class/__init__.py new file mode 100644 index 0000000..210ff0e --- /dev/null +++ b/amazeing/maze_class/__init__.py @@ -0,0 +1,13 @@ +from .maze import Maze +from .maze_pattern import Pattern +from .maze_walls import (MazeWall, NetworkID, Orientation, + WallCoord, WallNetwork) + +all = [ + Maze, + Pattern, + MazeWall, + NetworkID, + Orientation, + WallCoord, + WallNetwork] diff --git a/amazeing/maze_class/maze.py b/amazeing/maze_class/maze.py new file mode 100644 index 0000000..e94ff02 --- /dev/null +++ b/amazeing/maze_class/maze.py @@ -0,0 +1,173 @@ +from typing import Callable, Generator, Iterable, cast +from .maze_walls import (MazeWall, NetworkID, + Orientation, WallCoord, WallNetwork) + + +class Maze: + def __init__(self, dims: tuple[int, int]) -> None: + self.__width: int = dims[0] + self.__height: int = dims[1] + self._clear() + + def _clear(self) -> None: + # list of lines + self.horizontal: list[list[MazeWall]] = [ + [MazeWall() for _ in range(0, self.__width)] + for _ in range(0, self.__height + 1) + ] + # list of lines + self.vertical: list[list[MazeWall]] = [ + [MazeWall() for _ in range(0, self.__height)] + for _ in range(0, self.__width + 1) + ] + self.networks: dict[NetworkID, WallNetwork] = {} + + def _rebuild(self) -> None: + """ + rebuilds the maze to recompute proper connectivity values + """ + walls: set[WallCoord] = {wall for wall in self.walls_full()} + self._clear() + for wall in walls: + self.fill_wall(wall) + + def __get_wall(self, coord: WallCoord) -> MazeWall: + if coord.orientation == Orientation.HORIZONTAL: + return self.horizontal[coord.a][coord.b] + return self.vertical[coord.a][coord.b] + + def _remove_wall(self, coord: WallCoord) -> None: + """ + removes the wall, without updating network connectivity + """ + wall: MazeWall = self.__get_wall(coord) + if wall.network_id is not None: + self.networks[wall.network_id].remove_wall(coord) + wall.network_id = None + + def all_walls(self) -> Generator[WallCoord]: + for orientation, a_count, b_count in [ + (Orientation.HORIZONTAL, self.__height + 1, self.__width), + (Orientation.VERTICAL, self.__width + 1, self.__height), + ]: + for a in range(0, a_count): + for b in range(0, b_count): + yield WallCoord(orientation, a, b) + + def _check_coord(self, coord: WallCoord) -> bool: + if coord.a < 0 or coord.b < 0: + return False + (a_max, b_max) = ( + (self.__height, self.__width - 1) + if coord.orientation == Orientation.HORIZONTAL + else (self.__width, self.__height - 1) + ) + if coord.a > a_max or coord.b > b_max: + return False + return True + + def get_walls_checked(self, ids: list[WallCoord]) -> list[MazeWall]: + return [self.__get_wall(id) for id in ids if self._check_coord(id)] + + def get_neighbours(self, id: WallCoord) -> list[MazeWall]: + return self.get_walls_checked(id.neighbours()) + + def _fill_wall_alone(self, id: WallCoord, wall: MazeWall) -> None: + network_id: NetworkID = NetworkID() + wall.network_id = network_id + network = WallNetwork() + network.add_wall(id) + self.networks[network_id] = network + + def fill_wall(self, id: WallCoord) -> None: + wall: MazeWall = self.__get_wall(id) + + if wall.is_full(): + return + + networks = { + cast(NetworkID, neighbour.network_id) + for neighbour in self.get_neighbours(id) + if neighbour.is_full() + } + + if len(networks) == 0: + return self._fill_wall_alone(id, wall) + + dest_id = max(networks, key=lambda n: self.networks[n].size()) + dest = self.networks[dest_id] + + wall.network_id = dest_id + dest.add_wall(id) + + for to_merge in filter(lambda n: n != dest_id, networks): + for curr in self.networks[to_merge].walls: + self.__get_wall(curr).network_id = dest_id + dest.add_wall(curr) + + del self.networks[to_merge] + + def outline(self) -> None: + if self.__width < 1 or self.__height < 1: + return + for orientation, a_iter, b_iter in [ + (Orientation.VERTICAL, (0, self.__width), range(0, self.__height)), + ( + Orientation.HORIZONTAL, + (0, self.__height), + range(0, self.__width), + ), + ]: + for a in a_iter: + for b in b_iter: + self.fill_wall(WallCoord(orientation, a, b)) + + def walls_full(self) -> Iterable[WallCoord]: + return filter(lambda w: self.__get_wall(w).is_full(), self.all_walls()) + + def walls_empty(self) -> Iterable[WallCoord]: + return filter( + lambda w: not self.__get_wall(w).is_full(), self.all_walls() + ) + + def wall_bisects(self, wall: WallCoord) -> bool: + a = { + cast(NetworkID, neighbour.network_id) + for neighbour in self.get_walls_checked(wall.a_neighbours()) + if neighbour.is_full() + } + b = { + cast(NetworkID, neighbour.network_id) + for neighbour in self.get_walls_checked(wall.b_neighbours()) + if neighbour.is_full() + } + return len(a & b) != 0 + + def wall_cuts_cycle(self, wall: WallCoord) -> bool: + return any( + ( + len( + [ + () + for wall in self.get_walls_checked(list(cell.walls())) + if wall.is_full() + ] + ) + >= 3 + if self.__get_wall(wall).is_full() + else 2 + ) + for cell in wall.neighbour_cells() + ) + + def wall_leaf_neighbours(self, coord: WallCoord) -> list[WallCoord]: + leaf_f: Callable[ + [Callable[[WallCoord], list[WallCoord]]], list[WallCoord] + ] = lambda f: ( + list(filter(lambda c: self._check_coord(c), f(coord))) + if all( + not wall.is_full() for wall in self.get_walls_checked(f(coord)) + ) + else [] + ) + return leaf_f(WallCoord.a_neighbours) + leaf_f(WallCoord.b_neighbours) diff --git a/amazeing/maze_class/maze_pattern.py b/amazeing/maze_class/maze_pattern.py new file mode 100644 index 0000000..2e5ed62 --- /dev/null +++ b/amazeing/maze_class/maze_pattern.py @@ -0,0 +1,54 @@ +from .maze import Maze +from .maze_walls import CellCoord +from typing import Callable + + +class Pattern: + FT_PATTERN: list[str] = [ + "# ###", + "# #", + "### ###", + " # # ", + " # ###", + ] + + def __init__(self, pat: list[str]) -> None: + self.cells: set[CellCoord] = { + CellCoord(x, y) + for y, line in enumerate(pat) + for x, char in enumerate(line) + if char != " " + } + + def offset(self, by: tuple[int, int]) -> "Pattern": + pattern: Pattern = Pattern([]) + pattern.cells = {cell.offset(by) for cell in self.cells} + return pattern + + def dims(self) -> tuple[int, int]: + 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) + ) + return (dim_by(CellCoord.x), dim_by(CellCoord.y)) + + def normalized(self) -> "Pattern": + min_by: Callable[[Callable[[CellCoord], int]], int] = lambda f: min( + map(f, self.cells), default=0 + ) + offset: tuple[int, int] = (-min_by(CellCoord.x), -min_by(CellCoord.y)) + return self.offset(offset) + + def centered_for(self, canvas: tuple[int, int]) -> "Pattern": + normalized: Pattern = self.normalized() + dims: tuple[int, int] = normalized.dims() + offset: tuple[int, int] = ( + (canvas[0] - dims[0]) // 2, + (canvas[1] - dims[1]) // 2, + ) + return normalized.offset(offset) + + def fill(self, maze: "Maze") -> None: + 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 new file mode 100644 index 0000000..d4f0faf --- /dev/null +++ b/amazeing/maze_class/maze_walls.py @@ -0,0 +1,142 @@ +from enum import Enum, auto +from typing import Iterable, Optional, cast + +from ..maze_display import PixelCoord + + +class NetworkID: + __uuid_gen: int = 0 + + def __init__(self) -> None: + self.uuid: int = NetworkID.__uuid_gen + NetworkID.__uuid_gen += 1 + + +class MazeWall: + def __init__(self, network_id: Optional[NetworkID] = None) -> None: + self.network_id: Optional[NetworkID] = network_id + + def is_full(self) -> bool: + return self.network_id is not None + + +class Orientation(Enum): + HORIZONTAL = auto() + VERTICAL = auto() + + def opposite(self) -> "Orientation": + if self == Orientation.HORIZONTAL: + return Orientation.VERTICAL + return Orientation.HORIZONTAL + + +class WallCoord: + def __init__(self, orientation: Orientation, a: int, b: int) -> None: + self.orientation: Orientation = orientation + self.a: int = a + self.b: int = b + + def __members(self) -> tuple[int, int, Orientation]: + return (self.a, self.b, self.orientation) + + def __eq__(self, value: object, /) -> bool: + return ( + self.__members() == cast(WallCoord, value).__members() + if type(self) is type(value) + else False + ) + + def __hash__(self) -> int: + return hash(self.__members()) + + def a_neighbours(self) -> list["WallCoord"]: + return [ + WallCoord(self.orientation.opposite(), self.b, self.a - 1), + WallCoord(self.orientation, self.a, self.b - 1), + WallCoord(self.orientation.opposite(), self.b, self.a), + ] + + def b_neighbours(self) -> list["WallCoord"]: + return [ + WallCoord(self.orientation.opposite(), self.b + 1, self.a - 1), + WallCoord(self.orientation, self.a, self.b + 1), + WallCoord(self.orientation.opposite(), self.b + 1, self.a), + ] + + def neighbours(self) -> list["WallCoord"]: + return self.a_neighbours() + self.b_neighbours() + + def pixel_coords(self) -> Iterable[PixelCoord]: + a: Iterable[int] = [self.a * 2] + b: Iterable[int] = [self.b * 2, self.b * 2 + 1, self.b * 2 + 2] + x_iter: Iterable[int] = ( + a if self.orientation == Orientation.VERTICAL else b + ) + y_iter: Iterable[int] = ( + a if self.orientation == Orientation.HORIZONTAL else b + ) + return (PixelCoord(x, y) for x in x_iter for y in y_iter) + + def neighbour_cells(self) -> list["CellCoord"]: + if self.orientation == Orientation.HORIZONTAL: + return [ + CellCoord(self.b, self.a), + CellCoord(self.b, self.a - 1), + ] + return [ + CellCoord(self.a, self.b), + CellCoord(self.a - 1, self.b), + ] + + +class CellCoord: + def __init__(self, x: int, y: int) -> None: + self.__x: int = x + self.__y: int = y + + def __members(self) -> tuple[int, int]: + return (self.__x, self.__y) + + 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 walls(self) -> Iterable[WallCoord]: + return [ + WallCoord(Orientation.HORIZONTAL, self.__y, self.__x), + WallCoord(Orientation.HORIZONTAL, self.__y + 1, self.__x), + WallCoord(Orientation.VERTICAL, self.__x, self.__y), + WallCoord(Orientation.VERTICAL, self.__x + 1, self.__y), + ] + + def pixel_coords(self) -> Iterable[PixelCoord]: + return [PixelCoord(self.__x * 2 + 1, self.__y * 2 + 1)] + + def offset(self, by: tuple[int, int]) -> "CellCoord": + return CellCoord(self.__x + by[0], self.__y + by[1]) + + def x(self) -> int: + return self.__x + + def y(self) -> int: + return self.__y + + +class WallNetwork: + def __init__(self) -> None: + self.walls: set[WallCoord] = set() + + def size(self) -> int: + return len(self.walls) + + def add_wall(self, id: WallCoord) -> None: + self.walls.add(id) + + def remove_wall(self, id: WallCoord) -> None: + self.walls.remove(id) diff --git a/amazeing/display.py b/amazeing/maze_display/TTYdisplay.py similarity index 75% rename from amazeing/display.py rename to amazeing/maze_display/TTYdisplay.py index c1619c5..57d3f47 100644 --- a/amazeing/display.py +++ b/amazeing/maze_display/TTYdisplay.py @@ -1,20 +1,11 @@ -from abc import ABC, abstractmethod +from .backend import Backend, PixelCoord import sys -class PixelCoord: - def __init__(self, x: int, y: int) -> None: - self.x: int = x - self.y: int = y - - -class Backend(ABC): - @abstractmethod - def draw_pixel(self, pos: PixelCoord) -> None: - pass - - class TTYBackend(Backend): + """ + Takes the ABC Backend and displays the maze in the terminal. + """ def __init__( self, maze_width: int, maze_height: int, style: str = " " ) -> None: diff --git a/amazeing/maze_display/__init__.py b/amazeing/maze_display/__init__.py new file mode 100644 index 0000000..972cb85 --- /dev/null +++ b/amazeing/maze_display/__init__.py @@ -0,0 +1,11 @@ +__version__ = "0.0.0" +__author__ = "luflores & agilliar" + +from .backend import Backend, PixelCoord +from .TTYdisplay import TTYBackend + +__all__ = [ + "Backend", + "PixelCoord", + "TTYBackend", +] diff --git a/amazeing/maze_display/backend.py b/amazeing/maze_display/backend.py new file mode 100644 index 0000000..7f17bed --- /dev/null +++ b/amazeing/maze_display/backend.py @@ -0,0 +1,18 @@ +from abc import ABC, abstractmethod + + +class PixelCoord: + def __init__(self, x: int, y: int) -> None: + self.x: int = x + self.y: int = y + + +class Backend(ABC): + """ + ABC for the maze display. + defining how the maze should be drawn. + (PixelCoord) + """ + @abstractmethod + def draw_pixel(self, pos: PixelCoord) -> None: + pass diff --git a/amazeing/perfect_to_imperfect.py b/amazeing/perfect_to_imperfect.py new file mode 100644 index 0000000..7430dfd --- /dev/null +++ b/amazeing/perfect_to_imperfect.py @@ -0,0 +1,26 @@ +from amazeing import Maze, WallCoord +import random + + +def perfect_to_imperfect(maze: Maze, walls_const: set[WallCoord]) -> None: + iterations = 10 + for _ in range(0, iterations): + walls = [wall for wall in maze.walls_full() if wall not in walls_const] + random.shuffle(walls) + for wall in walls: + if not maze.wall_cuts_cycle(wall): + continue + if maze.wall_leaf_neighbours(wall): + continue + maze._remove_wall(wall) + walls = [wall for wall in maze.walls_full() if wall not in walls_const] + random.shuffle(walls) + for wall in walls: + if not maze.wall_cuts_cycle(wall): + continue + leaf_neighbours = maze.wall_leaf_neighbours(wall) + if len(leaf_neighbours) == 0: + continue + maze._remove_wall(wall) + maze.fill_wall(random.choice(leaf_neighbours)) + maze._rebuild() diff --git a/amazeing/prototype_perfect.py b/amazeing/prototype_perfect.py new file mode 100644 index 0000000..54fadf4 --- /dev/null +++ b/amazeing/prototype_perfect.py @@ -0,0 +1,11 @@ +from amazeing import Maze +import random + + +def make_perfect(maze: Maze) -> None: + empty = list(maze.walls_empty()) + random.shuffle(empty) + for wall in empty: + if maze.wall_bisects(wall): + continue + maze.fill_wall(wall)