+import curses
from amazeing import (
Maze,
TTYBackend,
maze_make_perfect,
)
from time import sleep
-from sys import stdin
+from sys import stderr, stdin
from amazeing.config.config_parser import Config
from amazeing.maze_class.maze_walls import Cardinal, CellCoord
+from amazeing.maze_display.TTYdisplay import TTYTile
+from amazeing.maze_display.backend import IVec2
# random.seed(42)
# print(Config.parse(stdin.read()).__dict__)
-dims = (25, 25)
+dims = (15, 15)
maze = Maze(dims)
walls_const = set(maze.walls_full())
+backend = TTYBackend(IVec2(*dims), IVec2(1, 1), IVec2(2, 2))
+curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_BLACK)
+black = curses.color_pair(1)
+empty = (" ", black)
+style_empty = backend.add_style(
+ TTYTile(
+ [
+ [empty, empty, empty],
+ [empty, empty, empty],
+ [empty, empty, empty],
+ ]
+ )
+)
+curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE)
+white = curses.color_pair(2)
+full = ("#", white)
+style_full = backend.add_style(
+ TTYTile(
+ [
+ [full, full, full],
+ [full, full, full],
+ [full, full, full],
+ ]
+ )
+)
+
+
+def clear_backend() -> None:
+ dims = backend.dims() * 2 + 1
+ backend.set_style(style_empty)
+ for x in range(dims.x):
+ for y in range(dims.y):
+ backend.draw_tile(IVec2(x, y))
+
def display_maze(maze: Maze) -> None:
- backend = TTYBackend(*dims, style="\x1b[48;5;240m \x1b[0m")
- backend.set_style("\x1b[48;5;248m \x1b[0m")
+ clear_backend()
+ backend.set_style(style_full)
for wall in maze.walls_full():
- for pixel in wall.pixel_coords():
- backend.draw_pixel(pixel)
+ for pixel in wall.tile_coords():
+ backend.draw_tile(pixel)
backend.present()
- sleep(0.05)
+ if backend.event(0) is not None:
+ exit()
maze_make_perfect(maze, callback=display_maze)
+backend.event(-1)
# maze_make_pacman(maze, walls_const, callback=display_maze)
while False:
maze_make_perfect(maze, callback=display_maze)
__author__ = "luflores & agilliar"
from amazeing.maze_class import WallCoord, Maze, Pattern
-from amazeing.maze_display import Backend, PixelCoord, TTYBackend
+from amazeing.maze_display import Backend, IVec2, TTYBackend
from .maze_make_pacman import maze_make_pacman
from .maze_make_perfect import maze_make_perfect
"Maze",
"Pattern",
"Backend",
- "PixelCoord",
+ "IVec2",
"TTYBackend",
"maze_make_pacman",
"maze_make_perfect",
one_of,
pair,
parser_complete,
- parser_default,
parser_map,
preceeded,
recognize,
from enum import Enum, auto
+from sys import stderr
from typing import Iterable, Optional, cast
-from ..maze_display import PixelCoord
+from ..maze_display import IVec2
class NetworkID:
def neighbours(self) -> list["WallCoord"]:
return self.a_neighbours() + self.b_neighbours()
- def pixel_coords(self) -> Iterable[PixelCoord]:
+ def tile_coords(self) -> Iterable[IVec2]:
a: Iterable[int] = [self.a * 2]
b: Iterable[int] = [self.b * 2, self.b * 2 + 1, self.b * 2 + 2]
x_iter: Iterable[int] = (
y_iter: Iterable[int] = (
a if self.orientation == Orientation.HORIZONTAL else b
)
- return (PixelCoord(x, y) for x in x_iter for y in y_iter)
+ return (IVec2(x, y) for x in x_iter for y in y_iter)
def neighbour_cells(self) -> list["CellCoord"]:
if self.orientation == Orientation.HORIZONTAL:
case Cardinal.WEST:
return WallCoord(Orientation.VERTICAL, self.__x + 1, self.__y)
- def pixel_coords(self) -> Iterable[PixelCoord]:
- return [PixelCoord(self.__x * 2 + 1, self.__y * 2 + 1)]
+ def pixel_coords(self) -> Iterable[IVec2]:
+ return [IVec2(self.__x * 2 + 1, self.__y * 2 + 1)]
def offset(self, by: tuple[int, int]) -> "CellCoord":
return CellCoord(self.__x + by[0], self.__y + by[1])
-from .backend import Backend, PixelCoord
-import sys
+from sys import stderr
+from .backend import Backend, IVec2, BackendEvent, KeyboardInput
+import curses
-class TTYBackend(Backend):
+class TTYTile:
+ def __init__(self, pixels: list[list[tuple[str, int]]]) -> None:
+ self.__pixels: list[list[tuple[str, int]]] = pixels
+
+ def blit(
+ self, src: IVec2, dst: IVec2, size: IVec2, window: curses.window
+ ) -> None:
+ for y, line in enumerate(self.__pixels[src.y : src.y + size.y]):
+ for x, (char, attrs) in enumerate(line[src.x : src.x + size.x]):
+ window.addch(dst.y + y, dst.x + x, char, attrs)
+
+
+class TTYTileMap:
+ def __init__(self, wall_dim: IVec2, cell_dim: IVec2) -> None:
+ self.__wall_dim: IVec2 = wall_dim
+ self.__cell_dim: IVec2 = cell_dim
+ self.__tiles: list[TTYTile] = []
+
+ def add_tile(self, tile: TTYTile) -> int:
+ res = len(self.__tiles)
+ self.__tiles.append(tile)
+ return res
+
+ def dst_coord(self, pos: IVec2) -> IVec2:
+ return (n := pos // 2) * self.__cell_dim + (pos - n) * self.__wall_dim
+
+ def src_coord(self, pos: IVec2) -> IVec2:
+ return pos % 2 * self.__wall_dim
+
+ def tile_size(self, pos: IVec2) -> IVec2:
+ return (pos + 1) % 2 * self.__wall_dim + pos % 2 * self.__cell_dim
+
+ def draw_at(self, pos: IVec2, idx: int, window: curses.window) -> None:
+ self.__tiles[idx].blit(
+ self.src_coord(pos),
+ self.dst_coord(pos),
+ self.tile_size(pos),
+ window,
+ )
+
+
+class TTYBackend(Backend[int]):
"""
Takes the ABC Backend and displays the maze in the terminal.
"""
def __init__(
- self, maze_width: int, maze_height: int, style: str = " "
+ self, maze_dims: IVec2, wall_dim: IVec2, cell_dim: IVec2
) -> None:
super().__init__()
- self.width: int = maze_width * 2 + 1
- self.height: int = maze_height * 2 + 1
- self.style: str = style
- self.lines: list[list[str]] = [
- [style for _ in range(0, self.width)]
- for _ in range(0, self.height)
- ]
+ self.__tilemap: TTYTileMap = TTYTileMap(wall_dim, cell_dim)
+ self.__style = 0
+
+ dims = self.__tilemap.dst_coord(maze_dims * 2 + 2)
+
+ self.__screen: curses.window = curses.initscr()
+ curses.start_color()
+ curses.noecho()
+ curses.cbreak()
+ self.__screen.keypad(True)
+
+ self.__pad: curses.window = curses.newpad(dims.y, dims.x)
+ self.__dims = maze_dims
- def draw_pixel(self, pos: PixelCoord) -> None:
- self.lines[pos.y][pos.x] = self.style
+ def __del__(self):
+ curses.nocbreak()
+ self.__screen.keypad(False)
+ curses.echo()
+ curses.endwin()
- def set_style(self, style: str) -> None:
- self.style = style
+ def add_style(self, style: TTYTile) -> int:
+ return self.__tilemap.add_tile(style)
+
+ def dims(self) -> IVec2:
+ return self.__dims
+
+ def draw_tile(self, pos: IVec2) -> None:
+ self.__tilemap.draw_at(pos, self.__style, self.__pad)
+
+ def set_style(self, style: int) -> None:
+ self.__style = style
def present(self) -> None:
- for line in self.lines:
- for char in line:
- sys.stdout.write(char)
- sys.stdout.write("\n")
- sys.stdout.flush()
+ self.__screen.refresh()
+ self.__pad.refresh(
+ 0,
+ 0,
+ 0,
+ 0,
+ min(self.__pad.getmaxyx()[0] - 1, self.__screen.getmaxyx()[0] - 1),
+ min(self.__pad.getmaxyx()[1] - 1, self.__screen.getmaxyx()[1] - 1),
+ )
+
+ def event(self, timeout_ms: int = -1) -> BackendEvent | None:
+ self.__screen.timeout(timeout_ms)
+ try:
+ return KeyboardInput(self.__screen.getkey())
+ except curses.error:
+ return None
__version__ = "0.0.0"
__author__ = "luflores & agilliar"
-from .backend import Backend, PixelCoord
+from .backend import Backend, IVec2
from .TTYdisplay import TTYBackend
__all__ = [
"Backend",
- "PixelCoord",
+ "IVec2",
"TTYBackend",
]
from abc import ABC, abstractmethod
+from collections.abc import Callable
+from dataclasses import dataclass
-class PixelCoord:
+class IVec2:
def __init__(self, x: int, y: int) -> None:
self.x: int = x
self.y: int = y
+ @staticmethod
+ def splat(n: int) -> "IVec2":
+ return IVec2(n, n)
-class Backend(ABC):
+ @staticmethod
+ def with_op(
+ op: Callable[[int, int], int],
+ ) -> Callable[["IVec2", "int | IVec2"], "IVec2"]:
+ return lambda self, other: IVec2(
+ op(
+ self.x,
+ (
+ other
+ if isinstance(other, IVec2)
+ else (other := IVec2.splat(other))
+ ).x,
+ ),
+ op(self.y, other.y),
+ )
+
+ def __mul__(self, other: "int | IVec2") -> "IVec2":
+ return self.with_op(int.__mul__)(self, other)
+
+ def __add__(self, other: "int | IVec2") -> "IVec2":
+ return self.with_op(int.__add__)(self, other)
+
+ def __sub__(self, other: "int | IVec2") -> "IVec2":
+ return self.with_op(int.__sub__)(self, other)
+
+ def __floordiv__(self, other: "int | IVec2") -> "IVec2":
+ return self.with_op(int.__floordiv__)(self, other)
+
+ def __mod__(self, other: "int | IVec2") -> "IVec2":
+ return self.with_op(int.__mod__)(self, other)
+
+
+@dataclass
+class KeyboardInput:
+ sym: str
+
+
+class CloseRequested:
+ pass
+
+
+type BackendEvent = KeyboardInput | CloseRequested
+
+
+class Backend[T](ABC):
"""
ABC for the maze display.
defining how the maze should be drawn.
- (PixelCoord)
"""
@abstractmethod
- def draw_pixel(self, pos: PixelCoord) -> None:
+ 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