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
12 changed files with 209 additions and 144 deletions
+1
View File
@@ -214,4 +214,5 @@ __marimo__/
# Streamlit
.streamlit/secrets.toml
test.txt
+3
View File
@@ -4,6 +4,9 @@ install:
run: install
uv run python3 a_maze_ing.py config.txt
run_windows:
.venv\Scripts\python -m a_maze_ing config.txt
debug:
uv pdb python3 a_maze_ing.py config.txt
+1 -90
View File
@@ -1,105 +1,16 @@
import os
from typing import Any, Callable
from src.AMazeIng import AMazeIng
from src.parsing import Parsing
from mlx.mlx import Mlx
import numpy as np
import math
from src.amaz_lib import Maze
class MazeMLX:
def __init__(self, height: int, width: int) -> None:
self.mlx = Mlx()
self.height = height
self.width = width
self.mlx_ptr = self.mlx.mlx_init()
self.win_ptr = self.mlx.mlx_new_window(
self.mlx_ptr, width, height, "amazing"
)
self.img_ptr = self.mlx.mlx_new_image(self.mlx_ptr, width, height)
self.buf, self.bpp, self.size_line, self.format = (
self.mlx.mlx_get_data_addr(self.img_ptr)
)
def __del__(self) -> None:
self.mlx.mlx_destroy_image(self.mlx_ptr, self.img_ptr)
self.mlx.mlx_destroy_window(self.mlx_ptr, self.win_ptr)
def put_pixel(self, x, y) -> None:
offset = y * self.size_line + x * (self.bpp // 8)
self.buf[offset + 0] = 0xFF
self.buf[offset + 1] = 0xFF
self.buf[offset + 2] = 0xFF
if self.bpp >= 32:
self.buf[offset + 3] = 0xFF
def clear_image(self) -> None:
self.buf[:] = b"\x00" * len(self.buf)
def put_line(self, start: tuple[int, int], end: tuple[int, int]) -> None:
sx, sy = start
ex, ey = end
if sy == ey:
for x in range(min(sx, ex), max(sx, ex) + 1):
self.put_pixel(x, sy)
if sx == ex:
for y in range(min(sy, ey), max(sy, ey) + 1):
self.put_pixel(sx, y)
def update_maze(self, maze: np.ndarray) -> None:
self.clear_image()
margin = math.trunc(
math.sqrt(self.width if self.width > self.height else self.height)
// 2
)
line_len = math.trunc(
(
(self.height - margin) // len(maze)
if self.height > self.width
else (self.width - margin) // len(maze[0])
)
)
for y in range(len(maze)):
for x in range(len(maze[0])):
x0 = x * line_len + margin
y0 = y * line_len + margin
x1 = x * line_len + line_len + margin
y1 = y * line_len + line_len + margin
if maze[y][x].get_north():
self.put_line((x0, y0), (x1, y0))
if maze[y][x].get_est():
self.put_line((x1, y0), (x1, y1))
if maze[y][x].get_south():
self.put_line((x0, y1), (x1, y1))
if maze[y][x].get_west():
self.put_line((x0, y0), (x0, y1))
self.mlx.mlx_put_image_to_window(
self.mlx_ptr, self.win_ptr, self.img_ptr, 0, 0
)
def close_loop(self, _: Any):
self.mlx.mlx_loop_exit(self.mlx_ptr)
def gen_maze(self, maze: np.ndarray) -> None:
self.mlx.mlx_loop_hook(self.mlx_ptr, self.update_maze, maze)
self.mlx.mlx_hook(self.win_ptr, 17, 0, self.close_loop, None)
self.mlx.mlx_loop(self.mlx_ptr)
def main() -> None:
mlx = None
try:
mlx = MazeMLX(1000, 1000)
config = Parsing.DataMaze.get_data_maze("config.txt")
print(config)
amazing = AMazeIng(**config)
for _ in amazing.generate():
os.system("clear")
amazing.maze.ascii_print()
mlx.gen_maze(amazing.maze.get_maze())
with open("test.txt", "w") as output:
output.write(amazing.__str__())
except Exception as err:
+6 -6
View File
@@ -1,8 +1,8 @@
WIDTH=30
HEIGHT=30
ENTRY=1,1
EXIT=29,29
WIDTH=11
HEIGHT=11
ENTRY=4,3
EXIT=2,1
OUTPUT_FILE=maze.txt
PERFECT=True
GENERATOR=Kruskal
PERFECT=False
GENERATOR=DFS
SOLVER=AStar
+2 -2
View File
@@ -8,8 +8,8 @@ from src.amaz_lib import Maze, MazeGenerator, MazeSolver
class AMazeIng(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
width: int = Field(ge=3)
height: int = Field(ge=3)
width: int = Field(ge=4)
height: int = Field(ge=4)
entry: tuple[int, int]
exit: tuple[int, int]
output_file: str = Field(min_length=3)
+87 -4
View File
@@ -6,6 +6,11 @@ import math
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
def generator(
self, height: int, width: int, seed: int | None = None
@@ -35,8 +40,60 @@ class MazeGenerator(ABC):
forty_two.add((y + 2, x + 3))
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 Set:
def __init__(self, cells: list[int]) -> None:
self.cells: list[int] = cells
@@ -118,6 +175,8 @@ class Kruskal(MazeGenerator):
cells_ft = None
if height > 10 and width > 10:
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:
np.random.seed(seed)
@@ -148,10 +207,22 @@ class Kruskal(MazeGenerator):
):
break
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):
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(
self, height: int, width: int, seed: int = None
@@ -159,9 +230,15 @@ class DepthFirstSearch(MazeGenerator):
if seed is not None:
np.random.seed(seed)
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 = 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()
w_h = (width, height)
coord = (0, 0)
@@ -192,6 +269,12 @@ class DepthFirstSearch(MazeGenerator):
x, y = coord
maze[y][x] = self.broken_wall(maze[y][x], wall_r)
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
@staticmethod
@@ -252,7 +335,7 @@ class DepthFirstSearch(MazeGenerator):
return {"N": "S", "S": "N", "W": "E", "E": "W"}[direction]
@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:
last = path[-1]
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)
@abstractmethod
def solve(self, maze: Maze) -> str: ...
def solve(self, maze: Maze, height: int = None,
width: int = None) -> str: ...
class AStar(MazeSolver):
@@ -156,3 +157,81 @@ class AStar(MazeSolver):
if res is None:
raise Exception("Path not found")
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 .MazeGenerator import MazeGenerator, DepthFirstSearch
from .MazeGenerator import Kruskal
from .MazeSolver import MazeSolver, AStar
from .MazeSolver import MazeSolver, AStar, DepthFirstSearchSolver
__version__ = "1.0.0"
__author__ = "us"
__all__ = ["Cell", "Maze", "MazeGenerator",
__all__ = ["Cell", "Maze", "MazeGenerator", "DepthFirstSearchSolver",
"MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"]
+6 -3
View File
@@ -54,12 +54,14 @@ class DataMaze:
res.update({key: DataMaze.convert_bool(data[key])})
res.update({"OUTPUT_FILE": data["OUTPUT_FILE"]})
res.update(
DataMaze.get_solver_generator(data, res["ENTRY"], res["EXIT"])
DataMaze.get_solver_generator(data, res["ENTRY"], res["EXIT"],
res["PERFECT"])
)
return res
@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 = {
"Kruskal": Kruskal,
"DFS": DepthFirstSearch,
@@ -68,7 +70,8 @@ class DataMaze:
"AStar": AStar,
}
res = {}
res["GENERATOR"] = available_generator[data["GENERATOR"]]()
res["GENERATOR"] = available_generator[data["GENERATOR"]](entry, exit,
perfect)
res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit)
return res
+14 -33
View File
@@ -1,34 +1,15 @@
BBBBBB97D397BBBB917BBD15513D17
84684003BAA946AAA854696D3EEBC3
C112D2AC28443D06EAB9103945507A
BEAED283E857C3AD52AEAEAC7D5452
87C53EAEBC393C4154012D29553956
A95147EBAD6AEBB87D2EC386D52853
86D4517AC17C50443B813AABD1443E
EBD1787850513A9506AAEAAC3A93EB
B8543850787EEAAB87EC3EC3C2A852
AEBD443AD4517EC6ED3D2D3EB8687E
838553AC7BD2BD3BBD43EB85069457
EAC57843D2B86D4407D0106943C7BB
D01552B856C4553BC556AC783E97C2
92C7D6C29557F96AFFFD6D16850512
E83D15382D3BFC7857FD39696907EA
BEAD6D46ABC2FFFAFFF96AD2D2C57A
87ABD13BC6D293FAFD50547C3C3D3E
C168128455106EFAFFFABD17EBA907
9056AEC7B92ABBBAD15407ABD2C2C3
EAD1691786E8044296952D06B8147A
BEB87EAD053E87D6ED07812BAE8796
87AAD5412B87C7957BEBEAAC2D07EB
C3C2D55286857905169456E9438796
D07EBBD287A97EAB8507957E944507
96BD0692AD445146C7EBC55503D3AB
C16D2BEAC3D514553D52BD53AE92EA
B8552C12BE97AB952D104796C52E92
C47D016AABC16869456AD5413BAD6A
D113AED2C292D03ED53ABD52800392
D6EC457C7C6C7EC57D446D56EEEC6E
BD1553D3913
C3AD54386AA
BAC5396C7AA
82956C5396E
A86D553AC53
C295552C512
9283B9693AA
AAAAC456AAA
AC2C553D2C2
83C3D3C3E96
C454547C547
1,1
29,29
SSEESSEESWSEESSSESSWSWWWWSSESSSWSEENEENESEEEESWSWSEESSSSEEEENNEESEENEEENNWNNWNNEENNENENESEESWSSESSESWSSSSWWSSSEESSESEN
4,3
2,1
EENWWNNEEESESWSEESSEEESSSSWSWWNWNWWWNNWSSSESWWNWNNNENNNNNW
+7 -3
View File
@@ -1,14 +1,18 @@
import numpy
from amaz_lib.MazeGenerator import DepthFirstSearch
from amaz_lib.MazeGenerator import DepthFirstSearch, MazeGenerator
class TestMazeGenerator:
def test_generator(self) -> None:
w_h = (300, 300)
w_h = (10, 10)
maze = numpy.array([])
generator = DepthFirstSearch().generator(*w_h)
generator = DepthFirstSearch((1, 1), (2, 2), True).generator(*w_h)
for output in generator:
maze = output
assert maze.shape == w_h
def test_gen_broken(self) -> None:
test = MazeGenerator.gen_broken_set(50, 50)
assert len(test) > 0