22 Commits

Author SHA1 Message Date
da7e 843fe5f80c uv config for build wheel package
add clean and fclean method to Makefile
2026-04-01 14:31:11 +02:00
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
da7e 92c6237f06 fix(astar): the actual astar wasn't the real astar algoritm 2026-03-29 15:38:40 +02: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
12 changed files with 385 additions and 281 deletions
+1
View File
@@ -216,3 +216,4 @@ __marimo__/
.streamlit/secrets.toml .streamlit/secrets.toml
test.txt test.txt
mazegen-1.0.0-py3-none-any.whl
+10 -1
View File
@@ -1,3 +1,7 @@
build:
uv build --clear --wheel
cp dist/*.whl mazegen-1.0.0-py3-none-any.whl
install: install:
uv sync uv sync
uv pip install mlx-2.2-py3-none-any.whl uv pip install mlx-2.2-py3-none-any.whl
@@ -12,7 +16,10 @@ debug:
uv pdb python3 a_maze_ing.py config.txt uv pdb python3 a_maze_ing.py config.txt
clean: clean:
rm -rf __pycache__ .mypy_cache .venv rm -rf */**__pycache__ __pycache__ .mypy_cache .venv dist build */**/*.egg-info *.egg-info
fclean: clean
rm mazegen-1.0.0-py3-none-any.whl
lint: lint:
uv run flake8 . --exclude=.venv uv run flake8 . --exclude=.venv
@@ -34,3 +41,5 @@ run_test:
uv run pytest uv run pytest
mlx: mlx:
uv run python3 test.py uv run python3 test.py
.PHONY: build install run debug clean fclean lint lint-strict run_test
View File
+213 -126
View File
@@ -1,9 +1,8 @@
from typing import Any from typing import Any
from src.AMazeIng import AMazeIng from src.AMazeIng import AMazeIng
from src.parsing import Parsing from src.parsing.Parsing import DataMaze as Parsing
from mlx import Mlx from mlx import Mlx
import numpy as np import numpy as np
import math
import time import time
@@ -12,6 +11,8 @@ class MazeMLX:
self.mlx = Mlx() self.mlx = Mlx()
self.height = height self.height = height
self.width = width self.width = width
self.print_path = False
self.color = [0x00, 0x00, 0xFF, 0xFF]
self.mlx_ptr = self.mlx.mlx_init() self.mlx_ptr = self.mlx.mlx_init()
self.win_ptr = self.mlx.mlx_new_window( self.win_ptr = self.mlx.mlx_new_window(
self.mlx_ptr, width, height + 200, "A-Maze-Ing" self.mlx_ptr, width, height + 200, "A-Maze-Ing"
@@ -26,6 +27,12 @@ class MazeMLX:
def close(self) -> None: def close(self) -> None:
self.mlx.mlx_destroy_image(self.mlx_ptr, self.img_ptr) 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: def redraw_image(self) -> None:
self.mlx.mlx_clear_window(self.mlx_ptr, self.win_ptr) self.mlx.mlx_clear_window(self.mlx_ptr, self.win_ptr)
self.mlx.mlx_put_image_to_window( self.mlx.mlx_put_image_to_window(
@@ -40,58 +47,100 @@ class MazeMLX:
"1: regen; 2: path; 3: color; 4: quit;", "1: regen; 2: path; 3: color; 4: quit;",
) )
def put_pixel(self, x, y) -> None: 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) offset = y * self.size_line + x * (self.bpp // 8)
self.buf[offset + 0] = 0x00 if color:
self.buf[offset + 1] = 0x00 self.buf[offset + 0] = color[0]
self.buf[offset + 2] = 0xFF self.buf[offset + 1] = color[1]
if self.bpp >= 32: self.buf[offset + 2] = color[2]
self.buf[offset + 3] = 0xFF 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 clear_image(self) -> None: def put_line(
self.buf[:] = b"\x00" * len(self.buf) self,
start: tuple[int, int],
def random_color() -> Any: end: tuple[int, int],
colors = [ color: list | None = None,
[0x00, 0x00, 0xFF, 0xFF], # red ) -> None:
[0x00, 0xFF, 0xFF, 0xFF], # yellow
[0x00, 0xFF, 0x40, 0xFF], # green
[0xFF, 0xBF, 0x00, 0xFF], # blue
[0xFF, 0x00, 0x80, 0xFF], # purple
[0xFF, 0x00, 0xFF, 0xFF], # rose
]
return np.random.choice(colors)
def put_line(self, start: tuple[int, int], end: tuple[int, int]) -> None:
sx, sy = start sx, sy = start
ex, ey = end ex, ey = end
if sy == ey: if sy == ey:
for x in range(min(sx, ex), max(sx, ex) + 1): for x in range(min(sx, ex), max(sx, ex) + 1):
self.put_pixel(x, sy) self.put_pixel(x, sy, color)
if sx == ex: if sx == ex:
for y in range(min(sy, ey), max(sy, ey) + 1): for y in range(min(sy, ey), max(sy, ey) + 1):
self.put_pixel(sx, y) 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: def update_maze(self, maze: np.ndarray) -> None:
self.clear_image() self.clear_image()
margin = math.trunc(
math.sqrt(self.width if self.width > self.height else self.height) line_len, margin_x, margin_y = self.get_margin_line_len(maze)
// 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 y in range(len(maze)):
for x in range(len(maze[0])): for x in range(len(maze[0])):
x0 = x * line_len + margin x0 = x * line_len + margin_x
y0 = y * line_len + margin y0 = y * line_len + margin_y
x1 = x * line_len + line_len + margin x1 = x * line_len + line_len + margin_x
y1 = y * line_len + line_len + margin y1 = y * line_len + line_len + margin_y
if maze[y][x].get_north(): if maze[y][x].get_north():
self.put_line((x0, y0), (x1, y0)) self.put_line((x0, y0), (x1, y0))
@@ -101,13 +150,8 @@ class MazeMLX:
self.put_line((x0, y1), (x1, y1)) self.put_line((x0, y1), (x1, y1))
if maze[y][x].get_west(): if maze[y][x].get_west():
self.put_line((x0, y0), (x0, y1)) self.put_line((x0, y0), (x0, y1))
self.redraw_image()
def put_block(self, ul: tuple[int, int], dr: tuple[int, int]) -> None: def put_path(self, amazing: AMazeIng) -> Any:
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))
def put_path(self, amazing: AMazeIng):
path = amazing.solve_path() path = amazing.solve_path()
print(path) print(path)
actual = amazing.entry actual = amazing.entry
@@ -115,34 +159,23 @@ class MazeMLX:
maze = amazing.maze.get_maze() maze = amazing.maze.get_maze()
if maze is None: if maze is None:
return return
margin = math.trunc(
math.sqrt(self.width if self.width > self.height else self.height) line_len, margin_x, margin_y = self.get_margin_line_len(maze)
// 2
)
cell_size = math.trunc(
(
(self.height - margin) // len(maze)
if self.height > self.width
else (self.width - margin) // len(maze[0])
)
)
self.update_maze(maze)
self.draw_ft(maze)
for i in range(len(path)): for i in range(len(path)):
ul = ( ul = (
(actual[0]) * cell_size + margin + 12, (actual[0]) * line_len + margin_x + 12,
(actual[1]) * cell_size + 12 + margin, (actual[1]) * line_len + 12 + margin_y,
) )
dr = ( dr = (
(actual[0]) * cell_size + cell_size + margin - 12, (actual[0]) * line_len + line_len + margin_x - 12,
(actual[1]) * cell_size + cell_size - 12 + margin, (actual[1]) * line_len + line_len - 12 + margin_y,
) )
self.put_block(ul, dr) self.put_block(ul, dr)
self.redraw_image() x0 = actual[0] * line_len + margin_x + 12
x0 = actual[0] * cell_size + margin + 12 y0 = actual[1] * line_len + margin_y + 12
y0 = actual[1] * cell_size + margin + 12 x1 = actual[0] * line_len + line_len + margin_x - 12
x1 = actual[0] * cell_size + cell_size + margin - 12 y1 = actual[1] * line_len + line_len + margin_y - 12
y1 = actual[1] * cell_size + cell_size + margin - 12
yield yield
match path[i]: match path[i]:
case "N": case "N":
@@ -158,102 +191,156 @@ class MazeMLX:
self.put_block((x0, y0), (x0 - 24, y1)) self.put_block((x0, y0), (x0 - 24, y1))
actual = (actual[0] - 1, actual[1]) actual = (actual[0] - 1, actual[1])
ul = ( ul = (
(actual[0]) * cell_size + margin + 12, (actual[0]) * line_len + margin_x + 12,
(actual[1]) * cell_size + 12 + margin, (actual[1]) * line_len + 12 + margin_y,
) )
dr = ( dr = (
(actual[0]) * cell_size + cell_size + margin - 12, (actual[0]) * line_len + line_len + margin_x - 12,
(actual[1]) * cell_size + cell_size - 12 + margin, (actual[1]) * line_len + line_len - 12 + margin_y,
) )
self.put_block(ul, dr) self.put_block(ul, dr)
self.redraw_image()
return return
def draw_ft(self, maze: np.ndarray): def put_start_end(self, amazing: AMazeIng):
self.clear_image() entry = amazing.entry
margin = math.trunc( exit = amazing.exit
math.sqrt(self.width if self.width > self.height else self.height) maze = amazing.maze.get_maze()
// 2 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,
) )
line_len = math.trunc( dr = (
( (entry[0] - 1) * line_len + line_len + margin_x - 3,
(self.height - margin) // len(maze) (entry[1] - 1) * line_len + line_len - 3 + margin_y,
if self.height > self.width
else (self.width - margin) // len(maze[0])
)
) )
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 y in range(len(maze)):
for x in range(len(maze[0])): 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))
if maze[y][x].value == 15: if maze[y][x].value == 15:
self.put_block((x0, y0), (x1, y1)) 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() self.redraw_image()
def close_loop(self, _: Any): def shift_color(self):
self.mlx.mlx_loop_exit(self.mlx_ptr) self.color_gen = self.random_color()
def handle_key_press(self, keycode: int, amazing: AMazeIng) -> None: def shift_color_ft(self):
if keycode == 49: self.color_gen_ft = self.random_color_ft()
self.restart_maze(amazing)
if keycode == 50:
self.restart_path(amazing)
if keycode == 51:
pass
if keycode == 52:
self.close_loop(None)
def start(self, amazing: AMazeIng) -> None: def time_gen(self):
self.restart_maze(amazing) self.timer_gen = self.time_generator()
self.mlx.mlx_loop_hook(self.mlx_ptr, self.render_maze, 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, amazing
)
self.mlx.mlx_loop(self.mlx_ptr)
def restart_maze(self, amazing: AMazeIng) -> None: def restart_maze(self, amazing: AMazeIng) -> None:
self.generator = amazing.generate() self.generator = amazing.generate()
def time_generator(self) -> Any:
yield
while True:
time.sleep(0.3)
yield
def restart_path(self, amazing: AMazeIng) -> None: def restart_path(self, amazing: AMazeIng) -> None:
self.path_printer = self.put_path(amazing) self.path_printer = self.put_path(amazing)
def render_path(self): def render_path(self) -> bool:
try: try:
next(self.path_printer) next(self.path_printer)
time.sleep(0.03) time.sleep(0.03)
return False
except StopIteration: except StopIteration:
pass pass
return True
def render_maze(self, amazing: AMazeIng): def render_maze(self, amazing: AMazeIng) -> bool:
try: try:
next(self.generator) next(self.generator)
self.update_maze(amazing.maze.get_maze()) self.update_maze(amazing.maze.get_maze())
# time.sleep(0.01) return False
except StopIteration: except StopIteration:
# self.color_ft(amazing) pass
if self.path_printer is not None: return True
self.render_path()
else: def handle_key_press(self, keycode: int, amazing: AMazeIng) -> None:
self.color_ft(amazing.maze.get_maze()) 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)
def main() -> None: def main() -> None:
mlx = None mlx = None
try: try:
mlx = MazeMLX(1000, 1000) mlx = MazeMLX(1000, 1000)
config = Parsing.DataMaze.get_data_maze("config.txt") config = Parsing.get_data_maze("config.txt")
amazing = AMazeIng(**config) amazing = AMazeIng(**config)
mlx.start(amazing) mlx.start(amazing)
with open("test.txt", "w") as output: with open("test.txt", "w") as output:
+5 -5
View File
@@ -1,8 +1,8 @@
WIDTH=15 WIDTH=13
HEIGHT=15 HEIGHT=13
ENTRY=1,1 ENTRY=1,1
EXIT=11,11 EXIT=5,5
OUTPUT_FILE=maze.txt OUTPUT_FILE=maze.txt
PERFECT=False PERFECT=False
GENERATOR=DFS GENERATOR=Kruskal
SOLVER=DFS SOLVER=AStar
+8
View File
@@ -23,3 +23,11 @@ python_version = "3.10"
[tool.pytest.ini_options] [tool.pytest.ini_options]
pythonpath = ["src"] pythonpath = ["src"]
[build-system]
requires = ["setuptools>=78.1.0", "wheel>=0.45.1"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
package-dir = {"" = "src/amaz_lib"}
+8 -5
View File
@@ -1,16 +1,16 @@
from dataclasses import dataclass from dataclasses import dataclass
from numpy.typing import NDArray
import numpy from typing import Optional, Any
@dataclass @dataclass
class Maze: 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 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 self.maze = new_maze
def __str__(self) -> str: def __str__(self) -> str:
@@ -24,6 +24,9 @@ class Maze:
return res return res
def ascii_print(self) -> None: def ascii_print(self) -> None:
if self.maze is None:
print("None")
return
for cell in self.maze[0]: for cell in self.maze[0]:
print("_", end="") print("_", end="")
if cell.get_north(): if cell.get_north():
+138 -135
View File
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .Maze import Maze from .Maze import Maze
from typing import Any
import numpy as np import numpy as np
@@ -9,170 +10,165 @@ 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, height: int = None, def solve(
width: int = None) -> str: ... self, maze: Maze, height: int | None = None, width: int | None = None
) -> str: ...
class AStar(MazeSolver): 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: def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None:
super().__init__(start, end) super().__init__(start, end)
self.path = []
def f(self, n): def h(self, n: tuple[int, int]) -> int:
def g(n: tuple[int, int]) -> int: return (
res = 0 max(n[0], self.end[0])
if n[0] < self.start[0]: - min(n[0], self.end[0])
res += self.start[0] - n[0] + max(n[1], self.end[1])
else: - min(n[1], self.end[1])
res += n[0] - self.start[0] )
if n[1] < self.start[1]:
res += self.start[1] - n[1]
else:
res += n[1] - self.start[1]
return res
def h(n: tuple[int, int]) -> int: def get_paths(
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, self,
maze: np.ndarray, maze: np.ndarray,
actual: tuple[int, int], actual: tuple[int, int],
last: str | None, close: list,
) -> dict[str, int]: ) -> list[tuple[int, int]]:
path = { path = [
"N": ( (
self.f((actual[0], actual[1] - 1)) (actual[0], actual[1] - 1)
if not maze[actual[1]][actual[0]].get_north() and actual[1] > 0 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 else None
), ),
"E": ( (
self.f((actual[0] + 1, actual[1])) (actual[0] + 1, actual[1])
if not maze[actual[1]][actual[0]].get_est() if not maze[actual[1]][actual[0]].get_est()
and actual[0] < len(maze[0]) - 1 and actual[0] < len(maze[0]) - 1
and (actual[0] + 1, actual[1])
not in [n.coordinate for n in close]
else None else None
), ),
"S": ( (
self.f((actual[0], actual[1] + 1)) (actual[0], actual[1] + 1)
if not maze[actual[1]][actual[0]].get_south() if not maze[actual[1]][actual[0]].get_south()
and actual[1] < 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 else None
), ),
"W": ( (
self.f((actual[0] - 1, actual[1])) (actual[0] - 1, actual[1])
if not maze[actual[1]][actual[0]].get_west() and actual[0] > 0 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 else None
), ),
} ]
return { return [p for p in path if p is not None]
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_path(self, maze: np.ndarray) -> list:
match dir: open: list[AStar.Node] = []
case "N": close: list[AStar.Node] = []
return "S"
case "E":
return "W"
case "S":
return "N"
case "W":
return "E"
case _:
return ""
def get_next_pos( open.append(
self, dir: str, actual: tuple[int, int] AStar.Node(
) -> tuple[int, int]: self.start,
match dir: 0,
case "N": self.h(self.start),
return (actual[0], actual[1] - 1) self.h(self.start),
case "E": None,
return (actual[0] + 1, actual[1])
case "S":
return (actual[0], actual[1] + 1)
case "W":
return (actual[0] - 1, actual[1])
case _:
return actual
def get_path(self, maze: np.ndarray) -> str | None:
path = [(self.start, self.best_path(maze, self.start, None))]
visited = [self.start]
while len(path) > 0 and path[-1][0] != self.end:
if len(path[-1][1]) == 0:
path.pop(-1)
if len(path) == 0:
break
k = next(iter(path[-1][1]))
path[-1][1].pop(k)
continue
while len(path[-1][1]) > 0:
next_pos = self.get_next_pos(
list(path[-1][1].keys())[0], path[-1][0]
)
if next_pos in visited:
k = next(iter(path[-1][1]))
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, height: int = None, while len(open) > 0:
width: int = None) -> str: to_check = sorted(open, key=lambda x: x.f)[0]
res = self.get_path(maze.get_maze()) open.remove(to_check)
if res is None: close.append(to_check)
raise Exception("Path not found") 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")
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 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 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): class DepthFirstSearchSolver(MazeSolver):
def __init__(self, start, end): def __init__(self, start, end):
super().__init__(start, end) super().__init__(start, end)
def solve(self, maze: Maze, height: int = None, def solve(
width: int = None) -> str: self, maze: Maze, height: int | None = None, width: int | None = None
res = list() ) -> str:
for _ in range(50):
res.append(self.get_path(maze, height, width))
return min(res, key=lambda x: len(x))
def get_path(self, maze: Maze, height: int = None,
width: int = None) -> str:
path_str = "" path_str = ""
visited = np.zeros((height, width), dtype=bool) visited = np.zeros((height, width), dtype=bool)
path = list() path = list()
@@ -186,8 +182,9 @@ class DepthFirstSearchSolver(MazeSolver):
rand_p = self.random_path(visited, coord, maze_s, h_w) rand_p = self.random_path(visited, coord, maze_s, h_w)
if not rand_p: if not rand_p:
path, move = self.back_on_step(path, visited, maze_s, h_w, path, move = self.back_on_step(
move) path, visited, maze_s, h_w, move
)
if not path: if not path:
break break
coord = path[-1] coord = path[-1]
@@ -202,8 +199,9 @@ class DepthFirstSearchSolver(MazeSolver):
return path_str return path_str
@staticmethod @staticmethod
def random_path(visited: np.ndarray, coord: tuple, def random_path(
maze: np.ndarray, h_w: tuple) -> list: visited: np.ndarray, coord: tuple, maze: np.ndarray, h_w: tuple
) -> list:
random_p = [] random_p = []
h, w = h_w h, w = h_w
y, x = coord y, x = coord
@@ -226,8 +224,13 @@ class DepthFirstSearchSolver(MazeSolver):
return np.random.choice(rand_path) return np.random.choice(rand_path)
@staticmethod @staticmethod
def back_on_step(path: list, visited: np.ndarray, def back_on_step(
maze: np.ndarray, h_w: tuple, move: list) -> list: path: list,
visited: np.ndarray,
maze: np.ndarray,
h_w: tuple,
move: list,
) -> list:
while path: while path:
last = path[-1] last = path[-1]
if DepthFirstSearchSolver.random_path(visited, last, maze, h_w): if DepthFirstSearchSolver.random_path(visited, last, maze, h_w):
-6
View File
@@ -1,6 +0,0 @@
__version__ = "1.0.0"
__author__ = "mteriier, dgaillet"
from .Parsing import DataMaze
__all__ = ["DataMaze"]
-1
View File
@@ -1,4 +1,3 @@
import pytest
from amaz_lib.Cell import Cell from amaz_lib.Cell import Cell
+1 -1
View File
@@ -1,6 +1,6 @@
from amaz_lib.Cell import Cell from amaz_lib.Cell import Cell
import numpy as np import numpy as np
from amaz_lib import AStar, Maze, MazeSolver from amaz_lib import AStar, Maze
def test_solver() -> None: def test_solver() -> None:
Generated
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]] [[package]]
name = "a-maze-ing" name = "a-maze-ing"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },