diff --git a/Makefile b/Makefile index 3c33258..0d575a3 100644 --- a/Makefile +++ b/Makefile @@ -18,5 +18,13 @@ lint-strict: uv run flake8 . uv run mypy . --strict +run_test_parsing: + PYTHONPATH=src uv run pytest tests/test_parsing.py + +run_test_dfs: + PYTHONPATH=src uv run pytest tests/test_Depth.py + +run_test_maze_gen: + PYTHONPATH=src uv run pytest tests/test_MazeGenerator.py run_test: uv run pytest diff --git a/a_maze_ing.py b/a_maze_ing.py index 81182c1..c99338e 100644 --- a/a_maze_ing.py +++ b/a_maze_ing.py @@ -1,10 +1,10 @@ import os -from numpy import ma -from src.amaz_lib import MazeGenerator, Kruskal, AStar from src.amaz_lib import Maze +from src.amaz_lib import MazeGenerator +import src.amaz_lib as g -def main() -> None: +def main(maze_gen: MazeGenerator) -> None: # try: maze = Maze(maze=None) generator = Kruskal() @@ -21,4 +21,4 @@ def main() -> None: if __name__ == "__main__": - main() + main(g.DepthFirstSearch()) diff --git a/src/amaz_lib/Cell.py b/src/amaz_lib/Cell.py index 56f0c61..cc205ab 100644 --- a/src/amaz_lib/Cell.py +++ b/src/amaz_lib/Cell.py @@ -1,8 +1,10 @@ -from pydantic import BaseModel, Field +from dataclasses import dataclass -class Cell(BaseModel): - value: int = Field(ge=0, le=15) +@dataclass +class Cell: + def __init__(self, value: int) -> None: + self.value = value def __str__(self) -> str: return hex(self.value).removeprefix("0x").upper() diff --git a/src/amaz_lib/MazeGenerator.py b/src/amaz_lib/MazeGenerator.py index 24ee52d..0475810 100644 --- a/src/amaz_lib/MazeGenerator.py +++ b/src/amaz_lib/MazeGenerator.py @@ -102,3 +102,112 @@ class Kruskal(MazeGenerator): break print(f"nb sets: {len(sets.sets)}") return self.walls_to_maze(walls, height, width) + + +class DepthFirstSearch(MazeGenerator): + + def generator( + self, width: int, height: int + ) -> Generator[np.ndarray, None, np.ndarray]: + maze = DepthFirstSearch.init_maze(width, height) + visited = np.zeros((height, width), dtype=bool) + path = list() + w_h = (width, height) + coord = (0, 0) + x, y = coord + first = True + + while path or first: + first = False + visited[y, x] = True + path = DepthFirstSearch.add_cell_visited(coord, path) + random_c = DepthFirstSearch.random_cells(visited, coord, w_h) + if len(random_c) == 0: + path = DepthFirstSearch.back_on_step(path, w_h, visited) + if path: + coord = path[-1] + random_c = DepthFirstSearch.random_cells(visited, coord, w_h) + x, y = coord + if not path: + break + + wall = DepthFirstSearch.next_step(random_c) + maze[y][x] = DepthFirstSearch.broken_wall(maze[y][x], wall) + + coord = DepthFirstSearch.next_cell(x, y, wall) + wall_r = DepthFirstSearch.reverse_path(wall) + x, y = coord + maze[y][x] = DepthFirstSearch.broken_wall(maze[y][x], wall_r) + yield maze + return maze + + @staticmethod + def init_maze(width: int, height: int) -> np.ndarray: + maze = np.array( + [[Cell(value=15) for _ in range(width)] for _ in range(height)] + ) + return maze + + @staticmethod + def add_cell_visited(coord: tuple, path: set) -> list: + path.append(coord) + return path + + @staticmethod + def random_cells(visited: np.array, coord: tuple, w_h: tuple) -> list: + rand_cell = [] + x, y = coord + width, height = w_h + + if y - 1 >= 0 and not visited[y - 1][x]: + rand_cell.append("N") + + if y + 1 < height and not visited[y + 1][x]: + rand_cell.append("S") + + if x - 1 >= 0 and not visited[y][x - 1]: + rand_cell.append("W") + + if x + 1 < width and not visited[y][x + 1]: + rand_cell.append("E") + return rand_cell + + @staticmethod + def next_step(rand_cell: list) -> str: + return np.random.choice(rand_cell) + + @staticmethod + def broken_wall(cell: Cell, wall: str) -> Cell: + if wall == "N": + cell.set_north(False) + elif wall == "S": + cell.set_south(False) + elif wall == "W": + cell.set_west(False) + elif wall == "E": + cell.set_est(False) + return cell + + @staticmethod + def next_cell(x: int, y: int, next: str) -> tuple: + next_step = {"N": (0, -1), "S": (0, 1), "W": (-1, 0), "E": (1, 0)} + add_x, add_y = next_step[next] + return (x + add_x, y + add_y) + + @staticmethod + def reverse_path(next: str) -> str: + reverse = {"N": "S", "S": "N", "W": "E", "E": "W"} + return reverse[next] + + @staticmethod + def back_on_step(path: list, w_h: tuple, visited: np.array) -> list: + last = path[-1] + r_cells = DepthFirstSearch.random_cells(visited, last, w_h) + while len(path) > 0: + path.pop() + if path: + last = path[-1] + r_cells = DepthFirstSearch.random_cells(visited, last, w_h) + if r_cells: + break + return path diff --git a/src/amaz_lib/MazeSolver.py b/src/amaz_lib/MazeSolver.py index 2022c7f..b5ae9a7 100644 --- a/src/amaz_lib/MazeSolver.py +++ b/src/amaz_lib/MazeSolver.py @@ -53,25 +53,25 @@ class AStar(MazeSolver): print(actual) path = { "N": ( - self.f((actual[0], actual[1] - 1)) - if not maze[actual[0]][actual[1]].get_north() and actual[1] > 0 + self.f((actual[1] - 1, actual[0])) + if not maze[actual[1]][actual[0]].get_north() and actual[0] > 0 else None ), "E": ( - self.f((actual[0] + 1, actual[1])) - if not maze[actual[0]][actual[1]].get_est() - and actual[0] < len(maze) - 1 + self.f((actual[1], actual[0] + 1)) + if not maze[actual[1]][actual[0]].get_est() + and actual[1] < len(maze) - 1 else None ), "S": ( - self.f((actual[0], actual[1] + 1)) - if not maze[actual[0]][actual[1]].get_south() - and actual[1] < len(maze[0]) - 1 + self.f((actual[1] + 1, actual[0])) + if not maze[actual[1]][actual[0]].get_south() + and actual[0] < len(maze) - 1 else None ), "W": ( - self.f((actual[0] - 1, actual[1])) - if not maze[actual[0]][actual[1]].get_west() and actual[0] > 0 + self.f((actual[1], actual[0] - 1)) + if not maze[actual[1]][actual[0]].get_west() and actual[1] > 0 else None ), } @@ -107,23 +107,10 @@ class AStar(MazeSolver): case _: return actual - def get_path( - self, actual: tuple[int, int], maze: np.ndarray, pre: str | None - ) -> str | None: - if actual == self.end: - return "" - paths = self.best_path(maze, actual) - for path in paths: - if paths[path] is None: - continue - if path != pre: - temp = self.get_path( - self.get_next_pos(path, actual), - maze, - self.get_opposit(path), - ) - if not temp is None: - return path + temp + def get_path(self, maze: np.ndarray) -> str | None: + actual = self.start + path = "" + return None def solve(self, maze: Maze) -> str: diff --git a/src/amaz_lib/__init__.py b/src/amaz_lib/__init__.py index 6dfd9df..fda6b32 100644 --- a/src/amaz_lib/__init__.py +++ b/src/amaz_lib/__init__.py @@ -1,8 +1,10 @@ from .Cell import Cell from .Maze import Maze -from .MazeGenerator import MazeGenerator, Kruskal +from .MazeGenerator import MazeGenerator, DepthFirstSearch +from .MazeGenerator import Kruskal from .MazeSolver import MazeSolver, AStar __version__ = "1.0.0" __author__ = "us" -__all__ = ["Cell", "Maze", "MazeGenerator", "MazeSolver", "AStar", "Kruskal"] +__all__ = ["Cell", "Maze", "MazeGenerator", + "MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"] diff --git a/tests/test_Depth.py b/tests/test_Depth.py new file mode 100644 index 0000000..32a6dad --- /dev/null +++ b/tests/test_Depth.py @@ -0,0 +1,27 @@ +from amaz_lib.MazeGenerator import DepthFirstSearch +from amaz_lib.Cell import Cell +import numpy as np + + +class TestDepth: + + def test_init_maze(self) -> None: + maze = DepthFirstSearch.init_maze(10, 10) + cell = Cell(value=15) + maze[1][1].set_est(False) + assert maze[0][0].value == cell.value + + def test_rand_cells(self) -> None: + w_h = (10, 10) + lst = np.zeros((10, 10), dtype=bool) + lst[0, 0] = True + rand_cells = DepthFirstSearch.random_cells(lst, (0, 1), w_h) + assert len(rand_cells) == 2 + + def test_next_cell(self) -> None: + coord = (5, 4) + x, y = coord + assert DepthFirstSearch.next_cell(x, y, "N") == (2, 3) + + def test_reverse_path(self) -> None: + assert DepthFirstSearch.reverse_path("N") == "S" diff --git a/tests/test_Maze.py b/tests/test_Maze.py index 7bdf580..6c0ae53 100644 --- a/tests/test_Maze.py +++ b/tests/test_Maze.py @@ -15,7 +15,7 @@ def test_maze_setter_getter() -> None: ) maze.set_maze(test) - assert numpy.array_equal(maze.get_maze(), test) == True + assert numpy.array_equal(maze.get_maze(), test) is True def test_maze_str() -> None: diff --git a/tests/test_MazeGenerator.py b/tests/test_MazeGenerator.py index cb0aa6b..948748a 100644 --- a/tests/test_MazeGenerator.py +++ b/tests/test_MazeGenerator.py @@ -1,11 +1,14 @@ import numpy -from amaz_lib.MazeGenerator import Kruskal +from amaz_lib.MazeGenerator import DepthFirstSearch -def test_kruskal_output_shape() -> None: - generator = Kruskal() - maze = numpy.array([]) - for output in generator.generator(10, 10): - maze = output +class TestMazeGenerator: - assert maze.shape == (10, 10) + def test_generator(self) -> None: + w_h = (300, 300) + maze = numpy.array([]) + generator = DepthFirstSearch().generator(*w_h) + for output in generator: + maze = output + + assert maze.shape == w_h