From: Axy Date: Wed, 25 Mar 2026 04:48:47 +0000 (+0100) Subject: Bad pathfinding yippee X-Git-Url: https://git.uwuaxy.net/animations_scrolling.mp4?a=commitdiff_plain;h=c051fcd6964663f33bafc5330dcd832762699efc;p=axy%2Fft%2Fa-maze-ing.git Bad pathfinding yippee --- diff --git a/__main__.py b/__main__.py index a5f7d62..9881279 100644 --- a/__main__.py +++ b/__main__.py @@ -8,6 +8,7 @@ import random from amazeing.config.config_parser import Config +from amazeing.maze.path import path_pixels, pathfind_astar from amazeing.utils import CellCoord from amazeing.maze import ( NetworkTracker, @@ -36,17 +37,17 @@ network_tracker = NetworkTracker(maze) backend = TTYBackend(dims, config.tilemap_wall_size, config.tilemap_cell_size) pair_map = extract_pairs(config) tilemaps = TileMaps(config, pair_map, backend) -filler = TileCycle(tilemaps.filler, backend.set_filler) -empty = TileCycle(tilemaps.empty, backend.map_style_cb()) -backend.set_bg_init(lambda _: empty.curr_style()) +filler_style = TileCycle(tilemaps.filler, backend.set_filler) +empty_style = TileCycle(tilemaps.empty, backend.map_style_cb()) +backend.set_bg_init(lambda _: empty_style.curr_style()) -full = TileCycle(tilemaps.full, backend.map_style_cb()) +full_style = TileCycle(tilemaps.full, backend.map_style_cb()) -path = TileCycle(tilemaps.path, backend.map_style_cb()) +path_style = TileCycle(tilemaps.path, backend.map_style_cb()) def clear_backend() -> None: - backend.set_style(empty.curr_style()) + backend.set_style(empty_style.curr_style()) for wall in dirty_tracker.curr_dirty(): if maze.get_wall(wall): continue @@ -58,6 +59,18 @@ class Tick: tick: float | None = None +def display_path() -> None: + if config.entry is None or config.exit is None: + return + path = pathfind_astar( + maze, network_tracker, CellCoord(config.entry), CellCoord(config.exit) + ) + if path is not None: + backend.set_style(path_style.curr_style()) + for tile in path_pixels(CellCoord(config.entry), path): + backend.draw_tile(tile) + + def display_maze(maze: Maze) -> None: now = time.monotonic() if Tick.tick is not None and now - Tick.tick < 0.016: @@ -65,7 +78,8 @@ def display_maze(maze: Maze) -> None: Tick.tick = time.monotonic() clear_backend() - # pathfind() + backend.map_style(path_style.curr_style(), empty_style.curr_style()) + display_path() rewrites = { wall for wall in dirty_tracker.curr_dirty() if maze.get_wall(wall) @@ -76,7 +90,7 @@ def display_maze(maze: Maze) -> None: if maze.check_coord(e) and maze.get_wall(e) } - backend.set_style(full.curr_style()) + backend.set_style(full_style.curr_style()) for wall in rewrites: for pixel in wall.tile_coords(): backend.draw_tile(pixel) @@ -104,10 +118,10 @@ def poll_events(timeout_ms: int = -1) -> None: if event.sym == "q": exit(0) if event.sym == "c": - filler.cycle() - full.cycle() - path.cycle() - empty.cycle() + filler_style.cycle() + full_style.cycle() + path_style.cycle() + empty_style.cycle() else: continue @@ -128,8 +142,6 @@ make_perfect(maze, network_tracker, callback=display_maze) make_pacman(maze, walls_const, pacman_tracker, callback=display_maze) -# pathfind() - while False: make_perfect(maze, network_tracker, callback=display_maze) # poll_events(200) @@ -137,7 +149,9 @@ while False: # maze_make_empty(maze, walls_const, callback=display_maze) # poll_events(200) # maze._rebuild() -while False: + + +while True: poll_events(16) backend.uninit() diff --git a/amazeing/display/tty.py b/amazeing/display/tty.py index a1459ea..892318b 100644 --- a/amazeing/display/tty.py +++ b/amazeing/display/tty.py @@ -521,6 +521,13 @@ class TTYBackend: def get_style_height(self, style: int) -> int: return self.__style_bimap.get(style).height() + def map_style(self, src: int, dst: int) -> None: + if src == dst: + return + if self.get_style_height(src) != 0: + self.__drawn = QuadTree() + self.__style_bimap.key_map(src, dst) + def map_style_cb(self) -> Callable[[int], None]: curr: int | None = None @@ -528,11 +535,7 @@ class TTYBackend: nonlocal curr if curr is None: curr = new - if curr == new: - return - if self.get_style_height(curr) != 0: - self.__drawn = QuadTree() - self.__style_bimap.key_map(curr, new) + self.map_style(curr, new) curr = new return inner diff --git a/amazeing/maze/maze.py b/amazeing/maze/maze.py index 13f899b..3b6919a 100644 --- a/amazeing/maze/maze.py +++ b/amazeing/maze/maze.py @@ -40,6 +40,9 @@ class Maze: def all_cells(self) -> Iterable[CellCoord]: return CellCoord(self.__dims).all_up_to() + def check_cell(self, cell: CellCoord) -> bool: + return self.__dims.x > cell.x and self.__dims.y > cell.y + def check_coord(self, coord: WallCoord) -> bool: if coord.a < 0 or coord.b < 0: return False diff --git a/amazeing/maze/network_tracker.py b/amazeing/maze/network_tracker.py index 427ffbf..f26d6ff 100644 --- a/amazeing/maze/network_tracker.py +++ b/amazeing/maze/network_tracker.py @@ -8,10 +8,6 @@ from amazeing.utils.avl import BVHKey from amazeing.utils.quadtree import Rect -class NetworkID: - pass - - class DualForest: """ A forest of trees that contour networks diff --git a/amazeing/maze/path.py b/amazeing/maze/path.py index 9bd608c..ed6a3a8 100644 --- a/amazeing/maze/path.py +++ b/amazeing/maze/path.py @@ -1,3 +1,79 @@ -class Path: - def __init__(self) -> None: - pass +from collections.abc import Generator +from dataclasses import dataclass +from amazeing.maze.maze import Maze +from amazeing.maze.network_tracker import NetworkTracker +from amazeing.utils.coords import Cardinal, CellCoord +from amazeing.utils.ivec2 import IVec2 +import heapq + + +def taxicab_distance(a: IVec2, b: IVec2) -> int: + return sum(a.with_op(lambda lhs, rhs: abs(lhs - rhs), b).xy()) + + +type LinkPath = None | tuple[Cardinal, LinkPath] + + +@dataclass(slots=True) +class AStarStep: + dst: CellCoord + path: LinkPath + path_length: int + min_distance: int + + def __lt__(self, other: "AStarStep") -> bool: + return self.min_distance < other.min_distance or ( + self.min_distance == other.min_distance + and self.path_length > other.path_length + ) + + def append(self, card: Cardinal, dst: CellCoord) -> "AStarStep": + next_dst = self.dst.get_neighbour(card) + next_path = (card, self.path) + next_dist = self.path_length + 1 + next_min_dist = next_dist + taxicab_distance(next_dst, dst) + return AStarStep(next_dst, next_path, next_dist, next_min_dist) + + def ends_in_bounds(self, maze: Maze) -> bool: + return maze.check_cell(self.dst) + + def to_path(self) -> list[Cardinal]: + curr = self.path + res = [] + while curr is not None: + res.append(curr[0]) + curr = curr[1] + res.reverse() + return res + + +def pathfind_astar( + maze: Maze, network_tracker: NetworkTracker, src: CellCoord, dst: CellCoord +) -> list[Cardinal] | None: + queue = [AStarStep(src, None, 0, taxicab_distance(src, dst))] + heapq.heapify(queue) + visited = set() + while len(queue) > 0: + curr = heapq.heappop(queue) + if curr.dst in visited: + continue + if curr.dst == dst: + return curr.to_path() + visited.add(curr.dst) + for card in Cardinal.all(): + if maze.get_wall(curr.dst.get_wall(card)): + continue + nxt = curr.append(card, dst) + if not nxt.ends_in_bounds(maze): + continue + heapq.heappush(queue, nxt) + return None + + +def path_pixels(curr: CellCoord, path: list[Cardinal]) -> Generator[IVec2]: + yield curr.tile_coords() + for card in path: + nxt = curr.get_neighbour(card) + yield (curr.tile_coords() + nxt.tile_coords()) // IVec2.splat(2) + yield nxt.tile_coords() + curr = nxt diff --git a/example.conf b/example.conf index dbd2d2c..572d49b 100644 --- a/example.conf +++ b/example.conf @@ -1,5 +1,5 @@ -WIDTH=25 -HEIGHT=25 +WIDTH=100 +HEIGHT=100 ENTRY=0,0 EXIT=24,24 OUTPUT_FILE=test