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.AMazeIng import AMazeIng
from src.parsing import Parsing from src.parsing import Parsing
from mlx import Mlx from mlx.mlx import Mlx
import numpy as np import numpy as np
import math import math
import time
class MazeMLX: class MazeMLX:
@@ -13,33 +12,18 @@ class MazeMLX:
self.height = height self.height = height
self.width = width self.width = width
self.mlx_ptr = self.mlx.mlx_init() self.mlx_ptr = self.mlx.mlx_init()
self.generator = None
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, "amazing"
) )
self.img_ptr = self.mlx.mlx_new_image(self.mlx_ptr, width, height) self.img_ptr = self.mlx.mlx_new_image(self.mlx_ptr, width, height)
self.buf, self.bpp, self.size_line, self.format = ( self.buf, self.bpp, self.size_line, self.format = (
self.mlx.mlx_get_data_addr(self.img_ptr) self.mlx.mlx_get_data_addr(self.img_ptr)
) )
self.path_printer = None
self.generator = None
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 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: def put_pixel(self, x, y) -> None:
offset = y * self.size_line + x * (self.bpp // 8) offset = y * self.size_line + x * (self.bpp // 8)
@@ -51,6 +35,7 @@ class MazeMLX:
def clear_image(self) -> None: def clear_image(self) -> None:
self.buf[:] = b"\x00" * len(self.buf) 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: def put_line(self, start: tuple[int, int], end: tuple[int, int]) -> None:
sx, sy = start sx, sy = start
@@ -90,120 +75,28 @@ 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() self.mlx.mlx_put_image_to_window(
self.mlx_ptr, self.win_ptr, self.img_ptr, 0, 0)
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): def close_loop(self, _: Any):
self.mlx.mlx_loop_exit(self.mlx_ptr) self.mlx.mlx_loop_exit(self.mlx_ptr)
def handle_key_press(self, keycode: int, amazing: AMazeIng) -> None: def gen_maze(self, 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:
self.generator = amazing.generate() self.generator = amazing.generate()
def restart_path(self, amazing: AMazeIng) -> None: def start(self, amazing: AMazeIng) -> None:
self.path_printer = self.put_path(amazing) 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): def render(self, amazing: AMazeIng):
try:
next(self.path_printer)
time.sleep(0.03)
except StopIteration:
pass
def render_maze(self, amazing: AMazeIng):
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) # time.sleep(0.01)
except StopIteration: except StopIteration:
if self.path_printer is not None: pass
try:
next(self.path_printer)
time.sleep(0.03)
except StopIteration:
pass
def main() -> None: def main() -> None:
+5 -5
View File
@@ -1,8 +1,8 @@
WIDTH=11 WIDTH=15
HEIGHT=11 HEIGHT=15
ENTRY=1,1 ENTRY=1,1
EXIT=11,11 EXIT=2,2
OUTPUT_FILE=maze.txt OUTPUT_FILE=maze.txt
PERFECT=True PERFECT=False
GENERATOR=Kruskal GENERATOR=DFS
SOLVER=AStar SOLVER=AStar
+2 -2
View File
@@ -20,9 +20,9 @@ class AMazeIng(BaseModel):
@model_validator(mode="after") @model_validator(mode="after")
def check_entry_exit(self) -> Self: 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") 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") raise ValueError("Exit coordinates exceed the maze size")
return self return self
+46 -55
View File
@@ -9,44 +9,30 @@ 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, width: int = None
) -> str: ...
class AStar(MazeSolver): class AStar(MazeSolver):
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 f(self, n):
def g(n: tuple[int, int]) -> int: def g() -> int:
res = 0 return len(self.path) + 1
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(n: tuple[int, int]) -> int: def h(n: tuple[int, int]) -> int:
res = 0 return (
if n[0] < self.end[0]: max(n[0], self.end[0])
res += self.end[0] - n[0] - min(n[0], self.end[0])
else: + max(n[1], self.end[1])
res += n[0] - self.end[0] - min(n[1], self.end[1])
if n[1] < self.end[1]: )
res += self.end[1] - n[1]
else:
res += n[1] - self.end[1]
return res
try: return g() + h(n)
return g(n) + h(n)
except Exception:
return 1000
def best_path( def best_path(
self, self,
@@ -113,47 +99,46 @@ class AStar(MazeSolver):
return actual return actual
def get_path(self, maze: np.ndarray) -> str | None: 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] visited = [self.start]
while len(path) > 0 and path[-1][0] != self.end: while len(self.path) > 0 and self.path[-1][0] != self.end:
if len(path[-1][1]) == 0: if len(self.path[-1][1]) == 0:
path.pop(-1) self.path.pop(-1)
if len(path) == 0: if len(self.path) == 0:
break break
k = next(iter(path[-1][1])) k = next(iter(self.path[-1][1]))
path[-1][1].pop(k) self.path[-1][1].pop(k)
continue continue
while len(path[-1][1]) > 0: while len(self.path[-1][1]) > 0:
next_pos = self.get_next_pos( 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: if next_pos in visited:
k = next(iter(path[-1][1])) k = next(iter(self.path[-1][1]))
path[-1][1].pop(k) self.path[-1][1].pop(k)
else: else:
break break
if len(path[-1][1]) == 0: if len(self.path[-1][1]) == 0:
path.pop(-1) self.path.pop(-1)
continue continue
pre = self.get_opposit(list(path[-1][1].keys())[0]) pre = self.get_opposit(list(self.path[-1][1].keys())[0])
path.append( self.path.append(
( (
next_pos, next_pos,
self.best_path(maze, next_pos, pre), self.best_path(maze, next_pos, pre),
) )
) )
visited += [next_pos] visited += [next_pos]
if len(path) == 0: if len(self.path) == 0:
return None return None
path[-1] = (self.end, {}) self.path[-1] = (self.end, {})
return "".join( 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, def solve(self, maze: Maze, height: int = None, width: int = None) -> str:
width: int = None) -> str:
res = self.get_path(maze.get_maze()) res = self.get_path(maze.get_maze())
if res is None: if res is None:
raise Exception("Path not found") raise Exception("Path not found")
@@ -164,8 +149,7 @@ 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(self, maze: Maze, height: int = None, width: int = None) -> str:
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()
@@ -179,8 +163,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]
@@ -195,8 +180,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
@@ -219,8 +205,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):
+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