]> Untitled Git - axy/ft/a-maze-ing.git/commitdiff
Partial refactor for more IVec2 usage, before possible breakage with config
authorAxy <gilliardmarthey.axel@gmail.com>
Wed, 4 Mar 2026 23:48:44 +0000 (00:48 +0100)
committerAxy <gilliardmarthey.axel@gmail.com>
Wed, 4 Mar 2026 23:48:44 +0000 (00:48 +0100)
__main__.py
amazeing/config/config_parser.py
amazeing/maze_class/maze.py
amazeing/maze_class/maze_pattern.py
amazeing/maze_class/maze_walls.py
amazeing/maze_display/TTYdisplay.py
example.conf

index f71a0d0dfa139288523e6ae5737a07f4eacf6771..1fa2f49476879ae511b16ded6e6a36ffa99c8625 100644 (file)
@@ -11,7 +11,7 @@ import random
 
 from amazeing.config.config_parser import Config
 from amazeing.maze_class.maze_walls import Cardinal, CellCoord
-from amazeing.maze_display.TTYdisplay import Tile
+from amazeing.maze_display.TTYdisplay import Tile, extract_pairs
 from amazeing.maze_display.backend import BackendEvent, CloseRequested, IVec2
 
 config = Config.parse(open("./example.conf").read())
@@ -19,7 +19,7 @@ config = Config.parse(open("./example.conf").read())
 if config.seed is not None:
     random.seed(config.seed)
 
-dims = (config.width, config.height)
+dims = IVec2(config.width, config.height)
 
 maze = Maze(dims)
 
@@ -30,7 +30,7 @@ pattern.fill(maze)
 
 walls_const = set(maze.walls_full())
 
-backend = TTYBackend(IVec2(*dims), IVec2(2, 1), IVec2(2, 1))
+backend = TTYBackend(dims, IVec2(2, 1), IVec2(2, 1))
 curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_BLACK)
 black = curses.color_pair(1)
 empty = (" ", black)
index 2dc9cc6383dd7ff8b94bdc65e81e29959fdc9c3b..fa47b28376d02821f12aa5ef9e612c450a49ef9c 100644 (file)
@@ -1,6 +1,8 @@
 from abc import ABC, abstractmethod
 from collections.abc import Callable, Generator
 from typing import Any, Type
