4 Commits

3 changed files with 143 additions and 135 deletions
+1 -5
View File
@@ -199,11 +199,7 @@ class MazeMLX:
# time.sleep(0.01)
except StopIteration:
if self.path_printer is not None:
try:
next(self.path_printer)
time.sleep(0.03)
except StopIteration:
pass
self.render_path()
def main() -> None:
+4 -4
View File
@@ -1,8 +1,8 @@
WIDTH=11
HEIGHT=11
WIDTH=15
HEIGHT=15
ENTRY=1,1
EXIT=11,11
EXIT=15,15
OUTPUT_FILE=maze.txt
PERFECT=True
PERFECT=False
GENERATOR=Kruskal
SOLVER=AStar
+138 -126
View File
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from .Maze import Maze
from typing import Any
import numpy as np
@@ -9,163 +10,167 @@ class MazeSolver(ABC):
self.end = (end[1] - 1, end[0] - 1)
@abstractmethod
def solve(self, maze: Maze, height: int = None,
width: int = None) -> str: ...
def solve(
self, maze: Maze, height: int = None, width: int = None
) -> str: ...
class AStar(MazeSolver):
class Node:
def __init__(
self,
coordinate: tuple[int, int],
g: int,
h: int,
f: int,
parent: Any,
) -> None:
self.coordinate = coordinate
self.g = g
self.h = h
self.f = f
self.parent = parent
def __eq__(self, value: object, /) -> bool:
return value == self.coordinate
def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None:
super().__init__(start, end)
self.path = []
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 g(self, n: tuple[int, int]) -> int:
return len(self.path) + 1
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
def h(self, n: tuple[int, int]) -> int:
return (
max(n[0], self.end[0])
- min(n[0], self.end[0])
+ max(n[1], self.end[1])
- min(n[1], self.end[1])
)
try:
return g(n) + h(n)
except Exception:
return 1000
def f(self, n: tuple[int, int]) -> int:
return self.g(n) + self.h(n)
def best_path(
def get_paths(
self,
maze: np.ndarray,
actual: tuple[int, int],
last: str | None,
) -> dict[str, int]:
path = {
"N": (
self.f((actual[0], actual[1] - 1))
if not maze[actual[1]][actual[0]].get_north() and actual[1] > 0
close: list,
) -> list[tuple[int, int]]:
path = [
(
(actual[0], actual[1] - 1)
if not maze[actual[1]][actual[0]].get_north()
and actual[1] > 0
and (actual[0], actual[1] - 1)
not in [n.coordinate for n in close]
else None
),
"E": (
self.f((actual[0] + 1, actual[1]))
(
(actual[0] + 1, actual[1])
if not maze[actual[1]][actual[0]].get_est()
and actual[0] < len(maze[0]) - 1
and (actual[0] + 1, actual[1])
not in [n.coordinate for n in close]
else None
),
"S": (
self.f((actual[0], actual[1] + 1))
(
(actual[0], actual[1] + 1)
if not maze[actual[1]][actual[0]].get_south()
and actual[1] < len(maze) - 1
and (actual[0], actual[1] + 1)
not in [n.coordinate for n in close]
else None
),
"W": (
self.f((actual[0] - 1, actual[1]))
if not maze[actual[1]][actual[0]].get_west() and actual[0] > 0
(
(actual[0] - 1, actual[1])
if not maze[actual[1]][actual[0]].get_west()
and actual[0] > 0
and (actual[0] - 1, actual[1])
not in [n.coordinate for n in close]
else None
),
}
return {
k: v
for k, v in sorted(path.items(), key=lambda item: item[0])
if v is not None and k != last
}
]
return [p for p in path if p is not None]
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_path(self, maze: np.ndarray) -> list:
open: list[AStar.Node] = []
close: list[AStar.Node] = []
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, maze: np.ndarray) -> str | None:
path = [(self.start, self.best_path(maze, self.start, None))]
visited = [self.start]
while len(path) > 0 and path[-1][0] != self.end:
if len(path[-1][1]) == 0:
path.pop(-1)
if len(path) == 0:
break
k = next(iter(path[-1][1]))
path[-1][1].pop(k)
continue
while len(path[-1][1]) > 0:
next_pos = self.get_next_pos(
list(path[-1][1].keys())[0], path[-1][0]
)
if next_pos in visited:
k = next(iter(path[-1][1]))
path[-1][1].pop(k)
else:
break
if len(path[-1][1]) == 0:
path.pop(-1)
continue
pre = self.get_opposit(list(path[-1][1].keys())[0])
path.append(
(
next_pos,
self.best_path(maze, next_pos, pre),
)
open.append(
AStar.Node(
self.start,
0,
self.h(self.start),
self.f(self.start),
None,
)
visited += [next_pos]
if len(path) == 0:
return None
path[-1] = (self.end, {})
return "".join(
str(list(c[1].keys())[0]) for c in path if len(c[1]) > 0
)
def solve(self, maze: Maze, height: int = None,
width: int = None) -> str:
res = self.get_path(maze.get_maze())
if res is None:
raise Exception("Path not found")
while len(open) > 0:
to_check = sorted(open, key=lambda x: x.f)[0]
open.remove(to_check)
close.append(to_check)
if to_check.coordinate == self.end:
return close
paths = self.get_paths(maze, to_check.coordinate, close)
for path in paths:
open.append(
self.Node(
path,
to_check.g + 1,
self.h(path),
self.h(path) + to_check.g + 1,
to_check,
)
)
raise Exception("Path not found")
def get_rev_dir(self, current: Node) -> str:
if current.parent.coordinate == (
current.coordinate[0],
current.coordinate[1] - 1,
):
return "S"
elif current.parent.coordinate == (
current.coordinate[0] + 1,
current.coordinate[1],
):
return "W"
elif current.parent.coordinate == (
current.coordinate[0],
current.coordinate[1] + 1,
):
return "N"
elif current.parent.coordinate == (
current.coordinate[0] - 1,
current.coordinate[1],
):
return "E"
else:
raise Exception("Translate error: AStar path not found")
def translate(self, close: list) -> str:
current = close[-1]
res = ""
while True:
res = self.get_rev_dir(current) + res
current = current.parent
if current.coordinate == self.start:
break
return res
def solve(self, maze: Maze, height: int = None, width: int = None) -> str:
path = self.get_path(maze.get_maze())
return self.translate(path)
class DepthFirstSearchSolver(MazeSolver):
def __init__(self, start, end):
super().__init__(start, end)
def solve(self, maze: Maze, height: int = None,
width: int = None) -> str:
def solve(self, maze: Maze, height: int = None, width: int = None) -> str:
path_str = ""
visited = np.zeros((height, width), dtype=bool)
path = list()
@@ -179,8 +184,9 @@ class DepthFirstSearchSolver(MazeSolver):
rand_p = self.random_path(visited, coord, maze_s, h_w)
if not rand_p:
path, move = self.back_on_step(path, visited, maze_s, h_w,
move)
path, move = self.back_on_step(
path, visited, maze_s, h_w, move
)
if not path:
break
coord = path[-1]
@@ -195,8 +201,9 @@ class DepthFirstSearchSolver(MazeSolver):
return path_str
@staticmethod
def random_path(visited: np.ndarray, coord: tuple,
maze: np.ndarray, h_w: tuple) -> list:
def random_path(
visited: np.ndarray, coord: tuple, maze: np.ndarray, h_w: tuple
) -> list:
random_p = []
h, w = h_w
y, x = coord
@@ -219,8 +226,13 @@ class DepthFirstSearchSolver(MazeSolver):
return np.random.choice(rand_path)
@staticmethod
def back_on_step(path: list, visited: np.ndarray,
maze: np.ndarray, h_w: tuple, move: list) -> list:
def back_on_step(
path: list,
visited: np.ndarray,
maze: np.ndarray,
h_w: tuple,
move: list,
) -> list:
while path:
last = path[-1]
if DepthFirstSearchSolver.random_path(visited, last, maze, h_w):