33 Commits

Author SHA1 Message Date
Maoake Teriierooiterai 3c072de0f4 finish the animation generator maze 2026-03-27 16:49:13 +01:00
Maoake Teriierooiterai a3bbce861d fix conflict 2026-03-27 14:39:06 +01:00
Maoake Teriierooiterai ca9444778e need to test the mlx 2026-03-27 14:35:04 +01:00
da7e 135e13deff WIP(iterative display): display change for every generation doesn't work 2026-03-27 13:33:45 +01:00
da7e 2828e37853 maze is display once 2026-03-26 18:22:45 +01:00
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
da7e e33d0a8e29 ADD(main): expected output in file test.txt 2026-03-25 17:48:18 +01:00
da7e a408004bd7 fix(parsing): make output work for AMazeIng class __init__
Basic main to display ascii print
2026-03-25 17:40:13 +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
da7e e717bf52e9 fix 42 logo adapt with size 2026-03-25 15:50:08 +01:00
da7e 3fa0d3204e add ft logo to maze 2026-03-25 15:27:39 +01:00
da7e cc6f2eb147 Merge branch 'fix_aster' 2026-03-25 14:52:10 +01:00
da7e c6242eeec0 fix astar algorithm work 2026-03-25 14:51:12 +01:00
Maoake Teriierooiterai 4055a8a7a2 finish to print the 42 2026-03-25 13:58:35 +01:00
Maoake TERIIEROOITERAI a39f348b1e set up the function draw ft 2026-03-24 22:11:54 +01:00
Maoake TERIIEROOITERAI 03c4d206d6 starting the branch parsing need to get a good start on this 2026-03-24 21:21:46 +01:00
Maoake TERIIEROOITERAI 8eb46f601f fixing the DFS and modify the main 2026-03-24 20:47:13 +01:00
da7e 991cdead51 Merge branch 'main' of github.com:maoakeEnterprise/amazing 2026-03-24 16:12:19 +01:00
da7e 6730ebcdb5 It's aliiiive! 2026-03-24 16:10:57 +01:00
Maoake Teriierooiterai c478400640 fix the cell pydantic cause the program was too long 2026-03-24 15:34:43 +01:00
Maoake Teriierooiterai 993bcce857 add generator to my maze generator DFS 2026-03-24 15:22:20 +01:00
Maoake Teriierooiterai a85e342a0a fix conflict 2026-03-24 14:31:49 +01:00
Maoake Teriierooiterai 4d151664ab finish the generator DFS 2026-03-24 14:28:10 +01:00
Maoake Teriierooiterai 8b4ef7afce finish the maze generator 2026-03-24 11:10:16 +01:00
Maoake Teriierooiterai 030c6142ba need to fix my infinite while so i make a checkpoint if i need to restore it 2026-03-24 09:34:53 +01:00
Maoake Teriierooiterai f8f0e31598 fix some bug with my unit testing on the DFS 2026-03-23 19:24:32 +01:00
Maoake Teriierooiterai e75e14110d adding my maze need to be tested 2026-03-23 18:49:13 +01:00
16 changed files with 693 additions and 155 deletions
+1
View File
@@ -214,4 +214,5 @@ __marimo__/
# Streamlit # Streamlit
.streamlit/secrets.toml .streamlit/secrets.toml
test.txt
+14
View File
@@ -1,9 +1,13 @@
install: install:
uv sync uv sync
uv pip install mlx-2.2-py3-none-any.whl
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
@@ -18,5 +22,15 @@ lint-strict:
uv run flake8 . uv run flake8 .
uv run mypy . --strict 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_maze_gen:
PYTHONPATH=src uv run pytest tests/test_MazeGenerator.py
run_test: run_test:
uv run pytest uv run pytest
mlx:
uv run python3 test.py
+112 -17
View File
@@ -1,23 +1,118 @@
import os from typing import Any
from numpy import ma from src.AMazeIng import AMazeIng
from src.amaz_lib import MazeGenerator, Kruskal, AStar from src.parsing import Parsing
from src.amaz_lib import Maze from mlx.mlx import Mlx
import numpy as np
import math
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.generator = None
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 close(self) -> None:
self.mlx.mlx_destroy_image(self.mlx_ptr, self.img_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)
self.mlx.mlx_clear_window(self.mlx_ptr, self.win_ptr)
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, amazing: AMazeIng) -> None:
self.generator = amazing.generate()
def start(self, amazing: AMazeIng) -> None:
self.gen_maze(amazing)
self.mlx.mlx_loop_hook(self.mlx_ptr, self.render, amazing)
self.mlx.mlx_hook(self.win_ptr, 33, 0, self.close_loop, None)
self.mlx.mlx_loop(self.mlx_ptr)
def render(self, amazing: AMazeIng):
try:
next(self.generator)
self.update_maze(amazing.maze.get_maze())
# time.sleep(0.01)
except StopIteration:
pass
def main() -> None: def main() -> None:
# try: mlx = None
maze = Maze(maze=None) try:
generator = Kruskal() mlx = MazeMLX(1000, 1000)
for alg in generator.generator(20, 20): config = Parsing.DataMaze.get_data_maze("config.txt")
maze.set_maze(alg) amazing = AMazeIng(**config)
# os.system("clear") mlx.start(amazing)
maze.ascii_print() with open("test.txt", "w") as output:
# solver = AStar((1, 1), (14, 18)) output.write(amazing.__str__())
# print(solver.solve(maze)) except Exception as err:
print(err)
finally:
# except Exception as err: if mlx is not None:
# print(err) mlx.close()
if __name__ == "__main__": if __name__ == "__main__":
+7 -5
View File
@@ -1,6 +1,8 @@
WIDTH=200 WIDTH=20
HEIGHT=100 HEIGHT=20
ENTRY=0,0 ENTRY=1,1
EXIT=19,14 EXIT=2,2
OUTPUT_FILE=maze.txt OUTPUT_FILE=maze.txt
PERFECT=True PERFECT=False
GENERATOR=DFS
SOLVER=AStar
Binary file not shown.
+8 -9
View File
@@ -1,22 +1,20 @@
from dataclasses import field
from os import eventfd_read
from typing import Generator from typing import Generator
import numpy
from typing_extensions import Self from typing_extensions import Self
from pydantic import AfterValidator, BaseModel, Field, model_validator from pydantic import BaseModel, Field, model_validator, ConfigDict
from amaz_lib import Maze, MazeGenerator, MazeSolver from src.amaz_lib import Maze, MazeGenerator, MazeSolver
from amaz_lib.Cell import Cell
class AMazeIng(BaseModel): class AMazeIng(BaseModel):
width: int = Field(ge=3) model_config = ConfigDict(arbitrary_types_allowed=True)
height: int = Field(ge=3)
width: int = Field(ge=4)
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)
perfect: bool = Field(default=True) perfect: bool = Field(default=True)
maze: Maze = Field(default=Maze(maze=numpy.array([]))) maze: Maze = Field(default=Maze(None))
generator: MazeGenerator generator: MazeGenerator
solver: MazeSolver solver: MazeSolver
@@ -32,6 +30,7 @@ class AMazeIng(BaseModel):
for array in self.generator.generator(self.height, self.width): for array in self.generator.generator(self.height, self.width):
self.maze.set_maze(array) self.maze.set_maze(array)
yield self.maze yield self.maze
return
def solve_path(self) -> str: def solve_path(self) -> str:
return self.solver.solve(self.maze) return self.solver.solve(self.maze)
+5 -3
View File
@@ -1,8 +1,10 @@
from pydantic import BaseModel, Field from dataclasses import dataclass
class Cell(BaseModel): @dataclass
value: int = Field(ge=0, le=15) class Cell:
def __init__(self, value: int) -> None:
self.value = value
def __str__(self) -> str: def __str__(self) -> str:
return hex(self.value).removeprefix("0x").upper() return hex(self.value).removeprefix("0x").upper()
-2
View File
@@ -1,8 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
import numpy import numpy
from .Cell import Cell
from .MazeGenerator import MazeGenerator
@dataclass @dataclass
+280 -21
View File
@@ -1,5 +1,4 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Generator, Set from typing import Generator, Set
import numpy as np import numpy as np
from .Cell import Cell from .Cell import Cell
@@ -7,17 +6,102 @@ 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 self, height: int, width: int, seed: int | None = None
) -> Generator[np.ndarray, None, np.ndarray]: ... ) -> Generator[np.ndarray, None, np.ndarray]: ...
@staticmethod
def get_cell_ft(width: int, height: int) -> set:
forty_two = set()
y, x = (int(height / 2), int(width / 2))
forty_two.add((y, x - 1))
forty_two.add((y, x - 2))
forty_two.add((y, x - 3))
forty_two.add((y - 1, x - 3))
forty_two.add((y - 2, x - 3))
forty_two.add((y + 1, x - 1))
forty_two.add((y + 2, x - 1))
forty_two.add((y, x + 1))
forty_two.add((y, x + 2))
forty_two.add((y, x + 3))
forty_two.add((y - 1, x + 3))
forty_two.add((y - 2, x + 3))
forty_two.add((y - 2, x + 2))
forty_two.add((y - 2, x + 1))
forty_two.add((y + 1, x + 1))
forty_two.add((y + 2, x + 1))
forty_two.add((y + 2, x + 2))
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 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
class Sets:
def __init__(self, sets: list[Set]) -> None:
self.sets = sets
@staticmethod @staticmethod
def walls_to_maze( def walls_to_maze(
walls: np.ndarray, height: int, width: int walls: np.ndarray, height: int, width: int
@@ -47,9 +131,9 @@ class Kruskal(MazeGenerator):
return maze return maze
@staticmethod @staticmethod
def is_in_same_set(sets: np.ndarray, wall: tuple[int, int]) -> bool: def is_in_same_set(sets: Sets, wall: tuple[int, int]) -> bool:
a, b = wall a, b = wall
for set in sets: for set in sets.sets:
if a in set.cells and b in set.cells: if a in set.cells and b in set.cells:
return True return True
elif a in set.cells or b in set.cells: elif a in set.cells or b in set.cells:
@@ -57,22 +141,46 @@ class Kruskal(MazeGenerator):
return False return False
@staticmethod @staticmethod
def merge_sets(sets: np.ndarray, wall: tuple[int, int]) -> None: def merge_sets(sets: Sets, wall: tuple[int, int]) -> None:
a, b = wall a, b = wall
base_set = None base_set = None
for i in range(len(sets)): for i in range(len(sets.sets)):
if base_set is None and (a in sets[i].cells or b in sets[i].cells): if base_set is None and (
base_set = sets[i] a in sets.sets[i].cells or b in sets.sets[i].cells
elif base_set and (a in sets[i].cells or b in sets[i].cells): ):
base_set.cells += sets[i].cells base_set = sets.sets[i]
np.delete(sets, i) elif base_set and (
a in sets.sets[i].cells or b in sets.sets[i].cells
):
base_set.cells += sets.sets[i].cells
sets.sets.pop(i)
return return
raise Exception("two sets not found") raise Exception("two sets not found")
@staticmethod
def touch_ft(
width: int,
wall: tuple[int, int],
cells_ft: None | set[tuple[int, int]],
) -> bool:
if cells_ft is None:
return False
s1 = (math.trunc(wall[0] / width), wall[0] % width)
s2 = (math.trunc(wall[1] / width), wall[1] % width)
return s1 in cells_ft or s2 in cells_ft
def generator( def generator(
self, height: int, width: int self, height: int, width: int, seed: int | None = None
) -> Generator[np.ndarray, None, np.ndarray]: ) -> Generator[np.ndarray, None, np.ndarray]:
sets = np.array([self.Set([i]) for i in range(height * width)]) 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)
sets = self.Sets([self.Set([i]) for i in range(height * width)])
walls = [] walls = []
for h in range(height): for h in range(height):
for w in range(width - 1): for w in range(width - 1):
@@ -80,14 +188,165 @@ class Kruskal(MazeGenerator):
for h in range(height - 1): for h in range(height - 1):
for w in range(width): for w in range(width):
walls += [(w + (width * h), w + (width * (h + 1)))] walls += [(w + (width * h), w + (width * (h + 1)))]
print(walls)
np.random.shuffle(walls) np.random.shuffle(walls)
yield self.walls_to_maze(walls, height, width) yield self.walls_to_maze(walls, height, width)
for wall in walls: while (len(sets.sets) != 1 and cells_ft is None) or (
if not self.is_in_same_set(sets, wall): len(sets.sets) != 19 and cells_ft is not None
self.merge_sets(sets, wall) ):
walls.remove(wall) for wall in walls:
yield self.walls_to_maze(walls, height, width) if not self.is_in_same_set(sets, wall) and not self.touch_ft(
print(f"nb sets: {len(sets)}") width, wall, cells_ft
return self.walls_to_maze(walls, height, width) ):
self.merge_sets(sets, wall)
walls.remove(wall)
yield self.walls_to_maze(walls, height, width)
if (len(sets.sets) == 1 and cells_ft is None) or (
len(sets.sets) == 19 and cells_ft is not None
):
break
print(f"nb sets: {len(sets.sets)}")
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
) -> Generator[np.ndarray, None, np.ndarray]:
if seed is not None:
np.random.seed(seed)
maze = self.init_maze(width, height)
if width > 9 and height > 9:
self.forty_two = self.get_cell_ft(width, height)
visited = np.zeros((height, width), dtype=bool)
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)
x, y = coord
first_iteration = True
while path or first_iteration:
first_iteration = False
visited[y, x] = True
path = self.add_cell_visited(coord, path)
random_c = self.random_cells(visited, coord, w_h)
if not random_c:
path = self.back_on_step(path, w_h, visited)
if not path:
break
coord = path[-1]
random_c = self.random_cells(visited, coord, w_h)
x, y = coord
wall = self.next_step(random_c)
maze[y][x] = self.broken_wall(maze[y][x], wall)
coord = self.next_cell(x, y, wall)
wall_r = self.reverse_path(wall)
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
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, path: set) -> list:
path.append(coord)
return path
@staticmethod
def random_cells(visited: np.array, coord: tuple, w_h: tuple) -> list:
rand_cell = []
x, y = coord
width, height = w_h
if y - 1 >= 0 and not visited[y - 1][x]:
rand_cell.append("N")
if y + 1 < height and not visited[y + 1][x]:
rand_cell.append("S")
if x - 1 >= 0 and not visited[y][x - 1]:
rand_cell.append("W")
if x + 1 < width and not visited[y][x + 1]:
rand_cell.append("E")
return rand_cell
@staticmethod
def next_step(rand_cell: list) -> str:
return np.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)
@staticmethod
def reverse_path(direction: str) -> str:
return {"N": "S", "S": "N", "W": "E", "E": "W"}[direction]
@staticmethod
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):
break
path.pop()
return path
@staticmethod
def lock_cell_ft(
visited: np.ndarray, forty_two: set[tuple[int]]
) -> np.ndarray:
tab = [cell for cell in forty_two]
for cell in tab:
visited[cell] = True
return visited
+134 -31
View File
@@ -5,11 +5,12 @@ import numpy as np
class MazeSolver(ABC): class MazeSolver(ABC):
def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None: def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None:
self.start = (start[0] - 1, start[1] - 1) self.start = (start[1] - 1, start[0] - 1)
self.end = (end[0] - 1, end[1] - 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):
@@ -48,35 +49,39 @@ class AStar(MazeSolver):
return 1000 return 1000
def best_path( def best_path(
self, maze: np.ndarray, actual: tuple[int, int] self,
) -> dict[str, int | None]: maze: np.ndarray,
print(actual) actual: tuple[int, int],
last: str | None,
) -> dict[str, int]:
path = { path = {
"N": ( "N": (
self.f((actual[0], actual[1] - 1)) self.f((actual[0], actual[1] - 1))
if not maze[actual[0]][actual[1]].get_north() and actual[1] > 0 if not maze[actual[1]][actual[0]].get_north() and actual[1] > 0
else None else None
), ),
"E": ( "E": (
self.f((actual[0] + 1, actual[1])) self.f((actual[0] + 1, actual[1]))
if not maze[actual[0]][actual[1]].get_est() if not maze[actual[1]][actual[0]].get_est()
and actual[0] < len(maze) - 1 and actual[0] < len(maze[0]) - 1
else None else None
), ),
"S": ( "S": (
self.f((actual[0], actual[1] + 1)) self.f((actual[0], actual[1] + 1))
if not maze[actual[0]][actual[1]].get_south() if not maze[actual[1]][actual[0]].get_south()
and actual[1] < len(maze[0]) - 1 and actual[1] < len(maze) - 1
else None else None
), ),
"W": ( "W": (
self.f((actual[0] - 1, actual[1])) self.f((actual[0] - 1, actual[1]))
if not maze[actual[0]][actual[1]].get_west() and actual[0] > 0 if not maze[actual[1]][actual[0]].get_west() and actual[0] > 0
else None else None
), ),
} }
return { return {
k: v for k, v in sorted(path.items(), key=lambda item: item[0]) k: v
for k, v in sorted(path.items(), key=lambda item: item[0])
if v is not None and k != last
} }
def get_opposit(self, dir: str) -> str: def get_opposit(self, dir: str) -> str:
@@ -107,28 +112,126 @@ class AStar(MazeSolver):
case _: case _:
return actual return actual
def get_path( def get_path(self, maze: np.ndarray) -> str | None:
self, actual: tuple[int, int], maze: np.ndarray, pre: str | None path = [(self.start, self.best_path(maze, self.start, None))]
) -> str | None: visited = [self.start]
if actual == self.end: while len(path) > 0 and path[-1][0] != self.end:
return "" if len(path[-1][1]) == 0:
paths = self.best_path(maze, actual) path.pop(-1)
for path in paths: if len(path) == 0:
if paths[path] is None: break
k = next(iter(path[-1][1]))
path[-1][1].pop(k)
continue continue
if path != pre:
temp = self.get_path( while len(path[-1][1]) > 0:
self.get_next_pos(path, actual), next_pos = self.get_next_pos(
maze, list(path[-1][1].keys())[0], path[-1][0]
self.get_opposit(path),
) )
if not temp is None: if next_pos in visited:
return path + temp k = next(iter(path[-1][1]))
return None 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),
)
)
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) -> str: def solve(self, maze: Maze) -> str:
print(maze) res = self.get_path(maze.get_maze())
res = self.get_path(self.start, maze.get_maze(), None)
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)
+5 -3
View File
@@ -1,8 +1,10 @@
from .Cell import Cell from .Cell import Cell
from .Maze import Maze from .Maze import Maze
from .MazeGenerator import MazeGenerator, Kruskal from .MazeGenerator import MazeGenerator, DepthFirstSearch
from .MazeSolver import MazeSolver, AStar from .MazeGenerator import Kruskal
from .MazeSolver import MazeSolver, AStar, DepthFirstSearchSolver
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "us" __author__ = "us"
__all__ = ["Cell", "Maze", "MazeGenerator", "MazeSolver", "AStar", "Kruskal"] __all__ = ["Cell", "Maze", "MazeGenerator", "DepthFirstSearchSolver",
"MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"]
+63 -35
View File
@@ -1,3 +1,7 @@
from src.amaz_lib.MazeGenerator import DepthFirstSearch, Kruskal
from src.amaz_lib.MazeSolver import AStar
class DataMaze: class DataMaze:
@staticmethod @staticmethod
@@ -11,28 +15,30 @@ class DataMaze:
@staticmethod @staticmethod
def transform_data(data: str) -> dict: def transform_data(data: str) -> dict:
tmp = data.split("\n") tmp = data.split("\n")
tmp2 = [ tmp2 = [value.split("=", 1) for value in tmp if "=" in value]
value.split("=", 1) for value in tmp data_t = {value[0]: value[1] for value in tmp2}
]
data_t = {
value[0]: value[1] for value in tmp2
}
return data_t return data_t
@staticmethod @staticmethod
def verif_key_data(data: dict) -> None: def verif_key_data(data: dict) -> None:
key_test = { key_test = {
"WIDTH", "HEIGHT", "ENTRY", "EXIT", "OUTPUT_FILE", "PERFECT" "WIDTH",
} "HEIGHT",
set_key = { "ENTRY",
key for key in data.keys() "EXIT",
"OUTPUT_FILE",
"PERFECT",
"GENERATOR",
"SOLVER",
} }
set_key = {key for key in data.keys()}
if len(set_key) != len(key_test): if len(set_key) != len(key_test):
raise KeyError("Missing some data the len do not correspond") raise KeyError("Missing some data the len do not correspond")
res_key = {key for key in set_key if key not in key_test} res_key = {key for key in set_key if key not in key_test}
if len(res_key) != 0: if len(res_key) != 0:
raise KeyError("Some Key " raise KeyError(
f"do not correspond the keys: {res_key}") "Some Key " f"do not correspond the keys: {res_key}"
)
@staticmethod @staticmethod
def convert_values(data: dict): def convert_values(data: dict):
@@ -47,8 +53,47 @@ class DataMaze:
for key in key_bool: for key in key_bool:
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(
DataMaze.get_solver_generator(data, res["ENTRY"], res["EXIT"],
res["PERFECT"])
)
return res return res
@staticmethod
def get_solver_generator(data: dict, entry: tuple, exit: tuple,
perfect: bool) -> dict:
available_generator = {
"Kruskal": Kruskal,
"DFS": DepthFirstSearch,
}
available_solver = {
"AStar": AStar,
}
res = {}
res["GENERATOR"] = available_generator[data["GENERATOR"]](entry, exit,
perfect)
res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit)
return res
@staticmethod
def convert_tuple(data: str) -> tuple:
data_t = data.split(",")
if len(data_t) != 2:
raise ValueError(
"There is too much " "argument in the coordinate given"
)
x, y = data_t
tup = (int(x), int(y))
return tup
@staticmethod
def convert_bool(data: str) -> bool:
if data != "True" and data != "False":
raise ValueError("This is not True or False")
if data == "True":
return True
return False
@staticmethod @staticmethod
def get_data_maze(name_file: str) -> dict: def get_data_maze(name_file: str) -> dict:
try: try:
@@ -56,7 +101,7 @@ class DataMaze:
data_dict = DataMaze.transform_data(data_str) data_dict = DataMaze.transform_data(data_str)
DataMaze.verif_key_data(data_dict) DataMaze.verif_key_data(data_dict)
data_maze = DataMaze.convert_values(data_dict) data_maze = DataMaze.convert_values(data_dict)
return data_maze return {k.lower(): v for k, v in data_maze.items()}
except FileNotFoundError: except FileNotFoundError:
print("The file do not exist") print("The file do not exist")
exit() exit()
@@ -70,28 +115,11 @@ class DataMaze:
print(f"Error on the key in the file: {e}") print(f"Error on the key in the file: {e}")
exit() exit()
except IndexError as e: except IndexError as e:
print("In the function transform Data some data cannot " print(
f"be splited by '=' because '=' was not present: {e}") "In the function transform Data some data cannot "
f"be splited by '=' because '=' was not present: {e}"
)
exit() exit()
except AttributeError as e: except AttributeError as e:
print("Error on the " print("Error on the " f"funciton get_data_maze : {e}")
f"funciton get_data_maze : {e}")
exit() exit()
@staticmethod
def convert_tuple(data: str) -> tuple:
data_t = data.split(",")
if len(data_t) != 2:
raise ValueError("There is too much "
"argument in the coordinate given")
x, y = data_t
tup = (int(x), int(y))
return tup
@staticmethod
def convert_bool(data: str) -> bool:
if data != "True" and data != "False":
raise ValueError("This is not True or False")
if data == "True":
return True
return False
+22 -21
View File
@@ -1,23 +1,24 @@
7D53BFD3D57951517D1D B9153957955513953953
3D12C3903BD03AD4178D AEA96A9569792C6BAAD6
2BAEBEEEAA92EED547C9 C5443AA9169281102C53
2287ED17AAAC5393FFF0 95556A82816C2AC2A952
6C6951292A87D2AEBD30 A93916A86A956C3A86D6
37D43E8686E93AABAB8C AEC6C542944513806953
21516D2D47FEE8284049 C395553AC393C2AC787A
6C7857C3FB9116C696D8 BC69512C7AAC56855692
751453D6D2AAC57BE970 A952BAAF96AFFFAD53AE
3BA952D17EA83BD05470 A810686FC5057FC516C3
22AAD2907BAE86967B74 AAC4543FFFAFFFB96952
2AA83C2EFC69696FBC35 AC5553817FAFD52ABC3A
686EE96FD7D4783FAD21 815552843FEFFF80296A
7ED17ED3D57D3EC52FA0 AC553A85413D55406C12
7B943D16FB7BABD3AFC8 C53BAAC392C3953C13AA
7407C5297EB82EB84174 9386AC386A9683C56AAA
392D53C6912EE9447E9D 846903AE96C568517C2A
62A952BBAAC13EFD7B89 AD3A82C385397C3C5546
3AAC3EC6EABAAD557824 C12AA87AA94293AD5513
66C7C7D7D6C6C7D556CD D46C6C5446D46C45556E
1,1 1,1
16,15 2,2
SSEEENENWWWS
+27
View File
@@ -0,0 +1,27 @@
from amaz_lib.MazeGenerator import DepthFirstSearch
from amaz_lib.Cell import Cell
import numpy as np
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 = np.zeros((10, 10), dtype=bool)
lst[0, 0] = True
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") == (2, 3)
def test_reverse_path(self) -> None:
assert DepthFirstSearch.reverse_path("N") == "S"
+1 -1
View File
@@ -15,7 +15,7 @@ def test_maze_setter_getter() -> None:
) )
maze.set_maze(test) maze.set_maze(test)
assert numpy.array_equal(maze.get_maze(), test) == True assert numpy.array_equal(maze.get_maze(), test) is True
def test_maze_str() -> None: def test_maze_str() -> None:
+14 -7
View File
@@ -1,11 +1,18 @@
import numpy import numpy
from amaz_lib.MazeGenerator import Kruskal from amaz_lib.MazeGenerator import DepthFirstSearch, MazeGenerator
def test_kruskal_output_shape() -> None: class TestMazeGenerator:
generator = Kruskal()
maze = numpy.array([])
for output in generator.generator(10, 10):
maze = output
assert maze.shape == (10, 10) def test_generator(self) -> None:
w_h = (10, 10)
maze = numpy.array([])
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