54 Commits

Author SHA1 Message Date
maoake 40e25757c7 starting mypy with maze 2026-03-31 22:31:48 +02:00
maoake b1eda06fa5 fixing flake8 2026-03-31 22:01:45 +02:00
maoake 769198c06b adding the blink on the 42 2026-03-31 21:03:10 +02:00
maoake 2c7b565137 give a checkpoint to the project blink the 42 2026-03-31 20:29:01 +02:00
maoake d23959ce74 fix conflict 2026-03-31 20:17:08 +02:00
maoake 4cb678b5be something is up 2026-03-31 19:59:09 +02:00
da7e b520210d58 fix(MazeMLX): margin calculation, big maze are now display fully 2026-03-30 16:36:52 +02:00
da7e bdb1056d69 fix(AmazMLX): draw_ft margin 2026-03-30 15:57:16 +02:00
da7e b2aa93e04d add color to put block 2026-03-30 15:47:39 +02:00
da7e 56ebb2823a code refactor(AmazMLX) 2026-03-30 15:45:15 +02:00
da7e 150eaedc94 Merge branch 'main' of github.com:maoakeEnterprise/amazing 2026-03-30 15:41:35 +02:00
da7e 6f4699c29f wip(entry exit) 2026-03-30 15:37:45 +02:00
Maoake Teriierooiterai 5913f5267d trying to get the blink on the 42 2026-03-30 15:36:52 +02:00
Maoake Teriierooiterai d4251dc8b7 fixing the conflict 2026-03-30 14:47:16 +02:00
Maoake Teriierooiterai 282fbd6867 poop the conflict 2026-03-30 14:39:05 +02:00
da7e 0f77e0c6e4 fix buffer overflow in put pixel + margin calculation 2026-03-30 14:37:33 +02:00
Maoake Teriierooiterai cfac4bed25 need to add the color 2026-03-30 13:53:14 +02:00
Maoake Teriierooiterai cd3c75fb1e set up the path print with the button 2026-03-30 12:01:23 +02:00
Maoake Teriierooiterai 628bb8a94b put the functions color and need to refactor the code 2026-03-30 08:26:53 +02:00
mteriier dc19b526fa testing colors on the project cause we need to test it out 2026-03-29 23:35:42 +02:00
Maoake Teriierooiterai 68d710e313 color 42 2026-03-29 18:47:29 +02:00
da7e 92c6237f06 fix(astar): the actual astar wasn't the real astar algoritm 2026-03-29 15:38:40 +02:00
Maoake Teriierooiterai b682274102 opti path 2026-03-29 14:31:04 +02:00
mteriier d534993f4c starting my branch need to rush this 2026-03-28 23:01:42 +01:00
da7e fa38f7a311 Merge branch 'mlx' 2026-03-27 21:53:06 +01:00
da7e 16d97e9912 fix(astar): function f() miscalculate the best path 2026-03-27 21:51:49 +01:00
da7e b317f7a3a0 FIX(path render): path render was called twice 2026-03-27 21:42:14 +01:00
da7e 2fc67683d8 add key handling without color management (not implemented) 2026-03-27 20:58:28 +01:00
da7e cb19cf1413 ADD(mlx path animation) 2026-03-27 19:47:21 +01:00
da7e 6ec617848f Merge branch 'main' of github.com:maoakeEnterprise/amazing into mlx 2026-03-27 18:29:39 +01:00
da7e 349e58ce41 ifpjefp 2026-03-27 18:29:09 +01:00
Maoake Teriierooiterai b078241359 fix something on the solver 2026-03-27 18:05:05 +01:00
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
15 changed files with 846 additions and 230 deletions
+1
View File
@@ -214,4 +214,5 @@ __marimo__/
# Streamlit
.streamlit/secrets.toml
test.txt
+6
View File
@@ -1,9 +1,13 @@
install:
uv sync
uv pip install mlx-2.2-py3-none-any.whl
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
@@ -28,3 +32,5 @@ run_test_maze_gen:
PYTHONPATH=src uv run pytest tests/test_MazeGenerator.py
run_test:
uv run pytest
mlx:
uv run python3 test.py
+348 -16
View File
@@ -1,23 +1,355 @@
import os
from src.amaz_lib import Maze
from src.amaz_lib import MazeGenerator
import src.amaz_lib as g
from typing import Any
from src.AMazeIng import AMazeIng
from src.parsing import Parsing
from mlx import Mlx
import numpy as np
import time
def main(maze_gen: MazeGenerator) -> None:
# try:
maze = Maze(maze=None)
for alg in maze_gen.generator(30, 10):
maze.set_maze(alg)
os.system("clear")
maze.ascii_print()
# solver = AStar((1, 1), (14, 18))
# print(solver.solve(maze))
class MazeMLX:
def __init__(self, height: int, width: int) -> None:
self.mlx = Mlx()
self.height = height
self.width = width
self.print_path = False
self.color = [0x00, 0x00, 0xFF, 0xFF]
self.mlx_ptr = self.mlx.mlx_init()
self.win_ptr = self.mlx.mlx_new_window(
self.mlx_ptr, width, height + 200, "A-Maze-Ing"
)
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)
)
self.path_printer = None
self.generator = None
def close(self) -> None:
self.mlx.mlx_destroy_image(self.mlx_ptr, self.img_ptr)
def close_loop(self, _: Any):
self.mlx.mlx_loop_exit(self.mlx_ptr)
def clear_image(self) -> None:
self.buf[:] = b"\x00" * len(self.buf)
def redraw_image(self) -> None:
self.mlx.mlx_clear_window(self.mlx_ptr, self.win_ptr)
self.mlx.mlx_put_image_to_window(
self.mlx_ptr, self.win_ptr, self.img_ptr, 0, 0
)
self.mlx.mlx_string_put(
self.mlx_ptr,
self.win_ptr,
self.width // 3,
self.height + 100,
0xFFFFFF,
"1: regen; 2: path; 3: color; 4: quit;",
)
def put_pixel(self, x, y, color: list | None = None) -> None:
if x < 0 or y < 0 or x >= self.width or y >= self.height:
return
offset = y * self.size_line + x * (self.bpp // 8)
if color:
self.buf[offset + 0] = color[0]
self.buf[offset + 1] = color[1]
self.buf[offset + 2] = color[2]
if self.bpp >= 32:
self.buf[offset + 3] = color[3]
else:
self.buf[offset + 0] = self.color[0]
self.buf[offset + 1] = self.color[1]
self.buf[offset + 2] = self.color[2]
if self.bpp >= 32:
self.buf[offset + 3] = self.color[3]
def put_line(
self,
start: tuple[int, int],
end: tuple[int, int],
color: list | None = None,
) -> 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, color)
if sx == ex:
for y in range(min(sy, ey), max(sy, ey) + 1):
self.put_pixel(sx, y, color)
def put_block(
self,
ul: tuple[int, int],
dr: tuple[int, int],
color: list | None = None,
) -> None:
for y in range(min(ul[1], dr[1]), max(dr[1], ul[1])):
self.put_line(
(min(ul[0], dr[0]), y), (max(ul[0], dr[0]), y), color
)
@staticmethod
def random_color_ft() -> Any:
colors = [
[0xFF, 0xBF, 0x00, 0xFF], # blue
[0x00, 0xFF, 0x40, 0xFF], # green
[0xFF, 0x00, 0xFF, 0xFF], # pink
[0x00, 0xFF, 0xFF, 0xFF], # yellow
]
while True:
for color in colors:
yield color
@staticmethod
def random_color() -> Any:
colors = [
[0xFF, 0x00, 0xFF, 0xFF], # pink
[0x00, 0xFF, 0xFF, 0xFF], # yellow
[0x00, 0xFF, 0x40, 0xFF], # green
[0xFF, 0xBF, 0x00, 0xFF], # blue
[0xFF, 0x00, 0x80, 0xFF], # purple
[0x00, 0x00, 0xFF, 0xFF], # red
]
while True:
for color in colors:
yield color
def get_margin_line_len(self, maze: np.ndarray) -> tuple[int, int, int]:
rows = len(maze)
cols = len(maze[0])
line_len = min(self.width // cols, self.height // rows) - 1
maze_width = cols * line_len
maze_height = rows * line_len
margin_x = ((self.width - maze_width) // 2) + 1
margin_y = ((self.height - maze_height) // 2) + 1
return (line_len, margin_x, margin_y)
def update_maze(self, maze: np.ndarray) -> None:
self.clear_image()
line_len, margin_x, margin_y = self.get_margin_line_len(maze)
for y in range(len(maze)):
for x in range(len(maze[0])):
x0 = x * line_len + margin_x
y0 = y * line_len + margin_y
x1 = x * line_len + line_len + margin_x
y1 = y * line_len + line_len + margin_y
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))
def put_path(self, amazing: AMazeIng) -> Any:
path = amazing.solve_path()
print(path)
actual = amazing.entry
actual = (actual[0] - 1, actual[1] - 1)
maze = amazing.maze.get_maze()
if maze is None:
return
line_len, margin_x, margin_y = self.get_margin_line_len(maze)
for i in range(len(path)):
ul = (
(actual[0]) * line_len + margin_x + 12,
(actual[1]) * line_len + 12 + margin_y,
)
dr = (
(actual[0]) * line_len + line_len + margin_x - 12,
(actual[1]) * line_len + line_len - 12 + margin_y,
)
self.put_block(ul, dr)
x0 = actual[0] * line_len + margin_x + 12
y0 = actual[1] * line_len + margin_y + 12
x1 = actual[0] * line_len + line_len + margin_x - 12
y1 = actual[1] * line_len + line_len + margin_y - 12
yield
match path[i]:
case "N":
self.put_block((x0, y0), (x1, y0 - 24))
actual = (actual[0], actual[1] - 1)
case "E":
self.put_block((x1, y0), (x1 + 24, y1))
actual = (actual[0] + 1, actual[1])
case "S":
self.put_block((x0, y1), (x1, y1 + 24))
actual = (actual[0], actual[1] + 1)
case "W":
self.put_block((x0, y0), (x0 - 24, y1))
actual = (actual[0] - 1, actual[1])
ul = (
(actual[0]) * line_len + margin_x + 12,
(actual[1]) * line_len + 12 + margin_y,
)
dr = (
(actual[0]) * line_len + line_len + margin_x - 12,
(actual[1]) * line_len + line_len - 12 + margin_y,
)
self.put_block(ul, dr)
return
def put_start_end(self, amazing: AMazeIng):
entry = amazing.entry
exit = amazing.exit
maze = amazing.maze.get_maze()
if maze is None:
return
line_len, margin_x, margin_y = self.get_margin_line_len(maze)
ul = (
(entry[0] - 1) * line_len + margin_x + 3,
(entry[1] - 1) * line_len + 3 + margin_y,
)
dr = (
(entry[0] - 1) * line_len + line_len + margin_x - 3,
(entry[1] - 1) * line_len + line_len - 3 + margin_y,
)
self.put_block(ul, dr, [0xFF, 0xBF, 0x00, 0x9F])
ul = (
(exit[0] - 1) * line_len + margin_x + 3,
(exit[1] - 1) * line_len + 3 + margin_y,
)
dr = (
(exit[0] - 1) * line_len + line_len + margin_x - 3,
(exit[1] - 1) * line_len + line_len - 3 + margin_y,
)
self.put_block(ul, dr, [0x00, 0xFF, 0x40, 0x9F])
def draw_ft(self, maze: np.ndarray, color: list | None = None):
line_len, margin_x, margin_y = self.get_margin_line_len(maze)
for y in range(len(maze)):
for x in range(len(maze[0])):
if maze[y][x].value == 15:
x0 = x * line_len + margin_x
y0 = y * line_len + margin_y
x1 = x * line_len + line_len + margin_x
y1 = y * line_len + line_len + margin_y
self.put_block((x0, y0), (x1, y1), color)
def draw_image(self, amazing: AMazeIng) -> None:
if self.render_maze(amazing):
if self.path_printer and self.print_path:
if self.render_path():
color = next(self.color_gen_ft)
self.draw_ft(amazing.maze.get_maze(), color)
next(self.timer_gen)
else:
self.time_gen()
self.update_maze(amazing.maze.get_maze())
self.draw_ft(amazing.maze.get_maze())
self.put_start_end(amazing)
self.redraw_image()
def shift_color(self):
self.color_gen = self.random_color()
def shift_color_ft(self):
self.color_gen_ft = self.random_color_ft()
def time_gen(self):
self.timer_gen = self.time_generator()
def restart_maze(self, amazing: AMazeIng) -> None:
self.generator = amazing.generate()
def time_generator(self) -> Any:
yield
while True:
time.sleep(0.3)
yield
def restart_path(self, amazing: AMazeIng) -> None:
self.path_printer = self.put_path(amazing)
def render_path(self) -> bool:
try:
next(self.path_printer)
time.sleep(0.03)
return False
except StopIteration:
pass
return True
def render_maze(self, amazing: AMazeIng) -> bool:
try:
next(self.generator)
self.update_maze(amazing.maze.get_maze())
return False
except StopIteration:
pass
return True
def handle_key_press(self, keycode: int, amazing: AMazeIng) -> None:
if keycode == 49:
self.restart_maze(amazing)
self.print_path = False
if keycode == 50:
self.restart_path(amazing)
self.print_path = True if self.print_path is False else False
if keycode == 51:
self.print_path = False
self.color = next(self.color_gen)
if keycode == 52:
self.close_loop(None)
def handle_key_press_mteriier(self, keycode: int,
amazing: AMazeIng) -> None:
if keycode == 38:
self.restart_maze(amazing)
self.print_path = False
if keycode == 233:
self.restart_path(amazing)
self.print_path = True if self.print_path is False else False
if keycode == 34:
self.print_path = False
self.color = next(self.color_gen)
if keycode == 39:
self.close_loop(None)
def start(self, amazing: AMazeIng) -> None:
self.restart_maze(amazing)
self.shift_color()
self.shift_color_ft()
self.time_gen()
self.mlx.mlx_loop_hook(self.mlx_ptr, self.draw_image, amazing)
self.mlx.mlx_hook(self.win_ptr, 33, 0, self.close_loop, None)
self.mlx.mlx_hook(
self.win_ptr, 2, 1 << 0, self.handle_key_press_mteriier, amazing
)
self.mlx.mlx_loop(self.mlx_ptr)
# except Exception as err:
# print(err)
def main() -> None:
mlx = None
try:
mlx = MazeMLX(1000, 1000)
config = Parsing.DataMaze.get_data_maze("config.txt")
amazing = AMazeIng(**config)
mlx.start(amazing)
with open("test.txt", "w") as output:
output.write(amazing.__str__())
except Exception as err:
print(err)
finally:
if mlx is not None:
mlx.close()
if __name__ == "__main__":
main(g.DepthFirstSearch())
main()
+7 -5
View File
@@ -1,6 +1,8 @@
WIDTH=200
HEIGHT=100
ENTRY=0,0
EXIT=19,14
WIDTH=13
HEIGHT=13
ENTRY=1,1
EXIT=5,5
OUTPUT_FILE=maze.txt
PERFECT=True
PERFECT=False
GENERATOR=Kruskal
SOLVER=AStar
Binary file not shown.
+11 -12
View File
@@ -1,30 +1,28 @@
from dataclasses import field
from os import eventfd_read
from typing import Generator
import numpy
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 amaz_lib.Cell import Cell
from src.amaz_lib import Maze, MazeGenerator, MazeSolver
class AMazeIng(BaseModel):
width: int = Field(ge=3)
height: int = Field(ge=3)
model_config = ConfigDict(arbitrary_types_allowed=True)
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)
perfect: bool = Field(default=True)
maze: Maze = Field(default=Maze(maze=numpy.array([])))
maze: Maze = Field(default=Maze(None))
generator: MazeGenerator
solver: MazeSolver
@model_validator(mode="after")
def check_entry_exit(self) -> Self:
if self.entry[0] >= self.width or self.entry[1] >= self.height:
if self.entry[0] > self.width or self.entry[1] > self.height:
raise ValueError("Entry coordinates exceed the maze size")
if self.exit[0] >= self.width or self.exit[1] >= self.height:
if self.exit[0] > self.width or self.exit[1] > self.height:
raise ValueError("Exit coordinates exceed the maze size")
return self
@@ -32,9 +30,10 @@ class AMazeIng(BaseModel):
for array in self.generator.generator(self.height, self.width):
self.maze.set_maze(array)
yield self.maze
return
def solve_path(self) -> str:
return self.solver.solve(self.maze)
return self.solver.solve(self.maze, self.height, self.width)
def __str__(self) -> str:
res = self.maze.__str__()
+8 -7
View File
@@ -1,18 +1,16 @@
from dataclasses import dataclass
import numpy
from .Cell import Cell
from .MazeGenerator import MazeGenerator
from numpy.typing import NDArray
from typing import Optional, Any
@dataclass
class Maze:
maze: numpy.ndarray
maze: Optional[NDArray[Any]] = None
def get_maze(self) -> numpy.ndarray | None:
def get_maze(self) -> Optional[NDArray[Any]]:
return self.maze
def set_maze(self, new_maze: numpy.ndarray) -> None:
def set_maze(self, new_maze: NDArray[Any]) -> None:
self.maze = new_maze
def __str__(self) -> str:
@@ -26,6 +24,9 @@ class Maze:
return res
def ascii_print(self) -> None:
if self.maze is None:
print("None")
return
for cell in self.maze[0]:
print("_", end="")
if cell.get_north():
+177 -37
View File
@@ -6,13 +6,94 @@ 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
self, height: int, width: int, seed: int | None = None
) -> 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 Set:
def __init__(self, cells: list[int]) -> None:
self.cells: list[int] = cells
@@ -76,9 +157,29 @@ class Kruskal(MazeGenerator):
return
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(
self, height: int, width: int
self, height: int, width: int, seed: int | None = None
) -> Generator[np.ndarray, None, np.ndarray]:
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 = []
for h in range(height):
@@ -87,57 +188,92 @@ class Kruskal(MazeGenerator):
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)
while len(sets.sets) > 1:
while (len(sets.sets) != 1 and cells_ft is None) or (
len(sets.sets) != 19 and cells_ft is not None
):
for wall in walls:
if not self.is_in_same_set(sets, wall):
if not self.is_in_same_set(sets, wall) and not self.touch_ft(
width, wall, cells_ft
):
self.merge_sets(sets, wall)
walls.remove(wall)
yield self.walls_to_maze(walls, height, width)
if len(sets.sets) == 1:
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)}")
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
self, height: int, width: int, seed: int = None
) -> Generator[np.ndarray, None, np.ndarray]:
maze = DepthFirstSearch.init_maze(width, height)
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 = True
first_iteration = True
while path or first_iteration:
first_iteration = False
while path or first:
first = False
visited[y, x] = True
path = DepthFirstSearch.add_cell_visited(coord, path)
random_c = DepthFirstSearch.random_cells(visited, coord, w_h)
if len(random_c) == 0:
path = DepthFirstSearch.back_on_step(path, w_h, visited)
if path:
coord = path[-1]
random_c = DepthFirstSearch.random_cells(visited, coord, w_h)
x, y = coord
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 = DepthFirstSearch.next_step(random_c)
maze[y][x] = DepthFirstSearch.broken_wall(maze[y][x], wall)
wall = self.next_step(random_c)
maze[y][x] = self.broken_wall(maze[y][x], wall)
coord = DepthFirstSearch.next_cell(x, y, wall)
wall_r = DepthFirstSearch.reverse_path(wall)
coord = self.next_cell(x, y, wall)
wall_r = self.reverse_path(wall)
x, y = coord
maze[y][x] = DepthFirstSearch.broken_wall(maze[y][x], wall_r)
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
@@ -194,19 +330,23 @@ class DepthFirstSearch(MazeGenerator):
return (x + add_x, y + add_y)
@staticmethod
def reverse_path(next: str) -> str:
reverse = {"N": "S", "S": "N", "W": "E", "E": "W"}
return reverse[next]
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.array) -> list:
last = path[-1]
r_cells = DepthFirstSearch.random_cells(visited, last, w_h)
while len(path) > 0:
path.pop()
if path:
last = path[-1]
r_cells = DepthFirstSearch.random_cells(visited, last, w_h)
if r_cells:
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
+214 -88
View File
@@ -1,121 +1,247 @@
from abc import ABC, abstractmethod
from .Maze import Maze
from typing import Any
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)
self.start = (start[1] - 1, start[0] - 1)
self.end = (end[1] - 1, end[0] - 1)
@abstractmethod
def solve(self, maze: Maze) -> str: ...
def solve(
self, maze: Maze, height: int | None = None, width: int | None = 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 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])
)
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[1] - 1, actual[0]))
if not maze[actual[1]][actual[0]].get_north() and actual[0] > 0
def get_paths(
self,
maze: np.ndarray,
actual: tuple[int, int],
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[1], actual[0] + 1))
(
(actual[0] + 1, actual[1])
if not maze[actual[1]][actual[0]].get_est()
and actual[1] < len(maze) - 1
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[1] + 1, actual[0]))
(
(actual[0], actual[1] + 1)
if not maze[actual[1]][actual[0]].get_south()
and actual[0] < len(maze) - 1
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[1], actual[0] - 1))
if not maze[actual[1]][actual[0]].get_west() and actual[1] > 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])
}
]
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
open.append(
AStar.Node(
self.start,
0,
self.h(self.start),
self.h(self.start),
None,
)
)
def get_path(self, maze: np.ndarray) -> str | None:
actual = self.start
path = ""
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")
return None
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 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")
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 = None, width: int | None = 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 = None, width: int | None = 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"]
+64 -35
View File
@@ -1,3 +1,7 @@
from src.amaz_lib.MazeGenerator import DepthFirstSearch, Kruskal
from src.amaz_lib.MazeSolver import AStar, DepthFirstSearchSolver
class DataMaze:
@staticmethod
@@ -11,28 +15,30 @@ class DataMaze:
@staticmethod
def transform_data(data: str) -> dict:
tmp = data.split("\n")
tmp2 = [
value.split("=", 1) for value in tmp
]
data_t = {
value[0]: value[1] for value in tmp2
}
tmp2 = [value.split("=", 1) for value in tmp if "=" in value]
data_t = {value[0]: value[1] for value in tmp2}
return data_t
@staticmethod
def verif_key_data(data: dict) -> None:
key_test = {
"WIDTH", "HEIGHT", "ENTRY", "EXIT", "OUTPUT_FILE", "PERFECT"
}
set_key = {
key for key in data.keys()
"WIDTH",
"HEIGHT",
"ENTRY",
"EXIT",
"OUTPUT_FILE",
"PERFECT",
"GENERATOR",
"SOLVER",
}
set_key = {key for key in data.keys()}
if len(set_key) != len(key_test):
raise KeyError("Missing some data the len do not correspond")
res_key = {key for key in set_key if key not in key_test}
if len(res_key) != 0:
raise KeyError("Some Key "
f"do not correspond the keys: {res_key}")
raise KeyError(
"Some Key " f"do not correspond the keys: {res_key}"
)
@staticmethod
def convert_values(data: dict):
@@ -47,8 +53,48 @@ class DataMaze:
for key in key_bool:
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"],
res["PERFECT"])
)
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,
"DFS": DepthFirstSearchSolver
}
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
def get_data_maze(name_file: str) -> dict:
try:
@@ -56,7 +102,7 @@ class DataMaze:
data_dict = DataMaze.transform_data(data_str)
DataMaze.verif_key_data(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:
print("The file do not exist")
exit()
@@ -70,28 +116,11 @@ class DataMaze:
print(f"Error on the key in the file: {e}")
exit()
except IndexError as e:
print("In the function transform Data some data cannot "
f"be splited by '=' because '=' was not present: {e}")
print(
"In the function transform Data some data cannot "
f"be splited by '=' because '=' was not present: {e}"
)
exit()
except AttributeError as e:
print("Error on the "
f"funciton get_data_maze : {e}")
print("Error on the " f"funciton get_data_maze : {e}")
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
-23
View File
@@ -1,23 +0,0 @@
7D53BFD3D57951517D1D
3D12C3903BD03AD4178D
2BAEBEEEAA92EED547C9
2287ED17AAAC5393FFF0
6C6951292A87D2AEBD30
37D43E8686E93AABAB8C
21516D2D47FEE8284049
6C7857C3FB9116C696D8
751453D6D2AAC57BE970
3BA952D17EA83BD05470
22AAD2907BAE86967B74
2AA83C2EFC69696FBC35
686EE96FD7D4783FAD21
7ED17ED3D57D3EC52FA0
7B943D16FB7BABD3AFC8
7407C5297EB82EB84174
392D53C6912EE9447E9D
62A952BBAAC13EFD7B89
3AAC3EC6EABAAD557824
66C7C7D7D6C6C7D556CD
1,1
16,15
-1
View File
@@ -1,4 +1,3 @@
import pytest
from amaz_lib.Cell import Cell
+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
+1 -1
View File
@@ -1,6 +1,6 @@
from amaz_lib.Cell import Cell
import numpy as np
from amaz_lib import AStar, Maze, MazeSolver
from amaz_lib import AStar, Maze
def test_solver() -> None: