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())
if config.seed is not None:
random.seed(config.seed)
-dims = (config.width, config.height)
+dims = IVec2(config.width, config.height)
maze = Maze(dims)
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)
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,
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)
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)
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]
from typing import Callable, Generator, Iterable, cast
+
+from amazeing.maze_display.backend import IVec2
from .maze_walls import (
MazeWall,
NetworkID,
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()
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] = {}
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):
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
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:
+from amazeing.maze_display.backend import IVec2
from .maze import Maze
from .maze_walls import CellCoord
from typing import Callable
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:
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
-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,
import curses
+class BackendException(Exception):
+ pass
+
+
def pad_write_safe(
pad: curses.window, dst: IVec2, char: str, attrs: int
) -> None:
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
-WIDTH=100
-HEIGHT=100
+WIDTH=250
+HEIGHT=250
ENTRY=2,5
#EXIT=100,100
OUTPUT_FILE=test