]> Untitled Git - axy/ft/a-maze-ing.git/commitdiff
A peu pres nettoye sah
authorLucas Flores <luflores@k1r1p8.42mulhouse.fr>
Tue, 10 Feb 2026 11:10:34 +0000 (12:10 +0100)
committerLucas Flores <luflores@k1r1p8.42mulhouse.fr>
Tue, 10 Feb 2026 11:10:34 +0000 (12:10 +0100)
12 files changed:
__main__.py
amazeing/__init__.py
amazeing/maze.py [deleted file]
amazeing/maze_class/__init__.py [new file with mode: 0644]
amazeing/maze_class/maze.py [new file with mode: 0644]
amazeing/maze_class/maze_pattern.py [new file with mode: 0644]
amazeing/maze_class/maze_walls.py [new file with mode: 0644]
amazeing/maze_display/TTYdisplay.py [moved from amazeing/display.py with 75% similarity]
amazeing/maze_display/__init__.py [new file with mode: 0644]
amazeing/maze_display/backend.py [new file with mode: 0644]
amazeing/perfect_to_imperfect.py [new file with mode: 0644]
amazeing/prototype_perfect.py [new file with mode: 0644]

index 949c77f234a48c6b6e3b97bf78a5d485b64263ab..a4a32178af40e682e103c3ebad63c76a6aba8a2d 100644 (file)
@@ -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)
index 6a7d189033d7ee592365dd4ebb742a9896b3d972..7569db10d3e29ca7baa18c6cd6bf16a5db61ea9e 100644 (file)
@@ -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 (file)
index e0c1174..0000000
+++ /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 (file)
index 0000000..210ff0e
--- /dev/null
@@ -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 (file)
index 0000000..e94ff02
--- /dev/null
@@ -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 (file)
index 0000000..2e5ed62
--- /dev/null
@@ -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 (file)
index 0000000..d4f0faf
--- /dev/null
@@ -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)
similarity index 75%
rename from amazeing/display.py
rename to amazeing/maze_display/TTYdisplay.py
index c1619c5f7c2dd3dce8ff0f4cd80d978cfdb54008..57d3f47defd85bc5e8783ba66e61a6e467f1ed95 100644 (file)
@@ -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 (file)
index 0000000..972cb85
--- /dev/null
@@ -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 (file)
index 0000000..7f17bed
--- /dev/null
@@ -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 (file)
index 0000000..7430dfd
--- /dev/null
@@ -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 (file)
index 0000000..54fadf4
--- /dev/null
@@ -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)