+
+from amazeing.maze_display.backend import IVec2
 from .parser_combinator import (
     ParseResult,
     Parser,
@@ -45,13 +47,16 @@ def spaced[T](parser: Parser[T]) -> Parser[T]:
     return delimited(multispace0, parser, multispace0)
 
 
-def parse_coord(s: str) -> ParseResult[tuple[int, int]]:
-    return pair(
-        terminated(
+def parse_coord(s: str) -> ParseResult[IVec2]:
+    return parser_map(
+        lambda e: IVec2(*e),
+        pair(
+            terminated(
+                parse_int,
+                delimited(multispace0, tag(","), multispace0),
+            ),
             parse_int,
-            delimited(multispace0, tag(","), multispace0),
         ),
-        parse_int,
     )(s)
 
 
@@ -174,8 +179,8 @@ class BoolField(ConfigField[bool]):
         return parse_bool(s)
 
 
-class CoordField(ConfigField[tuple[int, int]]):
-    def parse(self, s: str) -> ParseResult[tuple[int, int]]:
+class CoordField(ConfigField[IVec2]):
+    def parse(self, s: str) -> ParseResult[IVec2]:
         return parse_coord(s)
 
 
@@ -300,16 +305,16 @@ def fields_parser(
 class Config:
     width: int
     height: int
-    entry: tuple[int, int] | None
-    exit: tuple[int, int] | None
+    entry: IVec2 | None
+    exit: IVec2 | None
     output_file: str | None
     perfect: bool
     seed: int | None
     screensaver: bool
     visual: bool
     interactive: bool
-    tilemap_wall_size: tuple[int, int]
-    tilemap_cell_size: tuple[int, int]
+    tilemap_wall_size: IVec2
+    tilemap_cell_size: IVec2
     tilemap_full: list[ColoredLine]
     tilemap_empty: list[ColoredLine]
     tilemap_background: list[ColoredLine]
index 75cebe841736e88d6af686380d0bd4c5a8de4ab4..a01e07d6a314d576a91a608f4c3eb4174cc6134f 100644 (file)
@@ -1,4 +1,6 @@
 from typing import Callable, Generator, Iterable, cast
+
+from amazeing.maze_display.backend import IVec2
 from .maze_walls import (
     MazeWall,
     NetworkID,
@@ -9,9 +11,8 @@ from .maze_walls import (
 
 
 class Maze:
-    def __init__(self, dims: tuple[int, int]) -> None:
-        self.__width: int = dims[0]
-        self.__height: int = dims[1]
+    def __init__(self, dims: IVec2) -> None:
+        self.__dims = dims
         self.__dirty: set[WallCoord] = set()
         self._clear()
 
@@ -20,13 +21,13 @@ class Maze:
             self.__dirty ^= {wall for wall in self.walls_full()}
         # list of lines
         self.horizontal: list[list[MazeWall]] = [
-            [MazeWall() for _ in range(0, self.__width)]
-            for _ in range(0, self.__height + 1)
+            [MazeWall() for _ in range(0, self.__dims.x)]
+            for _ in range(0, self.__dims.y + 1)
         ]
         # list of lines
         self.vertical: list[list[MazeWall]] = [
-            [MazeWall() for _ in range(0, self.__height)]
-            for _ in range(0, self.__width + 1)
+            [MazeWall() for _ in range(0, self.__dims.y)]
+            for _ in range(0, self.__dims.x + 1)
         ]
         self.networks: dict[NetworkID, WallNetwork] = {}
 
@@ -56,8 +57,8 @@ class Maze:
 
     def all_walls(self) -> Generator[WallCoord]:
         for orientation, a_count, b_count in [
-            (Orientation.HORIZONTAL, self.__height + 1, self.__width),
-            (Orientation.VERTICAL, self.__width + 1, self.__height),
+            (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):
@@ -67,9 +68,9 @@ class Maze:
         if coord.a < 0 or coord.b < 0:
             return False
         (a_max, b_max) = (
-            (self.__height, self.__width - 1)
+            (self.__dims.y, self.__dims.x - 1)
             if coord.orientation == Orientation.HORIZONTAL
-            else (self.__width, self.__height - 1)
+            else (self.__dims.x, self.__dims.y - 1)
         )
         if coord.a > a_max or coord.b > b_max:
             return False
@@ -119,14 +120,18 @@ class Maze:
             del self.networks[to_merge]
 
     def outline(self) -> None:
-        if self.__width < 1 or self.__height < 1:
+        if self.__dims.x < 1 or self.__dims.y < 1:
             return
         for orientation, a_iter, b_iter in [
-            (Orientation.VERTICAL, (0, self.__width), range(0, self.__height)),
+            (
+                Orientation.VERTICAL,
+                (0, self.__dims.x),
+                range(0, self.__dims.y),
+            ),
             (
                 Orientation.HORIZONTAL,
-                (0, self.__height),
-                range(0, self.__width),
+                (0, self.__dims.y),
+                range(0, self.__dims.x),
             ),
         ]:
             for a in a_iter:
index 2e5ed6229b30c9f617b9285fe16b8a7ebadc8808..5c8df9a1405acef2931940b636ebba2ef286542b 100644 (file)
@@ -1,3 +1,4 @@
+from amazeing.maze_display.backend import IVec2
 from .maze import Maze
 from .maze_walls import CellCoord
 from typing import Callable
@@ -20,32 +21,29 @@ class Pattern:
             if char != " "
         }
 
-    def offset(self, by: tuple[int, int]) -> "Pattern":
+    def offset(self, by: IVec2) -> "Pattern":
         pattern: Pattern = Pattern([])
         pattern.cells = {cell.offset(by) for cell in self.cells}
         return pattern
 
-    def dims(self) -> tuple[int, int]:
+    def dims(self) -> IVec2:
         dim_by: Callable[[Callable[[CellCoord], int]], int] = lambda f: (
             max(map(lambda c: f(c) + 1, self.cells), default=0)
             - min(map(f, self.cells), default=0)
         )
-        return (dim_by(CellCoord.x), dim_by(CellCoord.y))
+        return IVec2(dim_by(CellCoord.x), dim_by(CellCoord.y))
 
     def normalized(self) -> "Pattern":
         min_by: Callable[[Callable[[CellCoord], int]], int] = lambda f: min(
             map(f, self.cells), default=0
         )
-        offset: tuple[int, int] = (-min_by(CellCoord.x), -min_by(CellCoord.y))
+        offset = IVec2(-min_by(CellCoord.x), -min_by(CellCoord.y))
         return self.offset(offset)
 
-    def centered_for(self, canvas: tuple[int, int]) -> "Pattern":
+    def centered_for(self, canvas: IVec2) -> "Pattern":
         normalized: Pattern = self.normalized()
-        dims: tuple[int, int] = normalized.dims()
-        offset: tuple[int, int] = (
-            (canvas[0] - dims[0]) // 2,
-            (canvas[1] - dims[1]) // 2,
-        )
+        dims = normalized.dims()
+        offset = (canvas - dims) // 2
         return normalized.offset(offset)
 
     def fill(self, maze: "Maze") -> None:
index 5e279c2a199412586d21d80a756102ead98ce913..c45d4b4601cbe45acc863ada98864321477b2ad5 100644 (file)
@@ -176,8 +176,8 @@ class CellCoord:
     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])
+    def offset(self, by: IVec2) -> "CellCoord":
+        return CellCoord(self.__x + by.x, self.__y + by.y)
 
     def x(self) -> int:
         return self.__x
index 0c90d684828121f0f5c8a86493484675c03fde45..64c965d6661f7d5412f48401de7d17041c00541e 100644 (file)
@@ -1,4 +1,5 @@
-from collections.abc import Generator
+from collections.abc import Generator, Iterable
+from ..config.config_parser import Color, Config, ColoredLine, ColorPair
 from amazeing.maze_display.layout import (
     BInt,
     Box,
@@ -13,6 +14,10 @@ from .backend import Backend, IVec2, BackendEvent, KeyboardInput
 import curses
 
 
+class BackendException(Exception):
+    pass
+
+
 def pad_write_safe(
     pad: curses.window, dst: IVec2, char: str, attrs: int
 ) -> None:
@@ -169,6 +174,67 @@ class ScrollablePad:
         self.move(by * IVec2.splat(-1))
 
 
+def extract_pairs(
+    config: Config, extra_colors: Iterable[ColorPair] = []
+) -> dict[ColorPair, int]:
+    all_tilemaps = (
+        config.tilemap_empty,
+        config.tilemap_full,
+        config.tilemap_background,
+    )
+    pairs = {
+        pair
+        for tilemap in all_tilemaps
+        for line in tilemap
+        for pair, _ in line
+    } | set(extra_colors)
+    colors = {color for pair in pairs for color in pair}
+    var_colors = {color for color in colors if isinstance(color, str)}
+    value_colors = {color for color in colors if not isinstance(color, str)}
+    color_lookup = {
+        "BLACK": curses.COLOR_BLACK,
+        "BLUE": curses.COLOR_BLUE,
+        "CYAN": curses.COLOR_CYAN,
+        "GREEN": curses.COLOR_GREEN,
+        "MAGENTA": curses.COLOR_MAGENTA,
+        "RED": curses.COLOR_RED,
+        "WHITE": curses.COLOR_WHITE,
+        "YELLOW": curses.COLOR_YELLOW,
+    }
+    available_colors = {i for i in range(0, curses.COLORS)}
+    res_colors: dict[Color, int] = {}
+    for color in var_colors:
+        if color not in color_lookup:
+            raise BackendException("Unknown color " + color + " in config")
+        res_colors[color] = color_lookup[color]
+        available_colors -= {color_lookup[color]}
+    if len(available_colors) < len(value_colors):
+        raise BackendException(
+            "Too many value color values in config: "
+            + f"maximum: {len(available_colors)}, "
+            + f"got: {len(value_colors)}"
+        )
+    for color, color_number in zip(value_colors, available_colors):
+        curses.init_color(
+            color_number, *(min(0, max(1000, channel)) for channel in color)
+        )
+        res_colors[color] = color_number
+    available_pairs = {i for i in range(1, curses.COLOR_PAIRS)}
+    if len(available_pairs) < len(pairs):
+        raise BackendException(
+            "Too many color pairs in config: "
+            + f"maximum: {len(available_pairs)}, "
+            + f"got: {len(pairs)}"
+        )
+    res_pairs = {}
+    for colors, pair_number in zip(pairs, available_pairs):
+        fg, bg = colors
+        curses.init_pair(pair_number, res_colors[fg], res_colors[bg])
+        res_pairs[colors] = pair_number
+
+    return res_pairs
+
+
 class TTYBackend(Backend[int]):
     def __init__(
         self, maze_dims: IVec2, wall_dim: IVec2, cell_dim: IVec2
index 826912347d8bec4cc0fb4d678e7485b2ef3ac82c..a8366b31d0628c9ff6041e1bd4236c1753be9b43 100644 (file)
@@ -1,5 +1,5 @@
-WIDTH=100
-HEIGHT=100
+WIDTH=250
+HEIGHT=250
 ENTRY=2,5
 #EXIT=100,100
 OUTPUT_FILE=test