]> Untitled Git - axy/ft/a-maze-ing.git/commitdiff
Simple generator done :D
authorAxy <gilliardmarthey.axel@gmail.com>
Thu, 5 Feb 2026 01:49:47 +0000 (02:49 +0100)
committerAxy <gilliardmarthey.axel@gmail.com>
Thu, 5 Feb 2026 01:49:47 +0000 (02:49 +0100)
.gitignore [new file with mode: 0644]
__main__.py [new file with mode: 0644]
amazeing/__init__.py [new file with mode: 0644]
amazeing/display.py [new file with mode: 0644]
amazeing/maze.py [new file with mode: 0644]
maze.py [deleted file]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..eeb8a6e
--- /dev/null
@@ -0,0 +1 @@
+**/__pycache__
diff --git a/__main__.py b/__main__.py
new file mode 100644 (file)
index 0000000..8848e9e
--- /dev/null
@@ -0,0 +1,17 @@
+from amazeing import *
+import random
+
+maze = Maze(10, 10)
+maze.outline()
+empty = list(maze.walls_empty())
+random.shuffle(empty)
+for wall in empty:
+    if maze.wall_maintains_topology(wall):
+        maze.fill_wall(wall)
+
+backend = TTYBackend(10, 10)
+backend.set_style("#")
+for wall in maze.walls_full():
+    for pixel in wall.pixel_coords():
+        backend.draw_pixel(pixel)
+backend.present()
diff --git a/amazeing/__init__.py b/amazeing/__init__.py
new file mode 100644 (file)
index 0000000..5041990
--- /dev/null
@@ -0,0 +1,4 @@
+__version__ = "0.0.0"
+
+from .display import Backend, PixelCoord, TTYBackend
+from .maze import WallID, Maze
diff --git a/amazeing/display.py b/amazeing/display.py
new file mode 100644 (file)
index 0000000..835e1a0
--- /dev/null
@@ -0,0 +1,39 @@
+from abc import ABC, abstractmethod
+from typing import Iterable
+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):
+    def __init__(self, maze_width: int, maze_height: int, style: str = " ") -> None:
+        super().__init__()
+        self.width: int = maze_width * 2 + 1
+        self.height: int = maze_height * 2 + 1
+        self.style: str = style
+        self.lines: list[list[str]] = [
+            [style for _ in range(0, self.width)] for _ in range(0, self.height)
+        ]
+
+    def draw_pixel(self, pos: PixelCoord) -> None:
+        self.lines[pos.y][pos.x] = self.style
+
+    def set_style(self, style: str):
+        self.style = style
+
+    def present(self) -> None:
+        for line in self.lines:
+            for char in line:
+                sys.stdout.write(char)
+            sys.stdout.write("\n")
+        sys.stdout.flush()
diff --git a/amazeing/maze.py b/amazeing/maze.py
new file mode 100644 (file)
index 0000000..0a96aa9
--- /dev/null
@@ -0,0 +1,184 @@
+from enum import Enum, auto
+from typing import Generator, Iterable, Optional, cast
+
+from amazeing.display import PixelCoord
+
+
+class NetworkID:
+    __uuid_gen: int = 0
+
+    def __init__(self) -> None:
+        self.uuid = 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 WallID:
+    def __init__(self, orientation: Orientation, a: int, b: int) -> None:
+        self.orientation: Orientation = orientation
+        self.a: int = a
+        self.b: int = b
+
+    def a_neighbours(self) -> list["WallID"]:
+        return [
+            WallID(self.orientation.opposite(), self.b, self.a - 1),
+            WallID(self.orientation, self.a, self.b - 1),
+            WallID(self.orientation.opposite(), self.b, self.a),
+        ]
+
+    def b_neighbours(self) -> list["WallID"]:
+        return [
+            WallID(self.orientation.opposite(), self.b + 1, self.a - 1),
+            WallID(self.orientation, self.a, self.b + 1),
+            WallID(self.orientation.opposite(), self.b + 1, self.a),
+        ]
+
+    def neighbours(self) -> list["WallID"]:
+        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.HORIZONTAL else b
+        y_iter: Iterable[int] = a if self.orientation == Orientation.VERTICAL else b
+        return (PixelCoord(x, y) for x in x_iter for y in y_iter)
+
+
+class WallNetwork:
+    def __init__(self) -> None:
+        self.walls: set[WallID] = set()
+
+    def size(self) -> int:
+        return len(self.walls)
+
+    def add_wall(self, id: WallID) -> None:
+        self.walls.add(id)
+
+
+class Maze:
+    def __init__(self, width: int, height: int) -> None:
+        self.width: int = width
+        self.height: int = height
+        # list of lines
+        self.horizontal: list[list[MazeWall]] = [
+            [MazeWall() for _ in range(0, width)] for _ in range(0, height + 1)
+        ]
+        # list of lines
+        self.vertical: list[list[MazeWall]] = [
+            [MazeWall() for _ in range(0, height)] for _ in range(0, width + 1)
+        ]
+        self.networks: dict[NetworkID, WallNetwork] = {}
+
+    def _get_wall(self, id: WallID) -> MazeWall:
+        if id.orientation == Orientation.HORIZONTAL:
+            return self.horizontal[id.a][id.b]
+        return self.vertical[id.a][id.b]
+
+    def all_walls(self) -> Generator[WallID]:
+        for orientation, a_count, b_count in [
+            (Orientation.HORIZONTAL, self.width + 1, self.height),
+            (Orientation.VERTICAL, self.height + 1, self.width),
+        ]:
+            for a in range(0, a_count):
+                for b in range(0, b_count):
+                    yield WallID(orientation, a, b)
+
+    def _check_id(self, id: WallID) -> bool:
+        if id.a < 0 or id.b < 0:
+            return False
+        (a_max, b_max) = (
+            (self.height, self.width - 1)
+            if id.orientation == Orientation.HORIZONTAL
+            else (self.width, self.height - 1)
+        )
+        if id.a > a_max or id.b > b_max:
+            return False
+        return True
+
+    def get_walls_checked(self, ids: list[WallID]) -> list[MazeWall]:
+        return [self._get_wall(id) for id in ids if self._check_id(id)]
+
+    def get_neighbours(self, id: WallID) -> list[MazeWall]:
+        return self.get_walls_checked(id.neighbours())
+
+    def _fill_wall_alone(self, id: WallID, 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: WallID) -> 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(WallID(orientation, a, b))
+
+    def walls_full(self) -> Iterable[WallID]:
+        return filter(lambda w: self._get_wall(w).is_full(), self.all_walls())
+
+    def walls_empty(self) -> Iterable[WallID]:
+        return filter(lambda w: not self._get_wall(w).is_full(), self.all_walls())
+
+    def wall_maintains_topology(self, wall: WallID) -> 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
diff --git a/maze.py b/maze.py
deleted file mode 100644 (file)
index 9ec3e72..0000000
--- a/maze.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from enum import Enum
-from typing import Optional, cast
-
-
-class NetworkID:
-    __uuid_gen: int = 0
-
-    def __init__(self) -> None:
-        self.uuid = 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 = "horizontal"
-    VERTICAL = "vertical"
-
-    def opposite(self) -> "Orientation":
-        if self == Orientation.HORIZONTAL:
-            return Orientation.VERTICAL
-        return Orientation.HORIZONTAL
-
-
-class WallId:
-    def __init__(self, orientation: Orientation, a: int, b: int) -> None:
-        self.orientation: Orientation = orientation
-        self.a: int = a
-        self.b: int = b
-
-    def a_neighbours(self) -> list["WallId"]:
-        return [
-            WallId(self.orientation.opposite(), self.b - 1, self.a),
-            WallId(self.orientation, self.a - 1, self.b),
-            WallId(self.orientation.opposite(), self.b, self.a),
-        ]
-
-    def b_neighbours(self) -> list["WallId"]:
-        return [
-            WallId(self.orientation.opposite(), self.b - 1, self.a + 1),
-            WallId(self.orientation, self.a + 1, self.b),
-            WallId(self.orientation.opposite(), self.b, self.a + 1),
-        ]
-
-    def neighbours(self) -> list["WallId"]:
-        return self.a_neighbours() + self.b_neighbours()
-
-
-class WallNetwork:
-    def __init__(self) -> None:
-        self.walls: set[WallId] = set()
-
-    def size(self) -> int:
-        return len(self.walls)
-
-    def add_wall(self, id: WallId) -> None:
-        self.walls.add(id)
-
-
-class Maze:
-    def __init__(self, width: int, height: int) -> None:
-        self.width: int = width
-        self.height: int = height
-        self.horizontal: list[list[MazeWall]] = [
-            [MazeWall() for _ in range(0, width)] for _ in range(0, height + 1)
-        ]
-        self.vertical: list[list[MazeWall]] = [
-            [MazeWall() for _ in range(0, height)] for _ in range(0, width + 1)
-        ]
-        self.networks: dict[NetworkID, WallNetwork] = {}
-
-    def _get_wall(self, id: WallId) -> MazeWall:
-        if id.orientation == Orientation.HORIZONTAL:
-            return self.horizontal[id.a][id.b]
-        return self.vertical[id.a][id.b]
-
-    def _check_id(self, id: WallId) -> bool:
-        if id.a < 0 or id.b < 0:
-            return False
-        (a_max, b_max) = (
-            (self.height + 1, self.width)
-            if id.orientation == Orientation.HORIZONTAL
-            else (self.width + 1, self.height)
-        )
-        if id.a > a_max or id.b > b_max:
-            return False
-        return True
-
-    def get_walls_checked(self, ids: list[WallId]) -> list[MazeWall]:
-        return [self._get_wall(id) for id in ids if self._check_id(id)]
-
-    def get_neighbours(self, id: WallId) -> list[MazeWall]:
-        return self.get_walls_checked(id.neighbours())
-
-    def _fill_wall_alone(self, id: WallId, 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: WallId) -> 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 networks == {}:
-            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]