3 Commits

Author SHA1 Message Date
da7e a79d4e5c3b algorithm edited but nothing better 2026-03-24 15:33:50 +01:00
da7e 8dc00e238a Merge branch 'main' of github.com:maoakeEnterprise/amazing 2026-03-24 13:37:30 +01:00
da7e 0f19d24736 base but not working astar solver 2026-03-24 13:30:32 +01:00
9 changed files with 193 additions and 184 deletions
+2 -5
View File
@@ -18,8 +18,5 @@ 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:
uv run pytest
+7 -5
View File
@@ -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")
# 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:
+7 -8
View File
@@ -26,15 +26,14 @@ class Maze:
return res
def ascii_print(self) -> None:
for cell in self.maze[0]:
print("_", end="")
if cell.get_north():
print("__", end="")
else:
print(" ", end="")
print("_")
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="")
+27 -129
View File
@@ -1,9 +1,9 @@
from abc import ABC, abstractmethod
from typing import Generator
from dataclasses import dataclass
from typing import Generator, Set
import numpy as np
from .Cell import Cell
import math
import random
class MazeGenerator(ABC):
@@ -14,9 +14,13 @@ class MazeGenerator(ABC):
class Kruskal(MazeGenerator):
class Set:
def __init__(self, cells: list[int]) -> None:
self.cells: list[int] = cells
@staticmethod
def walls_to_maze(
walls: list[tuple[int, int]], height: int, width: int
walls: np.ndarray, height: int, width: int
) -> np.ndarray:
maze: np.ndarray = np.array(
[[Cell(value=0) for _ in range(width)] for _ in range(height)]
@@ -37,43 +41,46 @@ class Kruskal(MazeGenerator):
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)
if y == width - 1:
maze[x][y].set_est(True)
return maze
@staticmethod
def is_in_same_set(sets: list[list[int]], wall: tuple[int, int]) -> bool:
def is_in_same_set(sets: np.ndarray, wall: tuple[int, int]) -> bool:
a, b = wall
for set in sets:
if a in set and b in set:
if a in set.cells and b in set.cells:
return True
if a in set or b in set:
elif a in set.cells or b in set.cells:
return False
return False
@staticmethod
def merge_sets(sets: list[list[int]], wall: tuple[int, int]) -> None:
def merge_sets(sets: np.ndarray, 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)
for i in range(len(sets)):
if base_set is None and (a in sets[i].cells or b in sets[i].cells):
base_set = sets[i]
elif base_set and (a in sets[i].cells or b in sets[i].cells):
base_set.cells += sets[i].cells
np.delete(sets, i)
return
raise Exception("two sets not found")
def generator(
self, height: int, width: int
) -> Generator[np.ndarray, None, np.ndarray]:
sets = [[i] for i in range(height * width)]
sets = np.array([self.Set([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)]
for h in range(height - 1):
for w in range(width):
walls += [(w + (width * h), w + (width * (h + 1)))]
print(walls)
np.random.shuffle(walls)
yield self.walls_to_maze(walls, height, width)
@@ -82,114 +89,5 @@ class Kruskal(MazeGenerator):
self.merge_sets(sets, wall)
walls.remove(wall)
yield self.walls_to_maze(walls, height, width)
print(f"nb sets: {len(sets)}")
return self.walls_to_maze(walls, height, width)
class DepthFirstSearch:
@staticmethod
def generator(width: int, height: int) -> np.ndarray:
maze = DepthFirstSearch.init_maze(width, height)
visited = []
path = []
w_h = (width, height)
coord = (0, 0)
while len(visited) < width * height:
x, y = coord
rand_steps = DepthFirstSearch.random_cells(visited, coord, w_h)
if len(rand_steps) == 0:
path = DepthFirstSearch.back_on_step(path, w_h)
coord = DepthFirstSearch.last(path)
rand_steps = DepthFirstSearch.random_cells(path, coord, w_h)
x, y = coord
wall = DepthFirstSearch.next_step(rand_steps)
wall_r = DepthFirstSearch.reverse_path(wall)
maze[y][x] = DepthFirstSearch.broken_wall(maze[y][x], wall)
visited = DepthFirstSearch.add_cell_visited(coord, visited)
path = DepthFirstSearch.add_cell_visited(coord, path)
coord = DepthFirstSearch.next_cell(x, y, wall)
x, y = coord
maze[y][x] = DepthFirstSearch.broken_wall(maze[y][x], wall_r)
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, visited: list = []) -> list:
visited.append(coord)
return visited
@staticmethod
def random_cells(visited: list, coord: tuple, w_h: tuple) -> list:
rand_cell = []
x, y = coord
width, height = w_h
# NORTH
if y - 1 >= 0 and (x, y - 1) not in visited:
rand_cell.append("N")
# SOUTH
if y + 1 < height and (x, y + 1) not in visited:
rand_cell.append("S")
# WEST
if x - 1 >= 0 and (x - 1, y) not in visited:
rand_cell.append("W")
# EAST
if x + 1 < width and (x + 1, y) not in visited:
rand_cell.append("E")
return rand_cell
@staticmethod
def next_step(rand_cell: list) -> str:
return 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)
def reverse_path(next: str) -> str:
reverse = {
"N": "S",
"S": "N",
"W": "E",
"E": "W"
}
return reverse[next]
@staticmethod
def last(path: list):
return path[len(path) - 1]
def back_on_step(path: list, w_h: tuple) -> list:
last = DepthFirstSearch.last(path)
r_cells = DepthFirstSearch.random_cells(path, last, w_h)
while len(r_cells == 0):
path.pop(len(path) - 1)
last = DepthFirstSearch.last(path)
r_cells = DepthFirstSearch.random_cells(path, last, w_h)
return path
+127
View File
@@ -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
+3 -3
View File
@@ -1,8 +1,8 @@
from .Cell import Cell
from .Maze import Maze
from .MazeGenerator import MazeGenerator, DepthFirstSearch
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", "DepthFirstSearch"]
__all__ = ["Cell", "Maze", "MazeGenerator", "MazeSolver", "AStar", "Kruskal"]
-33
View File
@@ -1,33 +0,0 @@
from amaz_lib.MazeGenerator import DepthFirstSearch
from amaz_lib.Cell import Cell
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 = DepthFirstSearch.add_cell_visited((0, 0))
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") == (5, 3)
def test_reverse_path(self) -> None:
assert DepthFirstSearch.reverse_path("N") == "S"
def test_last(self) -> None:
lst = [(0, 0), (1, 1)]
assert DepthFirstSearch.last(lst) == (1, 1)
def test_BOS(self) -> None:
path = [(0, 0), (0, 2), (1, 1)]
assert len(DepthFirstSearch.random_cells(path, (0, 1), (10, 10))) == 0
+1 -1
View File
@@ -15,7 +15,7 @@ def test_maze_setter_getter() -> None:
)
maze.set_maze(test)
assert numpy.array_equal(maze.get_maze(), test) is True
assert numpy.array_equal(maze.get_maze(), test) == True
def test_maze_str() -> None:
+19
View File
@@ -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"