diff --git a/Makefile b/Makefile index e33d978..3c33258 100644 --- a/Makefile +++ b/Makefile @@ -19,4 +19,4 @@ lint-strict: uv run mypy . --strict run_test: - PYTHONPATH=src uv run python3 test/test_parsing.py + uv run pytest diff --git a/a_maze_ing.py b/a_maze_ing.py index cfb67f2..62a2a8b 100644 --- a/a_maze_ing.py +++ b/a_maze_ing.py @@ -1,17 +1,19 @@ import os from numpy import ma -from src.amaz_lib import MazeGenerator +from src.amaz_lib import MazeGenerator, Kruskal, AStar from src.amaz_lib import Maze def main() -> None: # try: - maze = Maze(maze=None, start=(1, 1), end=(16, 15)) - for alg in MazeGenerator.Kruskal.kruskal(20, 20): + maze = Maze(maze=None) + generator = Kruskal() + for alg in generator.generator(20, 20): maze.set_maze(alg) os.system("clear") maze.ascii_print() - maze.export_maze("test.txt") + solver = AStar((1, 1), (14, 18)) + print(solver.solve(maze)) # except Exception as err: diff --git a/src/amaz_lib/MazeSolver.py b/src/amaz_lib/MazeSolver.py index 2efe587..2022c7f 100644 --- a/src/amaz_lib/MazeSolver.py +++ b/src/amaz_lib/MazeSolver.py @@ -1,7 +1,134 @@ from abc import ABC, abstractmethod from .Maze import Maze +import numpy as np class MazeSolver(ABC): + def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None: + self.start = (start[0] - 1, start[1] - 1) + self.end = (end[0] - 1, end[1] - 1) + @abstractmethod def solve(self, maze: Maze) -> str: ... + + +class AStar(MazeSolver): + + def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None: + super().__init__(start, end) + + def f(self, n): + def g(n: tuple[int, int]) -> int: + res = 0 + if n[0] < self.start[0]: + res += self.start[0] - n[0] + else: + res += n[0] - self.start[0] + if n[1] < self.start[1]: + res += self.start[1] - n[1] + else: + res += n[1] - self.start[1] + return res + + def h(n: tuple[int, int]) -> int: + res = 0 + if n[0] < self.end[0]: + res += self.end[0] - n[0] + else: + res += n[0] - self.end[0] + if n[1] < self.end[1]: + res += self.end[1] - n[1] + else: + res += n[1] - self.end[1] + return res + + try: + return g(n) + h(n) + except Exception: + return 1000 + + def best_path( + self, maze: np.ndarray, actual: tuple[int, int] + ) -> dict[str, int | None]: + print(actual) + path = { + "N": ( + self.f((actual[0], actual[1] - 1)) + if not maze[actual[0]][actual[1]].get_north() and actual[1] > 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 + 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 + else None + ), + "W": ( + self.f((actual[0] - 1, actual[1])) + if not maze[actual[0]][actual[1]].get_west() and actual[0] > 0 + else None + ), + } + return { + k: v for k, v in sorted(path.items(), key=lambda item: item[0]) + } + + def get_opposit(self, dir: str) -> str: + match dir: + case "N": + return "S" + case "E": + return "W" + case "S": + return "N" + case "W": + return "E" + case _: + return "" + + def get_next_pos( + self, dir: str, actual: tuple[int, int] + ) -> tuple[int, int]: + match dir: + case "N": + return (actual[0], actual[1] - 1) + case "E": + return (actual[0] + 1, actual[1]) + case "S": + return (actual[0], actual[1] + 1) + case "W": + return (actual[0] - 1, actual[1]) + 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 + return None + + def solve(self, maze: Maze) -> str: + print(maze) + res = self.get_path(self.start, maze.get_maze(), None) + if res is None: + raise Exception("Path not found") + return res diff --git a/src/amaz_lib/__init__.py b/src/amaz_lib/__init__.py index 9a6cf1d..6dfd9df 100644 --- a/src/amaz_lib/__init__.py +++ b/src/amaz_lib/__init__.py @@ -1,8 +1,8 @@ from .Cell import Cell from .Maze import Maze -from .MazeGenerator import MazeGenerator -from .MazeSolver import MazeSolver +from .MazeGenerator import MazeGenerator, Kruskal +from .MazeSolver import MazeSolver, AStar __version__ = "1.0.0" __author__ = "us" -__all__ = ["Cell", "Maze", "MazeGenerator", "MazeSolver"] +__all__ = ["Cell", "Maze", "MazeGenerator", "MazeSolver", "AStar", "Kruskal"] diff --git a/tests/test_MazeSolver.py b/tests/test_MazeSolver.py new file mode 100644 index 0000000..b8f2f4c --- /dev/null +++ b/tests/test_MazeSolver.py @@ -0,0 +1,19 @@ +from amaz_lib.Cell import Cell +import numpy as np +from amaz_lib import AStar, Maze, MazeSolver + + +def test_solver() -> None: + maze = Maze( + np.array( + [ + [Cell(value=13), Cell(value=3), Cell(value=11)], + [Cell(value=9), Cell(value=4), Cell(value=6)], + [Cell(value=12), Cell(value=5), Cell(value=7)], + ] + ) + ) + print(maze) + solver = AStar((1, 1), (3, 3)) + res = solver.solve(maze) + assert res == "ESWSEE"