diff --git a/Makefile b/Makefile index bfaca9a..e33d978 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ clean: rm -rf __pycache__ .mypy_cache .venv lint: - uv run flake8 . + uv run flake8 . --exclude=.venv uv run mypy . --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs lint-strict: diff --git a/README.md b/README.md index e69de29..e34f933 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,3 @@ +The Randomized Kruskal's Algorithm + +The Randomized Prim's Algorithm diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/a_maze_ing.py b/a_maze_ing.py index 08efe94..cfb67f2 100644 --- a/a_maze_ing.py +++ b/a_maze_ing.py @@ -1,5 +1,21 @@ +import os +from numpy import ma +from src.amaz_lib import MazeGenerator +from src.amaz_lib import Maze + + def main() -> None: - print("A-Maze-ing !!!") + # try: + maze = Maze(maze=None, start=(1, 1), end=(16, 15)) + for alg in MazeGenerator.Kruskal.kruskal(20, 20): + maze.set_maze(alg) + os.system("clear") + maze.ascii_print() + maze.export_maze("test.txt") + + +# except Exception as err: +# print(err) if __name__ == "__main__": diff --git a/output_validator.py b/output_validator.py new file mode 100644 index 0000000..dfc16eb --- /dev/null +++ b/output_validator.py @@ -0,0 +1,25 @@ +# This script does not check for errors or malformed files. +# It only validates that neighbooring cells sharing a wall have +# both the correct encoding. +# Usage: python3 output_validator.py output_maze.txt + +import sys + +if len(sys.argv) != 2: + print(f"Usage: python3 {sys.argv[0]} ") + sys.exit(1) + +g = [] +for line in open(sys.argv[1]): + if line.strip() == '': + break + g.append([int(c, 16) for c in line.strip(' \t\n\r')]) + +for r in range(len(g)): + for c in range(len(g[0])): + v = g[r][c] + if not all([(r < 1 or v & 1 == (g[r-1][c] >> 2) & 1), + (c >= len(g[0])-1 or (v >> 1) & 1 == (g[r][c+1] >> 3) & 1), + (r >= len(g)-1 or (v >> 2) & 1 == g[r+1][c] & 1), + (c < 1 or (v >> 3) & 1 == (g[r][c-1] >> 1) & 1)]): + print(f'Wrong encoding for ({c},{r})') diff --git a/pyproject.toml b/pyproject.toml index 7540464..738ccdf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,3 @@ dev = [ [tool.mypy] python_version = "3.10" -exclude = [ - ".venv", - "venv", -] diff --git a/src/amaz_lib/MazeGenerator.py b/src/amaz_lib/MazeGenerator.py new file mode 100644 index 0000000..a41536e --- /dev/null +++ b/src/amaz_lib/MazeGenerator.py @@ -0,0 +1,102 @@ +from abc import ABC, abstractmethod +from typing import Generator +import numpy as np +from .classes.Cell import Cell +import math + + +class MazeGenerator(ABC): + @abstractmethod + @classmethod + def generator( + cls, height: int, width: int + ) -> Generator[np.ndarray, None, np.ndarray]: ... + + +class Kruskal(MazeGenerator): + @staticmethod + def walls_to_maze( + walls: list[tuple[int, int]], height: int, width: int + ) -> np.ndarray: + maze: np.ndarray = np.array( + [[Cell(value=0) for _ in range(width)] for _ in range(height)] + ) + for wall in walls: + x, y = wall + match y - x: + case 1: + maze[math.trunc((x / width))][x % width].set_est(True) + maze[math.trunc((y / width))][y % width].set_west(True) + case width: + maze[math.trunc((x / width))][x % width].set_south(True) + maze[math.trunc((y / width))][y % width].set_north(True) + for x in range(height): + for y in range(width): + if x == 0: + maze[x][y].set_north(True) + if x == height - 1: + maze[x][y].set_south(True) + if y == 0: + maze[x][y].set_est(True) + if y == width - 1: + maze[x][y].set_west(True) + return maze + + @staticmethod + def is_in_same_set(sets: list[list[int]], wall: tuple[int, int]) -> bool: + a, b = wall + for set in sets: + if a in set and b in set: + return True + if a in set or b in set: + return False + return False + + @staticmethod + def merge_sets(sets: list[list[int]], wall: tuple[int, int]) -> None: + a, b = wall + base_set = None + for set in sets: + if base_set is None and (a in set or b in set): + base_set = set + elif base_set and (a in set or b in set): + base_set += set + sets.remove(set) + + @classmethod + def generator( + cls, height: int, width: int + ) -> Generator[np.ndarray, None, np.ndarray]: + sets = [[i] for i in range(height * width)] + walls = [] + for h in range(height): + for w in range(width - 1): + walls += [(w + (width * h), w + (width * h) + 1)] + for w in range(width): + for h in range(height - 1): + walls += [(w + (width * h), w + (width * h) + width)] + np.random.shuffle(walls) + + yield cls.walls_to_maze(walls, height, width) + for wall in walls: + if not cls.is_in_same_set(sets, wall): + cls.merge_sets(sets, wall) + walls.remove(wall) + yield cls.walls_to_maze(walls, height, width) + return cls.walls_to_maze(walls, height, width) + + +def main(): + try: + for alg in MazeGenerator.Kruskal.kruskal(10, 10): + maze = alg + # print(maze) + # print() + print(maze) + + except GeneratorExit as maze: + print(maze) + + +if __name__ == "__main__": + main() diff --git a/src/amaz_lib/__init__.py b/src/amaz_lib/__init__.py new file mode 100644 index 0000000..cf2963d --- /dev/null +++ b/src/amaz_lib/__init__.py @@ -0,0 +1,7 @@ +from .classes.Cell import Cell +from .classes.Maze import Maze +from .MazeGenerator import MazeGenerator + +__version__ = "1.0.0" +__author__ = "us" +__all__ = ["Cell", "Maze", "MazeGenerator"] diff --git a/src/lib/class/Cell.py b/src/amaz_lib/classes/Cell.py similarity index 63% rename from src/lib/class/Cell.py rename to src/amaz_lib/classes/Cell.py index 67d3ee6..d6675bc 100644 --- a/src/lib/class/Cell.py +++ b/src/amaz_lib/classes/Cell.py @@ -5,11 +5,17 @@ class Cell(BaseModel): value: int = Field(ge=0, le=15) def __str__(self) -> str: - return hex(self.value) + return hex(self.value).removeprefix("0x").upper() + + def set_value(self, value: int) -> None: + self.value = value + + def get_value(self) -> int: + return self.value def set_north(self, is_wall: bool) -> None: - if (is_wall and self.value | 14 == 15) or ( - not is_wall and self.value | 14 != 15 + if (not is_wall and self.value | 14 == 15) or ( + is_wall and self.value | 14 != 15 ): self.value = self.value ^ (1) @@ -17,8 +23,8 @@ class Cell(BaseModel): return self.value & 1 == 1 def set_est(self, is_wall: bool) -> None: - if (is_wall and self.value | 13 == 15) or ( - not is_wall and self.value | 13 != 15 + if (not is_wall and self.value | 13 == 15) or ( + is_wall and self.value | 13 != 15 ): self.value = self.value ^ (2) @@ -26,8 +32,8 @@ class Cell(BaseModel): return self.value & 2 == 2 def set_south(self, is_wall: bool) -> None: - if (is_wall and self.value | 11 == 15) or ( - not is_wall and self.value | 11 != 15 + if (not is_wall and self.value | 11 == 15) or ( + is_wall and self.value | 11 != 15 ): self.value = self.value ^ (4) @@ -35,8 +41,8 @@ class Cell(BaseModel): return self.value & 4 == 4 def set_west(self, is_wall: bool) -> None: - if (is_wall and self.value | 8 == 15) or ( - not is_wall and self.value | 8 != 15 + if (not is_wall and self.value | 8 == 15) or ( + is_wall and self.value | 8 != 15 ): self.value = self.value ^ (8) diff --git a/src/amaz_lib/classes/Maze.py b/src/amaz_lib/classes/Maze.py new file mode 100644 index 0000000..e47c51c --- /dev/null +++ b/src/amaz_lib/classes/Maze.py @@ -0,0 +1,61 @@ +from dataclasses import dataclass + +import numpy +from .Cell import Cell +from ..MazeGenerator import MazeGenerator + + +@dataclass +class Maze: + maze: numpy.ndarray + start: tuple[int, int] + end: tuple[int, int] + + def get_maze(self) -> numpy.ndarray | None: + return self.maze + + def set_maze(self, new_maze: numpy.ndarray) -> None: + self.maze = new_maze + + def __str__(self) -> str: + if self.maze is None: + return "None" + res = "" + for line in self.maze: + for cell in line: + res += cell.__str__() + res += "\n" + res += "\n" + res += f"{self.start[0]},{self.start[1]}\n" + res += f"{self.end[0]},{self.end[1]}\n" + return res + + def export_maze(self, file_name: str) -> None: + with open(file_name, "w") as file: + file.write(self.__str__()) + + def solver(self) -> str: + pass + + def ascii_print(self) -> None: + for line in self.maze: + if line is self.maze[0]: + for cell in line: + print("_", end="") + if cell.get_north(): + print("__", end="") + else: + print(" ", end="") + print() + for cell in line: + if cell is line[0] and cell.get_west(): + print("|", end="") + if cell.get_south() is True: + print("__", end="") + else: + print(" ", end="") + if cell.get_est() is True: + print("|", end="") + else: + print("_", end="") + print() diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..4c9352e --- /dev/null +++ b/test.txt @@ -0,0 +1,23 @@ +7D53BFD3D57951517D1D +3D12C3903BD03AD4178D +2BAEBEEEAA92EED547C9 +2287ED17AAAC5393FFF0 +6C6951292A87D2AEBD30 +37D43E8686E93AABAB8C +21516D2D47FEE8284049 +6C7857C3FB9116C696D8 +751453D6D2AAC57BE970 +3BA952D17EA83BD05470 +22AAD2907BAE86967B74 +2AA83C2EFC69696FBC35 +686EE96FD7D4783FAD21 +7ED17ED3D57D3EC52FA0 +7B943D16FB7BABD3AFC8 +7407C5297EB82EB84174 +392D53C6912EE9447E9D +62A952BBAAC13EFD7B89 +3AAC3EC6EABAAD557824 +66C7C7D7D6C6C7D556CD + +1,1 +16,15