**/__pycache__
**/.mypy_cache
+out.prof
+.venv
install:
+ pip install flake8 mypy flameprof
+
+.venv:
+ python -m venv .venv
+
+venv_bash: .venv
+ bash --init-file <(echo ". ~/.bashrc; source .venv/bin/activate")
run:
mypy . --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs
lint-strict:
- flake8 .
- mypy . --strict
+ bash -c "flake8 . --extend-exclude .venv; mypy . --strict"
+
+profile:
+ python -m cProfile -o out.prof __main__.py
+
+.PHONY: install venv run clean lint lint-strict profile
+from sys import stderr
import time
from amazeing import (
Maze,
Pattern,
maze_make_pacman,
maze_make_perfect,
- maze_make_empty,
)
import random
maze = Maze(dims)
dirty_tracker = MazeDirtyTracker(maze)
+pacman_tracker = MazeDirtyTracker(maze)
maze.outline()
def poll_events(timeout_ms: int = -1) -> None:
start = time.monotonic()
- elapsed_ms = lambda: int((time.monotonic() - start) * 1000.0)
- timeout = lambda: (
- max(timeout_ms - elapsed_ms(), 0) if timeout_ms != -1 else -1
- )
+
+ 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 event is None:
- if timeout_ms == -1:
- continue
- return
+ if isinstance(event, bool):
+ if timeout() == 0 and not event:
+ return
+ continue
if isinstance(event, CloseRequested) or event.sym == "q":
exit(0)
if event.sym == "c":
empty.cycle()
else:
continue
- backend.present()
prev_solution: list[Cardinal] = []
# solution = maze.pathfind(CellCoord(config.entry), CellCoord(config.exit))
# if solution is None or prev_solution == solution:
# return
-# prev_tiles = Cardinal.path_to_tiles(prev_solution, CellCoord(config.entry))
+# prev_tiles = Cardinal.path_to_tiles(prev_solution, CellCoord(config.entry))
# tiles = Cardinal.path_to_tiles(solution, CellCoord(config.entry))
# backend.set_style(empty.curr_style())
# for tile in prev_tiles:
network_tracker = MazeNetworkTracker(maze)
maze_make_perfect(maze, network_tracker, callback=display_maze)
-# maze_make_pacman(maze, walls_const, callback=display_maze)
+maze_make_pacman(maze, walls_const, pacman_tracker, callback=display_maze)
# pathfind()
# maze_make_empty(maze, walls_const, callback=display_maze)
# poll_events(200)
# maze._rebuild()
-poll_events()
+while True:
+ poll_events(16)
__author__ = "luflores & agilliar"
from amazeing.maze_class import WallCoord, Maze, Pattern
-from amazeing.maze_display import Backend, IVec2, TTYBackend
+from amazeing.maze_display import IVec2, TTYBackend
from .maze_make_pacman import maze_make_pacman
from .maze_make_perfect import maze_make_perfect
from .maze_make_empty import maze_make_empty
"WallCoord",
"Maze",
"Pattern",
- "Backend",
"IVec2",
"TTYBackend",
"maze_make_pacman",
"Failed to construct defaulted field " + self.name()
)
acc.append(res[0])
- return self.merge(acc)
+ return self.merge(acc) # type: ignore
return Inner
def MappedField[T, U, V](
cls: Type[ConfigField[T, U]], mapping: Callable[[U], V]
) -> Type[ConfigField[T, V]]:
- class Inner(ConfigField[T, V]): # type: ignore
+ class Inner(ConfigField[T, V]):
def __init__(self, name: str) -> None:
self.__inner = cls(name)
super().__init__(name)
def fields_parser(
- fields_raw: dict[str, type[ConfigField]],
+ fields_raw: dict[str, type[ConfigField[Any]]],
) -> Parser[dict[str, Any]]:
fields = {key: cls(key) for key, cls in fields_raw.items()}
parse_line = terminated(line_parser(fields), cut(tag("\n")))
-from typing import Callable, Generator, Iterable, cast
-
+from typing import Callable, Generator, Iterable
from amazeing.maze_display.backend import IVec2
from .maze_coords import (
CellCoord,
def walls_empty(self) -> Iterable[WallCoord]:
return filter(lambda w: not self.get_wall(w), self.all_walls())
+
+ def wall_cuts_cycle(self, wall: WallCoord) -> bool:
+ return any(
+ (
+ len(
+ [
+ ()
+ for wall in self.get_walls_checked(list(cell.walls()))
+ if wall
+ ]
+ )
+ >= (3 if self.get_wall(wall) else 2)
+ )
+ for cell in wall.neighbour_cells()
+ )
+
+ def wall_leaf_neighbours(self, coord: WallCoord) -> list[WallCoord]:
+ leaf_f: Callable[
+ [Callable[[WallCoord], list[WallCoord]]], list[WallCoord]
+ ] = lambda f: (
+ list(filter(lambda c: self.check_coord(c), f(coord)))
+ if all(not wall for wall in self.get_walls_checked(f(coord)))
+ else []
+ )
+ return leaf_f(WallCoord.a_neighbours) + leaf_f(WallCoord.b_neighbours)
from collections.abc import Iterable
from amazeing.maze_class.maze import Maze
from amazeing.maze_class.maze_coords import WallCoord
+from amazeing.utils.randset import Randset
class MazeDirtyTracker:
def __init__(self, maze: Maze) -> None:
self.__maze: Maze = maze
- self.__dirty: set[WallCoord] = set()
+ self.__dirty: Randset[WallCoord] = Randset()
maze.observers.add(self.__observer)
- def __del__(self):
+ def __repr__(self) -> str:
+ return f"MazeDirtyTracker({self.__dirty})"
+
+ def __del__(self) -> None:
self.__maze.observers.discard(self.__observer)
def __observer(self, wall: WallCoord) -> None:
self.__dirty ^= {wall}
- def end(self):
- self.__maze.observers.discard(self.__observer)
-
- def clear(self) -> set[WallCoord]:
+ def clear(self) -> Randset[WallCoord]:
res = self.__dirty
- self.__dirty = set()
+ self.__dirty = Randset()
return res
def curr_dirty(self) -> Iterable[WallCoord]:
- return list(self.__dirty)
+ return self.__dirty
+
+ def end(self) -> None:
+ self.__maze.observers.discard(self.__observer)
def wall_bisects(self, wall: WallCoord) -> bool:
return self.__forest.wall_bisects(wall)
- def end(self):
+ def end(self) -> None:
self.__maze.observers.discard(self.__observer)
layout_sort_chunked,
layout_split,
)
-from .backend import Backend, IVec2, BackendEvent, KeyboardInput
+from .backend import IVec2, BackendEvent, KeyboardInput
import curses
dim,
)
- def add_style(tilemap, size=mazetile_dims):
+ def add_style(
+ tilemap: list[ColoredLine], size: IVec2 = mazetile_dims
+ ) -> int:
return backend.add_style(new_tilemap(tilemap, size))
self.empty: list[int] = list(map(add_style, config.tilemap_empty))
class TileCycle[T]:
- def __init__(self, styles: list[T], cb: Callable[[T], None], i=0) -> None:
+ def __init__(
+ self, styles: list[T], cb: Callable[[T], None], i: int = 0
+ ) -> None:
if len(styles) == 0:
raise BackendException("No styles provided in tilecycle")
self.__styles = styles
self.__i = i
cb(styles[i])
- def cycle(self, by: int = 1):
+ def cycle(self, by: int = 1) -> None:
new = (self.__i + by) % len(self.__styles)
if new != self.__i:
self.__cb(self.__styles[new])
return self.__styles[self.__i]
-class TTYBackend(Backend[int]):
+class TTYBackend:
def __init__(
self, maze_dims: IVec2, wall_dim: IVec2, cell_dim: IVec2
) -> None:
self.__style_bimap: BiMap[int, IVec2] = BiMap()
- def __del__(self):
+ def __del__(self) -> None:
curses.curs_set(1)
curses.nocbreak()
self.__screen.keypad(False)
def inner(new: int) -> None:
nonlocal curr
- if curr == None:
+ if curr is None:
curr = new
if curr == new:
return
self.__layout.laid_out(IVec2(0, 0), IVec2(x, y))
self.__scratch.overwrite(self.__screen)
- def event(self, timeout_ms: int = -1) -> BackendEvent | None:
+ def event(self, timeout_ms: int = -1) -> BackendEvent | bool:
self.__screen.timeout(timeout_ms)
try:
key = self.__screen.getkey()
except curses.error:
- return None
+ return False
match key:
case "KEY_RESIZE":
self.__resize = True
self.__pad.scroll(IVec2(-1, 0))
case _:
return KeyboardInput(key)
- self.present()
- return None
+ return True
__version__ = "0.0.0"
__author__ = "luflores & agilliar"
-from .backend import Backend, IVec2
+from .backend import IVec2
from .TTYdisplay import TTYBackend
__all__ = [
- "Backend",
"IVec2",
"TTYBackend",
]
-from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from typing import Type, cast
(
other
if isinstance(other, IVec2)
- else (other := type(self).splat(cast(T, other)))
+ else (other := type(self).splat(other))
).x,
),
op(self.y, cast(IVec2[T], other).y),
)
- def innertype(self) -> Type:
+ def innertype(self) -> Type[T]:
return type(self.x)
def __mul__(self, other: "T | IVec2[T]") -> "IVec2[T]":
- return self.with_op(self.innertype().__mul__)(self, other)
+ return self.with_op(getattr(self.innertype(), "__mul__"))(self, other)
def __add__(self, other: "T | IVec2[T]") -> "IVec2[T]":
- return self.with_op(self.innertype().__add__)(self, other)
+ return self.with_op(getattr(self.innertype(), "__add__"))(self, other)
def __sub__(self, other: "T | IVec2[T]") -> "IVec2[T]":
- return self.with_op(self.innertype().__sub__)(self, other)
+ return self.with_op(getattr(self.innertype(), "__sub__"))(self, other)
def __floordiv__(self, other: "T| IVec2[T]") -> "IVec2[T]":
- return self.with_op(self.innertype().__floordiv__)(self, other)
+ return self.with_op(getattr(self.innertype(), "__floordiv__"))(
+ self, other
+ )
def __mod__(self, other: "T | IVec2[T]") -> "IVec2[T]":
- return self.with_op(self.innertype().__mod__)(self, other)
+ return self.with_op(getattr(self.innertype(), "__mod__"))(self, other)
def __eq__(self, value: object, /) -> bool:
if not isinstance(value, IVec2):
return False
- return self.x == value.x and self.y == value.y
+ if self.x != value.x:
+ return False
+ if self.y != value.y:
+ return False
+ return True
def __hash__(self) -> int:
return hash((self.x, self.y))
type BackendEvent = KeyboardInput | CloseRequested
-
-
-class Backend[T](ABC):
- """
- ABC for the maze display.
- defining how the maze should be drawn.
- """
-
- @abstractmethod
- def dims(self) -> IVec2:
- pass
-
- @abstractmethod
- def draw_tile(self, pos: IVec2) -> None:
- pass
-
- @abstractmethod
- def set_style(self, style: T) -> None:
- pass
-
- @abstractmethod
- def present(self) -> None:
- pass
-
- @abstractmethod
- def event(self, timeout_ms: int = -1) -> BackendEvent | None:
- pass
class VBox[T](Box):
def __init__(
- self, layout: Layout, boxes: list[tuple[Box, T]] = []
+ self, layout: Layout[T], boxes: list[tuple[Box, T]] = []
) -> None:
self.boxes: list[tuple[Box, T]] = boxes
- self.layout: Layout = layout
+ self.layout: Layout[T] = layout
@staticmethod
- def noassoc(layout: Layout, boxes: list[Box]) -> "VBox[None]":
+ def noassoc(layout: Layout[None], boxes: list[Box]) -> "VBox[None]":
return VBox(layout, [(box, None) for box in boxes])
def dims(self) -> BVec2:
class HBox[T](Box):
def __init__(
- self, layout: Layout, boxes: list[tuple[Box, T]] = []
+ self, layout: Layout[T], boxes: list[tuple[Box, T]] = []
) -> None:
self.boxes: list[tuple[Box, T]] = boxes
- self.layout: Layout = layout
+ self.layout: Layout[T] = layout
@staticmethod
- def noassoc(layout: Layout, boxes: list[Box]) -> "HBox[None]":
+ def noassoc(layout: Layout[None], boxes: list[Box]) -> "HBox[None]":
return HBox(layout, [(box, None) for box in boxes])
def dims(self) -> BVec2:
self.__inner.laid_out(at, into)
-def vpad_box(min_pad: int = 0, cb=lambda _at, _into: None) -> FBox:
+def vpad_box(
+ min_pad: int = 0,
+ cb: Callable[[IVec2, IVec2], None] = lambda _at, _into: None,
+) -> FBox:
return FBox(IVec2(BInt(0), BInt(min_pad, True)), cb)
-def hpad_box(min_pad: int = 0, cb=lambda _at, _into: None) -> FBox:
+def hpad_box(
+ min_pad: int = 0,
+ cb: Callable[[IVec2, IVec2], None] = lambda _at, _into: None,
+) -> FBox:
return FBox(IVec2(BInt(min_pad, True), BInt(0)), cb)
+from sys import stderr
from typing import Callable
from amazeing import Maze, WallCoord
import random
+from amazeing.maze_class.maze_dirty_tracker import MazeDirtyTracker
+
def maze_make_pacman(
maze: Maze,
walls_const: set[WallCoord],
+ dirty_tracker: MazeDirtyTracker,
callback: Callable[[Maze], None] = lambda _: None,
iterations: int = 10,
) -> None:
for _ in range(0, iterations):
- walls = [wall for wall in maze.walls_full() if wall not in walls_const]
- random.shuffle(walls)
+ walls = dirty_tracker.clear()
n = 0
for wall in walls:
+ if not maze.get_wall(wall) or wall in walls_const:
+ continue
leaf_neighbours = maze.wall_leaf_neighbours(wall)
if not maze.wall_cuts_cycle(wall):
continue
if len(leaf_neighbours) == 0:
- maze.set_wall(wall)
+ maze.set_wall(wall, False)
else:
- maze.set_wall(wall)
- maze.fill_wall(random.choice(leaf_neighbours))
+ maze.set_wall(wall, False)
+ maze.set_wall(random.choice(leaf_neighbours), True)
n += 1
callback(maze)
if n == 0:
break
- maze._rebuild()
self.root.with_parent,
lambda parent: Leaf(parent, value),
)
- return cast(Leaf, self.root.rhs)
+ return cast(Leaf[T], self.root.rhs)
def prepend(self, value: T) -> "Leaf[T]":
if self.root is None:
lambda parent: Leaf(parent, value),
self.root.with_parent,
)
- return cast(Leaf, self.root.lhs)
+ return cast(Leaf[T], self.root.lhs)
def height(self) -> int:
return 0 if self.root is None else self.root.height
self.balance_one_propagate()
return new_leaf
- def balance_one(self):
+ def balance_one(self) -> None:
if abs(self.get_balance()) <= 1:
return
--- /dev/null
+from collections.abc import Iterable, MutableSequence, MutableSet
+from typing import cast, overload
+
+
+class Randset[T](MutableSequence[T], MutableSet[T]):
+ # __getitem__, __setitem__, __delitem__, __len__, and insert().
+ # __contains__, __iter__, __len__,
+ # add(), and discard().
+ def __init__(self) -> None:
+ self.__elems: list[T] = []
+ self.__idx_map: dict[T, int] = {}
+
+ def __repr__(self) -> str:
+ return str(self.__idx_map)
+
+ @overload
+ def __getitem__(self, pos: int) -> T: ...
+ @overload
+ def __getitem__(self, pos: slice) -> "Randset[T]": ...
+
+ def __getitem__(self, pos: int | slice) -> T | "Randset[T]":
+ if isinstance(pos, int):
+ return self.__elems[pos]
+ else:
+ res = Randset[T]()
+ res.__elems = self.__elems[pos]
+ res.__idx_map = {e: i for i, e in enumerate(res.__elems)}
+ return res
+
+ @overload
+ def __setitem__(self, pos: int, value: T) -> None: ...
+ @overload
+ def __setitem__(self, pos: slice, value: Iterable[T]) -> None: ...
+
+ def __setitem__(self, pos: int | slice, value: T | Iterable[T]) -> None:
+ if isinstance(pos, int):
+ del self.__idx_map[self.__elems[pos]]
+ self.__elems[pos] = cast(T, value)
+ self.__idx_map[cast(T, value)] = pos
+ else:
+ raise NotImplementedError("slice setitem in randset")
+
+ def __len__(self) -> int:
+ return len(self.__elems)
+
+ @overload
+ def __delitem__(self, pos: int) -> None: ...
+ @overload
+ def __delitem__(self, pos: slice) -> None: ...
+ def __delitem__(self, pos: int | slice) -> None:
+ if isinstance(pos, int):
+ self.discard(self.__elems[pos])
+ else:
+ elems = self.__elems[pos]
+ for e in elems:
+ self.discard(e)
+
+ def add(self, value: T) -> None:
+ if value in self.__idx_map:
+ return
+ self.__idx_map[value] = len(self.__elems)
+ self.__elems.append(value)
+
+ def discard(self, value: T) -> None:
+ if value not in self.__idx_map:
+ return
+ self.__idx_map[self.__elems[-1]] = self.__idx_map[value]
+ self.__elems[self.__idx_map[value]] = self.__elems[-1]
+ self.__elems.pop()
+ del self.__idx_map[value]
+
+ def insert(self, index: int, value: T) -> None:
+ raise NotImplementedError("slice setitem in randset")