4 Commits

Author SHA1 Message Date
da7e d2d477d1b5 config class added 2026-03-19 16:13:13 +01:00
da7e c6c7e6e47e Generator class rework 2026-03-19 14:59:18 +01:00
da7e 84c4b63a6c ascii print + fix maze generator kruskal 2026-03-19 13:54:27 +01:00
da7e c14c043575 Rework and some fixes 2026-03-18 14:10:05 +01:00
12 changed files with 252 additions and 138 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ clean:
rm -rf __pycache__ .mypy_cache
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:
+11 -6
View File
@@ -1,16 +1,21 @@
import os
from numpy import ma
from src.amaz_lib import kruskal
from src.amaz_lib import MazeGenerator
from src.amaz_lib import Maze
def main() -> None:
try:
maze = Maze(maze=None)
for alg in kruskal(10, 10):
# 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)
# except Exception as err:
# print(err)
if __name__ == "__main__":
-4
View File
@@ -19,7 +19,3 @@ dev = [
[tool.mypy]
python_version = "3.10"
exclude = [
".venv",
"venv",
]
+46
View File
@@ -0,0 +1,46 @@
from dataclasses import field
from os import eventfd_read
from typing import Generator
import numpy
from typing_extensions import Self
from pydantic import AfterValidator, BaseModel, Field, model_validator
from amaz_lib import Maze, MazeGenerator, MazeSolver
from amaz_lib.Cell import Cell
class AMazeIng(BaseModel):
width: int = Field(ge=3)
height: int = Field(ge=3)
entry: tuple[int, int]
exit: tuple[int, int]
output_file: str = Field(min_length=3)
perfect: bool = Field(default=True)
maze: Maze = Field(default=Maze(maze=numpy.array([])))
generator: MazeGenerator
solver: MazeSolver
@model_validator(mode="after")
def check_entry_exit(self) -> Self:
if self.entry[0] >= self.width or self.entry[1] >= self.height:
raise ValueError("Entry coordinates exceed the maze size")
if self.exit[0] >= self.width or self.exit[1] >= self.height:
raise ValueError("Exit coordinates exceed the maze size")
return self
def generate(self) -> Generator[Maze, None, None]:
for array in self.generator.generator(self.height, self.width):
self.maze.set_maze(array)
yield self.maze
def solve_path(self) -> str:
return self.solver.solve(self.maze)
def __str__(self) -> str:
res = self.maze.__str__()
res += "\n"
res += f"{self.entry[0]},{self.entry[1]}\n"
res += f"{self.exit[0]},{self.exit[1]}\n"
res += self.solve_path()
res += "\n"
return res
@@ -5,7 +5,7 @@ class Cell(BaseModel):
value: int = Field(ge=0, le=15)
def __str__(self) -> str:
return hex(self.value).removeprefix("0x")
return hex(self.value).removeprefix("0x").upper()
def set_value(self, value: int) -> None:
self.value = value
+53
View File
@@ -0,0 +1,53 @@
from dataclasses import dataclass
import numpy
from .Cell import Cell
from .MazeGenerator import MazeGenerator
@dataclass
class Maze:
maze: numpy.ndarray
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"
return res
def export_maze(self, file_name: str) -> None:
with open(file_name, "w") as file:
file.write(self.__str__())
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()
+102
View File
@@ -0,0 +1,102 @@
from abc import ABC, abstractmethod
from typing import Generator
import numpy as np
from .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()
+8
View File
@@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
from .Maze import Maze
class MazeSolver(ABC):
@abstractmethod
@classmethod
def solve(cls, maze: Maze) -> str: ...
+5 -4
View File
@@ -1,7 +1,8 @@
from .classes.Cell import Cell
from .classes.Maze import Maze
from .generators.kruskal import kruskal
from .Cell import Cell
from .Maze import Maze
from .MazeGenerator import MazeGenerator
from .MazeSolver import MazeSolver
__version__ = "1.0.0"
__author__ = "us"
__all__ = ["Cell", "Maze", "kruskal"]
__all__ = ["Cell", "Maze", "MazeGenerator", "MazeSolver"]
-27
View File
@@ -1,27 +0,0 @@
from dataclasses import dataclass
from .Cell import Cell
@dataclass
class Maze:
maze: list[list[Cell]]
def get_maze(self) -> list[list[Cell]] | None:
return self.maze
def set_maze(self, new_maze: list[list[Cell]]) -> 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"
return res
def export_maze(self, file_name: str):
with open(file_name, "w") as file:
file.write(self.__str__())
-83
View File
@@ -1,83 +0,0 @@
from typing import Generator
import numpy as np
from ..classes.Cell import Cell
import math
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 5:
maze[math.trunc((x / width))][x % width].set_south(True)
maze[math.trunc((y / width))][y % width].set_north(True)
return maze
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
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)
def kruskal(
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 walls_to_maze(walls, height, width)
for wall in walls:
if not is_in_same_set(sets, wall):
merge_sets(sets, wall)
walls.remove(wall)
yield walls_to_maze(walls, height, width)
return walls_to_maze(walls, height, width)
def main():
try:
for alg in kruskal(10, 10):
maze = alg
# print(maze)
# print()
print(maze)
except GeneratorExit as maze:
print(maze)
if __name__ == "__main__":
main()
+23 -10
View File
@@ -1,10 +1,23 @@
2a80002a80
2a80282800
282aaaa800
2a82a802a8
2802800280
002aaaaa80
2a80280280
00282aa828
002a82aaa8
2aa8000028
7D53BFD3D57951517D1D
3D12C3903BD03AD4178D
2BAEBEEEAA92EED547C9
2287ED17AAAC5393FFF0
6C6951292A87D2AEBD30
37D43E8686E93AABAB8C
21516D2D47FEE8284049
6C7857C3FB9116C696D8
751453D6D2AAC57BE970
3BA952D17EA83BD05470
22AAD2907BAE86967B74
2AA83C2EFC69696FBC35
686EE96FD7D4783FAD21
7ED17ED3D57D3EC52FA0
7B943D16FB7BABD3AFC8
7407C5297EB82EB84174
392D53C6912EE9447E9D
62A952BBAAC13EFD7B89
3AAC3EC6EABAAD557824
66C7C7D7D6C6C7D556CD
1,1
16,15