From 16d97e9912a4a51a46974a93e7fed7b9d4b31958 Mon Sep 17 00:00:00 2001 From: David GAILLETON Date: Fri, 27 Mar 2026 21:51:49 +0100 Subject: [PATCH 1/3] fix(astar): function f() miscalculate the best path --- src/amaz_lib/MazeSolver.py | 101 +++++++++++++++++-------------------- test.txt | 32 ++++++------ 2 files changed, 62 insertions(+), 71 deletions(-) diff --git a/src/amaz_lib/MazeSolver.py b/src/amaz_lib/MazeSolver.py index 18033e6..b93ae4b 100644 --- a/src/amaz_lib/MazeSolver.py +++ b/src/amaz_lib/MazeSolver.py @@ -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): diff --git a/test.txt b/test.txt index d2bfc11..b969fc7 100644 --- a/test.txt +++ b/test.txt @@ -1,19 +1,19 @@ -D553955513B913B -9796C553AA86AC2 -83C151146AC7C12 -C03856817C393EA -96EC53A8556AC3A -8553FC6AFFFABC2 -8792FD5057FAC16 -E946FFFAFFF83AB -92913BFAFD52AC2 -AEAAC6FAFFFEC3A -A904553A9555382 -828557AAAD5546E -844553C6C151553 -C5395439545453A -D546D546D555546 +D53939553D13B93 +93AAC697A92C6AA +AC6A95056AE956A +A916856D1454152 +A843C5394555696 +A87AF96AFFFB96B +AE96FC5457F8412 +C52BFFFBFFFC102 +B94453FAFD516EA +86953AFAFFFA956 +81692C54153A853 +AC3A83950546C3A +ABA86C2D69517C2 +C2AC55293ABC53A +D6C55546C4457C6 1,1 2,2 -EEESWSEEEEEENNESSSESWWWSSSSSSSSWNNWWWNNWSSSSEEESWWSWNWWNENWNNNENEENNWWWNENNWNE +EESSWN From 92c6237f062759392a529b6469a0e0fe54e68590 Mon Sep 17 00:00:00 2001 From: David GAILLETON Date: Sun, 29 Mar 2026 15:38:40 +0200 Subject: [PATCH 2/3] fix(astar): the actual astar wasn't the real astar algoritm --- config.txt | 8 +- src/amaz_lib/MazeSolver.py | 219 ++++++++++++++++++++----------------- 2 files changed, 124 insertions(+), 103 deletions(-) diff --git a/config.txt b/config.txt index f441fba..091d975 100644 --- a/config.txt +++ b/config.txt @@ -1,8 +1,8 @@ -WIDTH=11 -HEIGHT=11 +WIDTH=15 +HEIGHT=15 ENTRY=1,1 -EXIT=11,11 +EXIT=15,15 OUTPUT_FILE=maze.txt -PERFECT=True +PERFECT=False GENERATOR=Kruskal SOLVER=AStar diff --git a/src/amaz_lib/MazeSolver.py b/src/amaz_lib/MazeSolver.py index b93ae4b..330a999 100644 --- a/src/amaz_lib/MazeSolver.py +++ b/src/amaz_lib/MazeSolver.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod from .Maze import Maze +from typing import Any import numpy as np @@ -15,135 +16,155 @@ class MazeSolver(ABC): 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() -> int: - return len(self.path) + 1 + def g(self, n: tuple[int, int]) -> int: + return len(self.path) + 1 - def h(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(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]) + ) - return g() + h(n) + 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: - self.path = [(self.start, self.best_path(maze, self.start, None))] - visited = [self.start] - 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(self.path[-1][1])) - self.path[-1][1].pop(k) - continue - - while len(self.path[-1][1]) > 0: - next_pos = self.get_next_pos( - list(self.path[-1][1].keys())[0], self.path[-1][0] - ) - if next_pos in visited: - k = next(iter(self.path[-1][1])) - self.path[-1][1].pop(k) - else: - break - if len(self.path[-1][1]) == 0: - self.path.pop(-1) - continue - - pre = self.get_opposit(list(self.path[-1][1].keys())[0]) - self.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(self.path) == 0: - return None - self.path[-1] = (self.end, {}) - return "".join( - 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: - 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): From 0f77e0c6e46dd5dc68dda8720c3b5194c4409686 Mon Sep 17 00:00:00 2001 From: David GAILLETON Date: Mon, 30 Mar 2026 14:37:33 +0200 Subject: [PATCH 3/3] fix buffer overflow in put pixel + margin calculation --- a_maze_ing.py | 79 ++++++++++++++++++++------------------ config.txt | 2 +- src/amaz_lib/MazeSolver.py | 18 ++++----- 3 files changed, 50 insertions(+), 49 deletions(-) diff --git a/a_maze_ing.py b/a_maze_ing.py index 8175b2e..04bd3ae 100644 --- a/a_maze_ing.py +++ b/a_maze_ing.py @@ -41,6 +41,8 @@ class MazeMLX: ) def put_pixel(self, x, y) -> 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) self.buf[offset + 0] = 0xFF @@ -64,23 +66,23 @@ class MazeMLX: def update_maze(self, maze: np.ndarray) -> None: self.clear_image() - margin = math.trunc( - math.sqrt(self.width if self.width > self.height else self.height) - // 2 - ) - line_len = math.trunc( - ( - (self.height - margin) // len(maze) - if self.height > self.width - else (self.width - margin) // len(maze[0]) - ) - ) + + rows = len(maze) + cols = len(maze[0]) + + line_len = min(self.width // cols, self.height // rows) + + maze_width = cols * line_len + maze_height = rows * line_len + + margin_x = (self.width - maze_width) // 2 + margin_y = (self.height - maze_height) // 2 for y in range(len(maze)): 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 + 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)) @@ -104,33 +106,34 @@ class MazeMLX: 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]) - ) - ) + + rows = len(maze) + cols = len(maze[0]) + + line_len = min(self.width // cols, self.height // rows) + + maze_width = cols * line_len + maze_height = rows * line_len + + margin_x = (self.width - maze_width) // 2 + margin_y = (self.height - maze_height) // 2 + self.update_maze(maze) for i in range(len(path)): ul = ( - (actual[0]) * cell_size + margin + 12, - (actual[1]) * cell_size + 12 + margin, + (actual[0]) * line_len + margin_x + 12, + (actual[1]) * line_len + 12 + margin_y, ) dr = ( - (actual[0]) * cell_size + cell_size + margin - 12, - (actual[1]) * cell_size + cell_size - 12 + margin, + (actual[0]) * line_len + line_len + margin_x - 12, + (actual[1]) * line_len + line_len - 12 + margin_y, ) 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 + 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": @@ -146,12 +149,12 @@ class MazeMLX: 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, + (actual[0]) * line_len + margin_x + 12, + (actual[1]) * line_len + 12 + margin_y, ) dr = ( - (actual[0]) * cell_size + cell_size + margin - 12, - (actual[1]) * cell_size + cell_size - 12 + margin, + (actual[0]) * line_len + line_len + margin_x - 12, + (actual[1]) * line_len + line_len - 12 + margin_y, ) self.put_block(ul, dr) self.redraw_image() diff --git a/config.txt b/config.txt index 091d975..71cf595 100644 --- a/config.txt +++ b/config.txt @@ -1,7 +1,7 @@ WIDTH=15 HEIGHT=15 ENTRY=1,1 -EXIT=15,15 +EXIT=10,10 OUTPUT_FILE=maze.txt PERFECT=False GENERATOR=Kruskal diff --git a/src/amaz_lib/MazeSolver.py b/src/amaz_lib/MazeSolver.py index 330a999..c77bffb 100644 --- a/src/amaz_lib/MazeSolver.py +++ b/src/amaz_lib/MazeSolver.py @@ -11,7 +11,7 @@ class MazeSolver(ABC): @abstractmethod def solve( - self, maze: Maze, height: int = None, width: int = None + self, maze: Maze, height: int | None = None, width: int | None = None ) -> str: ... @@ -38,9 +38,6 @@ class AStar(MazeSolver): super().__init__(start, end) self.path = [] - def g(self, n: tuple[int, int]) -> int: - return len(self.path) + 1 - def h(self, n: tuple[int, int]) -> int: return ( max(n[0], self.end[0]) @@ -49,9 +46,6 @@ class AStar(MazeSolver): - min(n[1], self.end[1]) ) - def f(self, n: tuple[int, int]) -> int: - return self.g(n) + self.h(n) - def get_paths( self, maze: np.ndarray, @@ -103,7 +97,7 @@ class AStar(MazeSolver): self.start, 0, self.h(self.start), - self.f(self.start), + self.h(self.start), None, ) ) @@ -161,7 +155,9 @@ class AStar(MazeSolver): break return res - def solve(self, maze: Maze, height: int = None, width: int = None) -> str: + 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) @@ -170,7 +166,9 @@ 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 = None, width: int | None = None + ) -> str: path_str = "" visited = np.zeros((height, width), dtype=bool) path = list()