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,
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
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:
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)
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)
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
make_pacman(maze, walls_const, pacman_tracker, callback=display_maze)
-# pathfind()
-
while False:
make_perfect(maze, network_tracker, callback=display_maze)
# poll_events(200)
# maze_make_empty(maze, walls_const, callback=display_maze)
# poll_events(200)
# maze._rebuild()
-while False:
+
+
+while True:
poll_events(16)
backend.uninit()
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
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
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
from amazeing.utils.quadtree import Rect
-class NetworkID:
- pass
-
-
class DualForest:
"""
A forest of trees that contour networks
-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
-WIDTH=25
-HEIGHT=25
+WIDTH=100
+HEIGHT=100
ENTRY=0,0
EXIT=24,24
OUTPUT_FILE=test