-import time
+from amazeing.display.observer import TTYTracker
from amazeing.maze import (
Maze,
Pattern,
-)
-from amazeing.display import TTYBackend
-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 (
+ make_empty,
NetworkTracker,
- DirtyTracker,
PacmanTracker,
make_pacman,
make_perfect,
)
-from amazeing.display import TileCycle, TileMaps, extract_pairs
-from amazeing.utils import IVec2
-from amazeing.utils.coords import Cardinal
+from amazeing.config.config_parser import Config
+import random
config = Config.parse(open("./example.conf").read())
if config.seed is not None:
random.seed(config.seed)
-dims = IVec2(config.width, config.height)
+maze = Maze(config)
-maze = Maze(dims)
-
-dirty_tracker = DirtyTracker(maze)
pacman_tracker = PacmanTracker(maze)
network_tracker = NetworkTracker(maze)
+tty_tracker = TTYTracker(maze, config)
-backend = TTYBackend(dims, config.tilemap_wall_size, config.tilemap_cell_size)
-pair_map = extract_pairs(config)
-tilemaps = TileMaps(config, pair_map, backend)
-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_style = TileCycle(tilemaps.full, backend.map_style_cb())
-
-path_style = TileCycle(tilemaps.path, backend.map_style_cb())
-
-
-def clear_backend() -> None:
- backend.set_style(empty_style.curr_style())
- for wall in dirty_tracker.curr_dirty():
- if maze.get_wall(wall):
- continue
- for tile in wall.tile_coords():
- backend.draw_tile(tile)
-
-
-class Tick:
- tick: float | None = None
- prev_path: list[Cardinal] | None = None
+excluded = {maze.entry, maze.exit}
-
-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 Tick.prev_path is not None:
- if Tick.prev_path == path:
- return
- backend.set_style(empty_style.curr_style())
- for tile in path_pixels(CellCoord(config.entry), Tick.prev_path):
- backend.draw_tile(tile)
- Tick.prev_path = path
- 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:
- return
- Tick.tick = time.monotonic()
-
- clear_backend()
- display_path()
-
- rewrites = {
- wall for wall in dirty_tracker.curr_dirty() if maze.get_wall(wall)
- } | {
- e
- for wall in dirty_tracker.curr_dirty()
- for e in wall.neighbours()
- if maze.check_coord(e) and maze.get_wall(e)
- }
-
- backend.set_style(full_style.curr_style())
- for wall in rewrites:
- for pixel in wall.tile_coords():
- backend.draw_tile(pixel)
- dirty_tracker.clear()
- backend.present()
- poll_events(0)
-
-
-def poll_events(timeout_ms: int = -1) -> None:
- start = time.monotonic()
-
- def elapsed_ms() -> int:
- return int((time.monotonic() - start) * 1000.0)
-
- def timeout() -> int:
- return max(timeout_ms - elapsed_ms(), 0) if timeout_ms != -1 else -1
-
- backend.present()
- while True:
- event = backend.event(timeout())
- if isinstance(event, bool):
- if timeout() == 0 and not event:
- return
- continue
- if event.sym == "q":
- exit(0)
- if event.sym == "c":
- filler_style.cycle()
- full_style.cycle()
- path_style.cycle()
- empty_style.cycle()
- else:
- continue
-
-
-excluded = set()
-if config.entry is not None:
- excluded.add(CellCoord(config.entry))
-if config.exit is not None:
- excluded.add(CellCoord(config.exit))
-
-pattern = Pattern(config.maze_pattern).centered_for(dims, excluded)
+pattern = Pattern(config.maze_pattern).centered_for(maze.dims, excluded)
pattern.fill(maze)
maze.outline()
walls_const = set(maze.walls_full())
-make_perfect(maze, network_tracker, callback=display_maze)
-make_pacman(maze, walls_const, pacman_tracker, callback=display_maze)
+make_perfect(maze, network_tracker)
+make_pacman(maze, walls_const, pacman_tracker)
-while False:
- make_perfect(maze, network_tracker, callback=display_maze)
- # poll_events(200)
- make_pacman(maze, walls_const, callback=display_maze)
- # maze_make_empty(maze, walls_const, callback=display_maze)
- # poll_events(200)
- # maze._rebuild()
+while True:
+ make_perfect(maze, network_tracker)
+ make_pacman(maze, walls_const, pacman_tracker)
+ make_empty(maze, walls_const)
while True:
- poll_events(16)
+ tty_tracker.display_maze()
-backend.uninit()
-print(network_tracker.contour_bound((CellCoord(0, 1), Cardinal.EAST)))
+tty_tracker.backend.uninit()
--- /dev/null
+import time
+from amazeing.config.config_parser import Config
+from amazeing.display.tty import TTYBackend, TileCycle, TileMaps, extract_pairs
+from amazeing.maze.dirty_tracker import DirtyTracker
+from amazeing.maze.maze import Maze
+from amazeing.maze.path import path_pixels, pathfind_astar
+from amazeing.utils.coords import Cardinal
+
+
+class TTYTracker:
+ def __init__(self, maze: Maze, config: Config):
+ self.maze = maze
+ self.dirty_tracker = DirtyTracker(maze)
+ self.backend = TTYBackend(
+ maze.dims, config.tilemap_wall_size, config.tilemap_cell_size
+ )
+ self.pair_map = extract_pairs(config)
+ tilemaps = TileMaps(config, self.pair_map, self.backend)
+ self.filler_style = TileCycle(tilemaps.filler, self.backend.set_filler)
+ self.empty_style = TileCycle(
+ tilemaps.empty, self.backend.map_style_cb()
+ )
+ self.full_style = TileCycle(tilemaps.full, self.backend.map_style_cb())
+ self.path_style = TileCycle(tilemaps.path, self.backend.map_style_cb())
+
+ self.backend.set_bg_init(lambda _: self.empty_style.curr_style())
+
+ self.tick: float | None = None
+ self.prev_path: list[Cardinal] | None = None
+
+ maze.observers.add(lambda _: self.display_maze())
+
+ def clear_backend(self) -> None:
+ self.backend.set_style(self.empty_style.curr_style())
+ for wall in self.dirty_tracker.curr_dirty():
+ if self.maze.get_wall(wall):
+ continue
+ for tile in wall.tile_coords():
+ self.backend.draw_tile(tile)
+
+ def path_invalidated(self) -> bool:
+ if self.prev_path is None:
+ return True
+ src = self.maze.entry
+ for card in self.prev_path:
+ if src.get_wall(card) in self.dirty_tracker.curr_dirty():
+ return True
+ src = src.get_neighbour(card)
+ return False
+
+ def display_path(self) -> None:
+ if (
+ all(map(self.maze.get_wall, self.dirty_tracker.curr_dirty()))
+ and not self.path_invalidated()
+ ):
+ return None
+ path = pathfind_astar(self.maze)
+ if self.prev_path is not None:
+ if self.prev_path == path:
+ return
+ self.backend.set_style(self.empty_style.curr_style())
+ for tile in path_pixels(self.maze.entry, self.prev_path):
+ self.backend.draw_tile(tile)
+ self.prev_path = path
+ if path is not None:
+ self.backend.set_style(self.path_style.curr_style())
+ for tile in path_pixels(self.maze.entry, path):
+ self.backend.draw_tile(tile)
+
+ def poll_events(self) -> None:
+ while True:
+ event = self.backend.event(0)
+ if isinstance(event, bool):
+ if not event:
+ return
+ continue
+ if event.sym == "q":
+ exit(0)
+ if event.sym == "c":
+ self.filler_style.cycle()
+ self.full_style.cycle()
+ self.path_style.cycle()
+ self.empty_style.cycle()
+ else:
+ continue
+
+ def display_maze(self) -> None:
+ now = time.monotonic()
+ if self.tick is not None and now - self.tick < 0.016:
+ return
+ self.tick = time.monotonic()
+
+ self.clear_backend()
+ self.display_path()
+
+ rewrites = {
+ wall
+ for wall in self.dirty_tracker.curr_dirty()
+ if self.maze.get_wall(wall)
+ } | {
+ e
+ for wall in self.dirty_tracker.curr_dirty()
+ for e in wall.neighbours()
+ if self.maze.check_coord(e) and self.maze.get_wall(e)
+ }
+
+ self.backend.set_style(self.full_style.curr_style())
+ for wall in rewrites:
+ for pixel in wall.tile_coords():
+ self.backend.draw_tile(pixel)
+ self.dirty_tracker.clear()
+ self.backend.present()
+ self.poll_events()
-from collections.abc import Iterable
from amazeing.maze import Maze
from amazeing.utils import WallCoord
self.__dirty = set()
return res
- def curr_dirty(self) -> Iterable[WallCoord]:
+ def curr_dirty(self) -> set[WallCoord]:
return self.__dirty
def end(self) -> None:
-from typing import Callable
from amazeing.maze import Maze
from amazeing.utils import WallCoord
import random
maze: Maze,
walls_const: set[WallCoord],
pacman_tracker: PacmanTracker,
- callback: Callable[[Maze], None] = lambda _: None,
iterations: int = 10,
) -> None:
for _ in range(0, iterations):
maze.set_wall(wall, False)
maze.set_wall(random.choice(leaf_neighbours), True)
n += 1
- callback(maze)
if n == 0:
break
def make_perfect(
maze: Maze,
tracker: NetworkTracker,
- callback: Callable[[Maze], None] = lambda _: None,
) -> None:
empty = list(maze.walls_empty())
random.shuffle(empty)
for wall in empty:
if not tracker.wall_bisects(wall):
maze.set_wall(wall, True)
- callback(maze)
from typing import Callable, Generator, Iterable
+from amazeing.config.config_parser import Config
from amazeing.utils import (
CellCoord,
Orientation,
class Maze:
- def __init__(self, dims: IVec2) -> None:
- self.__dims = dims
+ def __init__(self, config: Config) -> None:
+ self.dims = IVec2(config.width, config.height)
self.observers: set[MazeObserver] = set()
+ self.entry: CellCoord = (
+ CellCoord(0, 0)
+ if config.entry is None
+ else CellCoord(config.entry)
+ )
+ self.exit: CellCoord = (
+ CellCoord(self.dims - IVec2.splat(1))
+ if config.exit is None
+ else CellCoord(config.exit)
+ )
self.__walls_full: dict[WallCoord, None] = {}
def get_wall(self, coord: WallCoord) -> bool:
def all_walls(self) -> Generator[WallCoord]:
for orientation, a_count, b_count in [
- (Orientation.HORIZONTAL, self.__dims.y + 1, self.__dims.x),
- (Orientation.VERTICAL, self.__dims.x + 1, self.__dims.y),
+ (Orientation.HORIZONTAL, self.dims.y + 1, self.dims.x),
+ (Orientation.VERTICAL, self.dims.x + 1, self.dims.y),
]:
for a in range(0, a_count):
for b in range(0, b_count):
yield WallCoord(orientation, a, b)
def all_cells(self) -> Iterable[CellCoord]:
- return CellCoord(self.__dims).all_up_to()
+ 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
+ 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
(a_max, b_max) = (
- (self.__dims.y, self.__dims.x - 1)
+ (self.dims.y, self.dims.x - 1)
if coord.orientation == Orientation.HORIZONTAL
- else (self.__dims.x, self.__dims.y - 1)
+ else (self.dims.x, self.dims.y - 1)
)
if coord.a > a_max or coord.b > b_max:
return False
return self.get_walls_checked(id.neighbours())
def outline(self) -> None:
- if self.__dims.x < 1 or self.__dims.y < 1:
+ if self.dims.x < 1 or self.dims.y < 1:
return
for orientation, a_iter, b_iter in [
(
Orientation.VERTICAL,
- (0, self.__dims.x),
- range(0, self.__dims.y),
+ (0, self.dims.x),
+ range(0, self.dims.y),
),
(
Orientation.HORIZONTAL,
- (0, self.__dims.y),
- range(0, self.__dims.x),
+ (0, self.dims.y),
+ range(0, self.dims.x),
),
]:
for a in a_iter:
from amazeing.maze import Maze
from amazeing.utils.coords import (
+ Cardinal,
+ CellCoord,
split_wall_ccw,
+ split_wall_cw,
split_wall_opposite,
)
from amazeing.utils import AVLTree, AVLLeaf, SplitWall, WallCoord
return None
leaf = self.__revmap[wall]
parent = leaf.root()
- print(parent)
if parent.root is None:
raise Exception()
return parent.root.key.rect
from amazeing.utils.ivec2 import IVec2
import heapq
+from amazeing.utils.quadtree import rect_collides
+
def taxicab_distance(a: IVec2, b: IVec2) -> int:
return sum(a.with_op(lambda lhs, rhs: abs(lhs - rhs), b).xy())
return res
-def pathfind_astar(
- maze: Maze, network_tracker: NetworkTracker, src: CellCoord, dst: CellCoord
-) -> list[Cardinal] | None:
+def pathfind_astar(maze: Maze) -> list[Cardinal] | None:
+ src = maze.entry
+ dst = maze.exit
queue = [AStarStep(src, None, 0, taxicab_distance(src, dst))]
heapq.heapify(queue)
- visited = set()
+ visited = {src}
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):
+ if nxt.dst in visited or not nxt.ends_in_bounds(maze):
continue
+ visited.add(nxt.dst)
heapq.heappush(queue, nxt)
return None
type Rect = tuple[IVec2, IVec2]
-def rect_collide(a: Rect, b: Rect) -> bool:
+def rects_overlap(a: Rect, b: Rect) -> bool:
a_start, a_end = a
b_start, b_end = b
return (
)
+def rect_collides(a: Rect, b: IVec2) -> bool:
+ a_start, a_end = a
+ return (
+ a_end.x > b.x
+ and a_end.y > b.y
+ and b.x >= a_start.x
+ and b.y >= a_start.y
+ )
+
+
def rect_contains(a: Rect, b: Rect) -> bool:
a_start, a_end = a
b_start, b_end = b
node_rect = Tree.node_rect(pos, height)
if rect_contains(rect, node_rect):
return True
- if not rect_collide(rect, node_rect):
+ if not rects_overlap(rect, node_rect):
return False
starts = Tree.node_starts(pos, height)
return map4(