]> Untitled Git - axy/ft/a-maze-ing.git/commitdiff
Bad pathfinding yippee
authorAxy <gilliardmarthey.axel@gmail.com>
Wed, 25 Mar 2026 04:48:47 +0000 (05:48 +0100)
committerAxy <gilliardmarthey.axel@gmail.com>
Wed, 25 Mar 2026 04:48:47 +0000 (05:48 +0100)
__main__.py
amazeing/display/tty.py
amazeing/maze/maze.py
amazeing/maze/network_tracker.py
amazeing/maze/path.py
example.conf

index a5f7d628bf9d8d208c9d58360659cd24dd7d9ce2..9881279033963e55e0cf38f54eafe93aaf39ddce 100644 (file)
@@ -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()
index a1459eaa7653602afcf279dcecf5f83571ca71c5..892318b9b6ce3bb5edd2a4376bcef92679c4412d 100644 (file)
@@ -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
index 13f899be6e96a0603c25f6e3c5e9abc1896ce2de..3b6919a3a14ff4ec790ebd86818852d6a8477623 100644 (file)
@@ -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
index 427ffbf0839192e2a7799d0b1ea97076cd21a1d9..f26d6ff714f0de2df36beb6baf7716cabf64b028 100644 (file)
@@ -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
index 9bd608c778610b1905de4a9faec5bda0c69af411..ed6a3a896b18a116d4b26d197d9036218a399dea 100644 (file)
@@ -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
index dbd2d2cf7a8361496215e3d840a3bc3789829d15..572d49b8d3cb15149f0b0b308143e19b6a00f28a 100644 (file)
@@ -1,5 +1,5 @@
-WIDTH=25
-HEIGHT=25
+WIDTH=100
+HEIGHT=100
 ENTRY=0,0
 EXIT=24,24
 OUTPUT_FILE=test