8 Commits

Author SHA1 Message Date
Maoake Teriierooiterai ef030f70a7 finish to synchronize the maze generator and the solver 2026-03-26 14:19:43 +01:00
Maoake Teriierooiterai 170de8813a update the gitignore 2026-03-26 13:11:36 +01:00
Maoake TERIIEROOITERAI 5aec319f7b need to fix the unperfect maze and add the function in the kruskal generator 2026-03-26 00:58:07 +01:00
Maoake TERIIEROOITERAI 24748c47ad merge main to the branch solver_dfs and fixing some conflict 2026-03-26 00:14:39 +01:00
Maoake TERIIEROOITERAI e4c91dc4a1 finish the imperfect maze 2026-03-26 00:10:10 +01:00
Maoake Teriierooiterai 6b3fd9a6b7 need to finish the unperfect maze 2026-03-25 18:45:30 +01:00
Maoake Teriierooiterai b8631d5b89 doing some test 2026-03-25 17:24:32 +01:00
Maoake Teriierooiterai 5f1ffcc01c finish the solver 2026-03-25 17:00:14 +01:00
10 changed files with 208 additions and 54 deletions
+1
View File
@@ -214,4 +214,5 @@ __marimo__/
# Streamlit # Streamlit
.streamlit/secrets.toml .streamlit/secrets.toml
test.txt
+3
View File
@@ -4,6 +4,9 @@ install:
run: install run: install
uv run python3 a_maze_ing.py config.txt uv run python3 a_maze_ing.py config.txt
run_windows:
.venv\Scripts\python -m a_maze_ing config.txt
debug: debug:
uv pdb python3 a_maze_ing.py config.txt uv pdb python3 a_maze_ing.py config.txt
+6 -6
View File
@@ -1,8 +1,8 @@
WIDTH=30 WIDTH=11
HEIGHT=30 HEIGHT=11
ENTRY=1,1 ENTRY=4,3
EXIT=29,29 EXIT=2,1
OUTPUT_FILE=maze.txt OUTPUT_FILE=maze.txt
PERFECT=True PERFECT=False
GENERATOR=Kruskal GENERATOR=DFS
SOLVER=AStar SOLVER=AStar
+2 -2
View File
@@ -8,8 +8,8 @@ from src.amaz_lib import Maze, MazeGenerator, MazeSolver
class AMazeIng(BaseModel): class AMazeIng(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True) model_config = ConfigDict(arbitrary_types_allowed=True)
width: int = Field(ge=3) width: int = Field(ge=4)
height: int = Field(ge=3) height: int = Field(ge=4)
entry: tuple[int, int] entry: tuple[int, int]
exit: tuple[int, int] exit: tuple[int, int]
output_file: str = Field(min_length=3) output_file: str = Field(min_length=3)
+87 -4
View File
@@ -6,6 +6,11 @@ import math
class MazeGenerator(ABC): class MazeGenerator(ABC):
def __init__(self, start: tuple, end: tuple, perfect: bool) -> None:
self.start = (start[0] - 1, start[1] - 1)
self.end = (end[0] - 1, end[1] - 1)
self.perfect = perfect
@abstractmethod @abstractmethod
def generator( def generator(
self, height: int, width: int, seed: int | None = None self, height: int, width: int, seed: int | None = None
@@ -35,8 +40,60 @@ class MazeGenerator(ABC):
forty_two.add((y + 2, x + 3)) forty_two.add((y + 2, x + 3))
return forty_two return forty_two
@staticmethod
def unperfect_maze(width: int, height: int,
maze: np.ndarray, forty_two: set | None,
prob: float = 0.1
) -> Generator[np.ndarray, None, np.ndarray]:
directions = {
"N": (0, -1),
"S": (0, 1),
"W": (-1, 0),
"E": (1, 0)
}
reverse = {
"N": "S",
"S": "N",
"W": "E",
"E": "W"
}
min_break = 2
while True:
count = 0
for y in range(height):
for x in range(width):
if forty_two and (x, y) in forty_two:
continue
for direc, (dx, dy) in directions.items():
nx, ny = x + dx, y + dy
if forty_two and (
(y, x) in forty_two
or (ny, nx) in forty_two
):
continue
if not (0 <= nx < width and 0 < ny < height):
continue
if direc in ["S", "E"]:
continue
if np.random.random() < prob:
count += 1
cell = maze[y][x]
cell_n = maze[ny][nx]
cell = DepthFirstSearch.broken_wall(cell, direc)
cell_n = DepthFirstSearch.broken_wall(cell_n,
reverse[
direc])
maze[y][x] = cell
maze[ny][nx] = cell_n
yield maze
if count > min_break:
break
return maze
class Kruskal(MazeGenerator): class Kruskal(MazeGenerator):
class Set: class Set:
def __init__(self, cells: list[int]) -> None: def __init__(self, cells: list[int]) -> None:
self.cells: list[int] = cells self.cells: list[int] = cells
@@ -118,6 +175,8 @@ class Kruskal(MazeGenerator):
cells_ft = None cells_ft = None
if height > 10 and width > 10: if height > 10 and width > 10:
cells_ft = self.get_cell_ft(width, height) cells_ft = self.get_cell_ft(width, height)
if cells_ft and (self.start in cells_ft or self.end in cells_ft):
cells_ft = None
if seed is not None: if seed is not None:
np.random.seed(seed) np.random.seed(seed)
@@ -148,10 +207,22 @@ class Kruskal(MazeGenerator):
): ):
break break
print(f"nb sets: {len(sets.sets)}") print(f"nb sets: {len(sets.sets)}")
return self.walls_to_maze(walls, height, width) maze = self.walls_to_maze(walls, height, width)
if self.perfect is False:
gen = Kruskal.unperfect_maze(width, height, maze,
cells_ft)
for res in gen:
maze = res
yield maze
return maze
class DepthFirstSearch(MazeGenerator): class DepthFirstSearch(MazeGenerator):
def __init__(self, start: bool, end: bool, perfect: bool) -> None:
self.start = (start[0] - 1, start[1] - 1)
self.end = (end[0] - 1, end[1] - 1)
self.perfect = perfect
self.forty_two: set | None = None
def generator( def generator(
self, height: int, width: int, seed: int = None self, height: int, width: int, seed: int = None
@@ -159,9 +230,15 @@ class DepthFirstSearch(MazeGenerator):
if seed is not None: if seed is not None:
np.random.seed(seed) np.random.seed(seed)
maze = self.init_maze(width, height) maze = self.init_maze(width, height)
forty_two = self.get_cell_ft(width, height) if width > 9 and height > 9:
self.forty_two = self.get_cell_ft(width, height)
visited = np.zeros((height, width), dtype=bool) visited = np.zeros((height, width), dtype=bool)
visited = self.lock_cell_ft(visited, forty_two) if (
self.forty_two
and self.start not in self.forty_two
and self.end not in self.forty_two
):
visited = self.lock_cell_ft(visited, self.forty_two)
path = list() path = list()
w_h = (width, height) w_h = (width, height)
coord = (0, 0) coord = (0, 0)
@@ -192,6 +269,12 @@ class DepthFirstSearch(MazeGenerator):
x, y = coord x, y = coord
maze[y][x] = self.broken_wall(maze[y][x], wall_r) maze[y][x] = self.broken_wall(maze[y][x], wall_r)
yield maze yield maze
if self.perfect is False:
gen = DepthFirstSearch.unperfect_maze(width, height, maze,
self.forty_two)
for res in gen:
maze = res
yield maze
return maze return maze
@staticmethod @staticmethod
@@ -252,7 +335,7 @@ class DepthFirstSearch(MazeGenerator):
return {"N": "S", "S": "N", "W": "E", "E": "W"}[direction] return {"N": "S", "S": "N", "W": "E", "E": "W"}[direction]
@staticmethod @staticmethod
def back_on_step(path: list, w_h: tuple, visited: np.array) -> list: def back_on_step(path: list, w_h: tuple, visited: np.ndarray) -> list:
while path: while path:
last = path[-1] last = path[-1]
if DepthFirstSearch.random_cells(visited, last, w_h): if DepthFirstSearch.random_cells(visited, last, w_h):
+80 -1
View File
@@ -9,7 +9,8 @@ class MazeSolver(ABC):
self.end = (end[1] - 1, end[0] - 1) self.end = (end[1] - 1, end[0] - 1)
@abstractmethod @abstractmethod
def solve(self, maze: Maze) -> str: ... def solve(self, maze: Maze, height: int = None,
width: int = None) -> str: ...
class AStar(MazeSolver): class AStar(MazeSolver):
@@ -156,3 +157,81 @@ class AStar(MazeSolver):
if res is None: if res is None:
raise Exception("Path not found") raise Exception("Path not found")
return res return res
class DepthFirstSearchSolver(MazeSolver):
def __init__(self, start, end):
self.start = (start[1] - 1, start[0] - 1)
self.end = (end[1] - 1, end[0] - 1)
def solve(self, maze: Maze, height: int = None,
width: int = None) -> str:
path_str = ""
visited = np.zeros((height, width), dtype=bool)
path = list()
move = list()
maze_s = maze.get_maze()
coord = self.start
h_w = (height, width)
while coord != self.end:
visited[coord] = True
path.append(coord)
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)
if not path:
break
coord = path[-1]
rand_p = self.random_path(visited, coord, maze_s, h_w)
next = self.next_path(rand_p)
move.append(next)
coord = self.next_cell(coord, next)
for m in move:
path_str += m
if not path:
raise Exception("Path not found")
return path_str
@staticmethod
def random_path(visited: np.ndarray, coord: tuple,
maze: np.ndarray, h_w: tuple) -> list:
random_p = []
h, w = h_w
y, x = coord
if y - 1 >= 0 and not maze[y][x].get_north() and not visited[y - 1][x]:
random_p.append("N")
if y + 1 < h and not maze[y][x].get_south() and not visited[y + 1][x]:
random_p.append("S")
if x - 1 >= 0 and not maze[y][x].get_west() and not visited[y][x - 1]:
random_p.append("W")
if x + 1 < w and not maze[y][x].get_est() and not visited[y][x + 1]:
random_p.append("E")
return random_p
@staticmethod
def next_path(rand_path: list) -> str:
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:
while path:
last = path[-1]
if DepthFirstSearchSolver.random_path(visited, last, maze, h_w):
break
path.pop()
move.pop()
return path, move
@staticmethod
def next_cell(coord: tuple, next: str) -> tuple:
y, x = coord
next_step = {"N": (-1, 0), "S": (1, 0), "W": (0, -1), "E": (0, 1)}
add_y, add_x = next_step[next]
return (y + add_y, x + add_x)
+2 -2
View File
@@ -2,9 +2,9 @@ from .Cell import Cell
from .Maze import Maze from .Maze import Maze
from .MazeGenerator import MazeGenerator, DepthFirstSearch from .MazeGenerator import MazeGenerator, DepthFirstSearch
from .MazeGenerator import Kruskal from .MazeGenerator import Kruskal
from .MazeSolver import MazeSolver, AStar from .MazeSolver import MazeSolver, AStar, DepthFirstSearchSolver
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "us" __author__ = "us"
__all__ = ["Cell", "Maze", "MazeGenerator", __all__ = ["Cell", "Maze", "MazeGenerator", "DepthFirstSearchSolver",
"MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"] "MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"]
+6 -3
View File
@@ -54,12 +54,14 @@ class DataMaze:
res.update({key: DataMaze.convert_bool(data[key])}) res.update({key: DataMaze.convert_bool(data[key])})
res.update({"OUTPUT_FILE": data["OUTPUT_FILE"]}) res.update({"OUTPUT_FILE": data["OUTPUT_FILE"]})
res.update( res.update(
DataMaze.get_solver_generator(data, res["ENTRY"], res["EXIT"]) DataMaze.get_solver_generator(data, res["ENTRY"], res["EXIT"],
res["PERFECT"])
) )
return res return res
@staticmethod @staticmethod
def get_solver_generator(data: dict, entry: int, exit: int) -> dict: def get_solver_generator(data: dict, entry: tuple, exit: tuple,
perfect: bool) -> dict:
available_generator = { available_generator = {
"Kruskal": Kruskal, "Kruskal": Kruskal,
"DFS": DepthFirstSearch, "DFS": DepthFirstSearch,
@@ -68,7 +70,8 @@ class DataMaze:
"AStar": AStar, "AStar": AStar,
} }
res = {} res = {}
res["GENERATOR"] = available_generator[data["GENERATOR"]]() res["GENERATOR"] = available_generator[data["GENERATOR"]](entry, exit,
perfect)
res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit) res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit)
return res return res
+14 -33
View File
@@ -1,34 +1,15 @@
957D3955553B957917B93D3D15553B BD1553D3913
AB97AABBD16847D6ABAAAD452B97C6 C3AD54386AA
8687A8443EBC55538286E953A807D3 BAC5396C7AA
878506952BAD3B942EAD503E86ED52 82956C5396E
852D4383C6C384438147D2C3879796 A86D553AC53
ED2B96EC7D384792EC5792BEC38103 C295552C512
BBC2C795516C13EC51556C053AAEAA 9283B9693AA
8452BD43D4396AD552D3BBEBE803EE AAAAC456AAA
853A8552B902BABD5290029696AA93 AC2C553D2C2
C7AC2B9686EE82ABB82EEC07C7E86A 83C3D3C3E96
D3AD0683C7D3EAC2C6C3D5693956D2 C454547C547
9687E92EBBD0103857D43916A85796
C52BD2A942D2EAE857D542E946D147
D3EC12EE9692F852FFFBBE96957EBB
B87BAA97852AFED057FC07C507D506
C2942C47C7EEFFFAFFFD4517ED53C3
946BABB91553BBFEFD51796955147A
C3BA82AAC53C6AFBFFF816BEBD053A
D06AE828792B90543D568547856D42
92BC56AAD2C6A87B83916D516BD53A
AAAD13C47AD3AED46EEA95103853C2
EAAD2815143C43D3BBD0696EAED416
B847EEC3EBC57AD4407C7C3B85792B
AEBBD5503A9156BD16D1792A8796AA
87AC3BD2C42C57C383D2BEAC47C3EA
C50542B857879112E812C1297BD6BA
D3C3D44417C7EEAC3EAC52C47C516A
BA903B91693B97AB87A938157D5052
802A82EC5684414287AAEE87953AD2
EEEEEC57D547D6D6C546D54547C47E
1,1 4,3
29,29 2,1
SSSSEESSESSSENEEESWSSWSSWSSSSESESSENNNNESEESWSSSWWWSESSSSESWSEENESEEEENNNESESSSEENNNNESEESESSEENEESENNEESW EENWWNNEEESESWSEESSEEESSSSWSWWNWNWWWNNWSSSESWWNWNNNENNNNNW
+7 -3
View File
@@ -1,14 +1,18 @@
import numpy import numpy
from amaz_lib.MazeGenerator import DepthFirstSearch from amaz_lib.MazeGenerator import DepthFirstSearch, MazeGenerator
class TestMazeGenerator: class TestMazeGenerator:
def test_generator(self) -> None: def test_generator(self) -> None:
w_h = (300, 300) w_h = (10, 10)
maze = numpy.array([]) maze = numpy.array([])
generator = DepthFirstSearch().generator(*w_h) generator = DepthFirstSearch((1, 1), (2, 2), True).generator(*w_h)
for output in generator: for output in generator:
maze = output maze = output
assert maze.shape == w_h assert maze.shape == w_h
def test_gen_broken(self) -> None:
test = MazeGenerator.gen_broken_set(50, 50)
assert len(test) > 0