8 Commits

5 changed files with 258 additions and 162 deletions
+116 -13
View File
@@ -1,9 +1,10 @@
from typing import Any
from typing import Any, Generator
from src.AMazeIng import AMazeIng
from src.parsing import Parsing
from mlx.mlx import Mlx
from mlx import Mlx
import numpy as np
import math
import time
class MazeMLX:
@@ -12,18 +13,33 @@ class MazeMLX:
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.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 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) -> None:
offset = y * self.size_line + x * (self.bpp // 8)
@@ -35,7 +51,6 @@ class MazeMLX:
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
@@ -75,28 +90,116 @@ class MazeMLX:
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)
self.redraw_image()
def put_block(self, ul: tuple[int, int], dr: tuple[int, int]) -> 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))
def put_path(self, amazing: AMazeIng):
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
margin = math.trunc(
math.sqrt(self.width if self.width > self.height else self.height)
// 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)
for i in range(len(path)):
ul = (
(actual[0]) * cell_size + margin + 12,
(actual[1]) * cell_size + 12 + margin,
)
dr = (
(actual[0]) * cell_size + cell_size + margin - 12,
(actual[1]) * cell_size + cell_size - 12 + margin,
)
self.put_block(ul, dr)
self.redraw_image()
x0 = actual[0] * cell_size + margin + 12
y0 = actual[1] * cell_size + margin + 12
x1 = actual[0] * cell_size + cell_size + margin - 12
y1 = actual[1] * cell_size + cell_size + margin - 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]) * cell_size + margin + 12,
(actual[1]) * cell_size + 12 + margin,
)
dr = (
(actual[0]) * cell_size + cell_size + margin - 12,
(actual[1]) * cell_size + cell_size - 12 + margin,
)
self.put_block(ul, dr)
self.redraw_image()
return
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 handle_key_press(self, keycode: int, amazing: AMazeIng) -> None:
if keycode == 49:
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:
self.gen_maze(amazing)
self.mlx.mlx_loop_hook(self.mlx_ptr, self.render, amazing)
self.restart_maze(amazing)
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 render(self, amazing: AMazeIng):
def restart_maze(self, amazing: AMazeIng) -> None:
self.generator = amazing.generate()
def restart_path(self, amazing: AMazeIng) -> None:
self.path_printer = self.put_path(amazing)
def render_path(self):
try:
next(self.path_printer)
time.sleep(0.03)
except StopIteration:
pass
def render_maze(self, amazing: AMazeIng):
try:
next(self.generator)
self.update_maze(amazing.maze.get_maze())
# time.sleep(0.01)
except StopIteration:
pass
if self.path_printer is not None:
self.render_path()
def main() -> None:
+2 -2
View File
@@ -1,8 +1,8 @@
WIDTH=15
HEIGHT=15
ENTRY=1,1
EXIT=2,2
EXIT=15,15
OUTPUT_FILE=maze.txt
PERFECT=False
GENERATOR=DFS
GENERATOR=Kruskal
SOLVER=AStar
+2 -2
View File
@@ -20,9 +20,9 @@ class AMazeIng(BaseModel):
@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
+138 -126
View File
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from .Maze import Maze
from typing import Any
import numpy as np
@@ -9,163 +10,167 @@ class MazeSolver(ABC):
self.end = (end[1] - 1, end[0] - 1)
@abstractmethod
def solve(self, maze: Maze, height: int = None,
width: int = None) -> str: ...
def solve(
self, maze: Maze, height: int = None, width: int = 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 g(self, n: tuple[int, int]) -> int:
return len(self.path) + 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
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])
)
try:
return g(n) + h(n)
except Exception:
return 1000
def f(self, n: tuple[int, int]) -> int:
return self.g(n) + self.h(n)
def best_path(
def get_paths(
self,
maze: np.ndarray,
actual: tuple[int, int],
last: str | None,
) -> dict[str, int]:
path = {
"N": (
self.f((actual[0], actual[1] - 1))
if not maze[actual[1]][actual[0]].get_north() and actual[1] > 0
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[0] + 1, actual[1]))
(
(actual[0] + 1, actual[1])
if not maze[actual[1]][actual[0]].get_est()
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[0], actual[1] + 1))
(
(actual[0], actual[1] + 1)
if not maze[actual[1]][actual[0]].get_south()
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[0] - 1, actual[1]))
if not maze[actual[1]][actual[0]].get_west() and actual[0] > 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])
if v is not None and k != last
}
]
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
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),
)
open.append(
AStar.Node(
self.start,
0,
self.h(self.start),
self.f(self.start),
None,
)
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,
width: int = None) -> str:
res = self.get_path(maze.get_maze())
if res is None:
raise Exception("Path not found")
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")
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
def solve(self, maze: Maze, height: int = None, width: int = 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,
width: int = None) -> str:
def solve(self, maze: Maze, height: int = None, width: int = None) -> str:
path_str = ""
visited = np.zeros((height, width), dtype=bool)
path = list()
@@ -179,8 +184,9 @@ class DepthFirstSearchSolver(MazeSolver):
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)
path, move = self.back_on_step(
path, visited, maze_s, h_w, move
)
if not path:
break
coord = path[-1]
@@ -195,8 +201,9 @@ class DepthFirstSearchSolver(MazeSolver):
return path_str
@staticmethod
def random_path(visited: np.ndarray, coord: tuple,
maze: np.ndarray, h_w: tuple) -> list:
def random_path(
visited: np.ndarray, coord: tuple, maze: np.ndarray, h_w: tuple
) -> list:
random_p = []
h, w = h_w
y, x = coord
@@ -219,8 +226,13 @@ class DepthFirstSearchSolver(MazeSolver):
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:
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):
-19
View File
@@ -1,19 +0,0 @@
D553955513B913B
9796C553AA86AC2
83C151146AC7C12
C03856817C393EA
96EC53A8556AC3A
8553FC6AFFFABC2
8792FD5057FAC16
E946FFFAFFF83AB
92913BFAFD52AC2
AEAAC6FAFFFEC3A
A904553A9555382
828557AAAD5546E
844553C6C151553
C5395439545453A
D546D546D555546
1,1
2,2
EEESWSEEEEEENNESSSESWWWSSSSSSSSWNNWWWNNWSSSSEEESWWSWNWWNENWNNNENEENNWWWNENNWNE