1 Commits

Author SHA1 Message Date
da7e 16d97e9912 fix(astar): function f() miscalculate the best path 2026-03-27 21:51:49 +01:00
5 changed files with 87 additions and 184 deletions
+15 -122
View File
@@ -1,10 +1,9 @@
from typing import Any, Generator
from typing import Any
from src.AMazeIng import AMazeIng
from src.parsing import Parsing
from mlx import Mlx
from mlx.mlx import Mlx
import numpy as np
import math
import time
class MazeMLX:
@@ -13,33 +12,18 @@ 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 + 200, "A-Maze-Ing"
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)
)
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)
@@ -51,6 +35,7 @@ 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
@@ -90,120 +75,28 @@ class MazeMLX:
self.put_line((x0, y1), (x1, y1))
if maze[y][x].get_west():
self.put_line((x0, y0), (x0, y1))
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
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 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.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 restart_maze(self, amazing: AMazeIng) -> None:
def gen_maze(self, amazing: AMazeIng) -> None:
self.generator = amazing.generate()
def restart_path(self, amazing: AMazeIng) -> None:
self.path_printer = self.put_path(amazing)
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_path(self):
try:
next(self.path_printer)
time.sleep(0.03)
except StopIteration:
pass
def render_maze(self, amazing: AMazeIng):
def render(self, amazing: AMazeIng):
try:
next(self.generator)
self.update_maze(amazing.maze.get_maze())
# time.sleep(0.01)
except StopIteration:
if self.path_printer is not None:
try:
next(self.path_printer)
time.sleep(0.03)
except StopIteration:
pass
pass
def main() -> None:
+5 -5
View File
@@ -1,8 +1,8 @@
WIDTH=11
HEIGHT=11
WIDTH=15
HEIGHT=15
ENTRY=1,1
EXIT=11,11
EXIT=2,2
OUTPUT_FILE=maze.txt
PERFECT=True
GENERATOR=Kruskal
PERFECT=False
GENERATOR=DFS
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
+46 -55
View File
@@ -9,44 +9,30 @@ 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):
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() -> 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
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
return g() + h(n)
def best_path(
self,
@@ -113,47 +99,46 @@ class AStar(MazeSolver):
return actual
def get_path(self, maze: np.ndarray) -> str | None:
path = [(self.start, self.best_path(maze, self.start, None))]
self.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:
while len(self.path) > 0 and self.path[-1][0] != self.end:
if len(self.path[-1][1]) == 0:
self.path.pop(-1)
if len(self.path) == 0:
break
k = next(iter(path[-1][1]))
path[-1][1].pop(k)
k = next(iter(self.path[-1][1]))
self.path[-1][1].pop(k)
continue
while len(path[-1][1]) > 0:
while len(self.path[-1][1]) > 0:
next_pos = self.get_next_pos(
list(path[-1][1].keys())[0], path[-1][0]
list(self.path[-1][1].keys())[0], self.path[-1][0]
)
if next_pos in visited:
k = next(iter(path[-1][1]))
path[-1][1].pop(k)
k = next(iter(self.path[-1][1]))
self.path[-1][1].pop(k)
else:
break
if len(path[-1][1]) == 0:
path.pop(-1)
if len(self.path[-1][1]) == 0:
self.path.pop(-1)
continue
pre = self.get_opposit(list(path[-1][1].keys())[0])
path.append(
pre = self.get_opposit(list(self.path[-1][1].keys())[0])
self.path.append(
(
next_pos,
self.best_path(maze, next_pos, pre),
)
)
visited += [next_pos]
if len(path) == 0:
if len(self.path) == 0:
return None
path[-1] = (self.end, {})
self.path[-1] = (self.end, {})
return "".join(
str(list(c[1].keys())[0]) for c in path if len(c[1]) > 0
str(list(c[1].keys())[0]) for c in self.path if len(c[1]) > 0
)
def solve(self, maze: Maze, height: int = None,
width: int = None) -> str:
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")
@@ -164,8 +149,7 @@ 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 +163,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 +180,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 +205,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
@@ -0,0 +1,19 @@
D53939553D13B93
93AAC697A92C6AA
AC6A95056AE956A
A916856D1454152
A843C5394555696
A87AF96AFFFB96B
AE96FC5457F8412
C52BFFFBFFFC102
B94453FAFD516EA
86953AFAFFFA956
81692C54153A853
AC3A83950546C3A
ABA86C2D69517C2
C2AC55293ABC53A
D6C55546C4457C6
1,1
2,2
EESSWN