]> Untitled Git - axy/ft/a-maze-ing.git/commitdiff
Scuffed curses frontend
authorAxy <gilliardmarthey.axel@gmail.com>
Sat, 28 Feb 2026 15:33:13 +0000 (16:33 +0100)
committerAxy <gilliardmarthey.axel@gmail.com>
Sat, 28 Feb 2026 15:33:13 +0000 (16:33 +0100)
__main__.py
amazeing/__init__.py
amazeing/config/config_parser.py
amazeing/maze_class/maze_walls.py
amazeing/maze_display/TTYdisplay.py
amazeing/maze_display/__init__.py
amazeing/maze_display/backend.py

index e15277dc5aa79532b25e0893795f24c16099cfed..6d94cb7b10b2c59308d45887b8987d826c4af0fc 100644 (file)
@@ -1,3 +1,4 @@
+import curses
 from amazeing import (
     Maze,
     TTYBackend,
@@ -6,16 +7,18 @@ from amazeing import (
     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)
 
@@ -26,18 +29,54 @@ pattern.fill(maze)
 
 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)
index 3c2e118f7d6e7fc8a29b57d83aa0d476568cf685..7b2c0d22beefddb5bb924bef330be88098fafa2d 100644 (file)
@@ -2,7 +2,7 @@ __version__ = "0.0.0"
 __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
 
@@ -11,7 +11,7 @@ __all__ = [
     "Maze",
     "Pattern",
     "Backend",
-    "PixelCoord",
+    "IVec2",
     "TTYBackend",
     "maze_make_pacman",
     "maze_make_perfect",
index ded39469de0851ec31a2a7acecd0c3cac9c41aac..f20027d1615d1ab27ef2523238fd514073b49f74 100644 (file)
@@ -15,7 +15,6 @@ from .parser_combinator import (
     one_of,
     pair,
     parser_complete,
-    parser_default,
     parser_map,
     preceeded,
     recognize,
index cfbb96613aa0497dd545609e981ac0af9f7c96ec..178b619c1daa614b080e015139b83445844a1a2c 100644 (file)
@@ -1,6 +1,7 @@
 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:
@@ -113,7 +114,7 @@ class WallCoord:
     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] = (
@@ -122,7 +123,7 @@ class WallCoord:
         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:
@@ -173,8 +174,8 @@ class CellCoord:
             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])
index ccbc7669a1349dd50a3b08d795c74032a37f19e7..8a44ea84b6d5b7915cecd8fc07059e40489b2b4a 100644 (file)
-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
index 972cb85136d5829e4ee49d55917cd316b9233a59..6ae3ed2e29e95e2514affb49cec6ad511054ce8e 100644 (file)
@@ -1,11 +1,11 @@
 __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",
 ]
index 56d173bc42b70d534aaeb14b679a0bed314568de..45d346e9f635ef504d473d66071abb4842a24219 100644 (file)
@@ -1,19 +1,83 @@
 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