from amazeing.config.config_parser import Config
from amazeing.maze_class.maze_walls import CellCoord
-from amazeing.maze_display.TTYdisplay import TileMaps, extract_pairs
+from amazeing.maze_display.TTYdisplay import TileCycle, TileMaps, extract_pairs
from amazeing.maze_display.backend import CloseRequested, IVec2
config = Config.parse(open("./example.conf").read())
backend = TTYBackend(dims, config.tilemap_wall_size, config.tilemap_cell_size)
pair_map = extract_pairs(config)
tilemaps = TileMaps(config, pair_map, backend)
-backend.set_filler(tilemaps.filler)
+filler = TileCycle(tilemaps.filler, backend.set_filler)
-backend.set_style(tilemaps.empty)
+backend.set_style(tilemaps.empty[0])
for wall in maze.all_walls():
for tile in wall.tile_coords():
backend.draw_tile(tile)
def clear_backend() -> None:
- backend.set_style(tilemaps.empty)
+ backend.set_style(tilemaps.empty[0])
for wall in maze.walls_dirty():
if maze.get_wall(wall).is_full():
continue
def display_maze(maze: Maze) -> None:
clear_backend()
- backend.set_style(tilemaps.full)
+ backend.set_style(tilemaps.full[0])
rewrites = {
wall for wall in maze.walls_dirty() if maze.get_wall(wall).is_full()
return
if isinstance(event, CloseRequested) or event.sym == "q":
exit(0)
+ if event.sym == "c":
+ filler.cycle()
+ else:
+ continue
+ backend.present()
maze_make_perfect(maze, callback=display_maze)
many,
many_count,
none_of,
+ null_parser,
one_of,
pair,
parser_complete,
type ColorPair = tuple[Color, Color]
type ColoredLine = list[tuple[ColorPair, str]]
+type Grouped[T] = tuple[int, T]
def parse_color(s: str) -> ParseResult[Color]:
)(s)
+def grouped_parser[T](parser: Parser[T]) -> Parser[Grouped[T]]:
+ return pair(alt(spaced(parse_int), value(0, null_parser)), parser)
+
+
class ConfigException(Exception):
pass
-class ConfigField[T](ABC):
+class ConfigField[T, U = T](ABC):
def __init__(
- self, name: str, default: Callable[[], T] | None = None
+ self,
+ name: str,
) -> None:
self.__name = name
- self.__default = default
@abstractmethod
def parse(self, s: str) -> ParseResult[T]: ...
- def default(self) -> T:
- if self.__default is None:
- raise ConfigException(
- f"Value {self.__name} not provided, "
- + "and no default value exists"
- )
- return self.__default()
+ def default(self) -> U:
+ raise ConfigException(
+ f"Value {self.__name} not provided, "
+ + "and no default value exists"
+ )
+
+ @abstractmethod
+ def merge(self, vals: list[T]) -> U: ...
+ def name(self) -> str:
+ return self.__name
+
+
+class SimpleField[T](ConfigField[T, T]):
def merge(self, vals: list[T]) -> T:
if len(vals) == 0:
return self.default()
f"More than one definition of config field {self.__name}"
)
- def name(self) -> str:
- return self.__name
-
-class IntField(ConfigField[int]):
+class IntField(SimpleField[int]):
def parse(self, s: str) -> ParseResult[int]:
return parse_int(s)
-class BoolField(ConfigField[bool]):
+class BoolField(SimpleField[bool]):
def parse(self, s: str) -> ParseResult[bool]:
return parse_bool(s)
-class CoordField(ConfigField[IVec2]):
+class CoordField(SimpleField[IVec2]):
def parse(self, s: str) -> ParseResult[IVec2]:
return parse_coord(s)
-class PathField(ConfigField[str]):
+class PathField(SimpleField[str]):
def parse(self, s: str) -> ParseResult[str]:
return parse_path(s)
-def OptionalField[T](cls: Type[ConfigField[T]]) -> Type[ConfigField[T | None]]:
- class Inner(ConfigField[T | None]):
- parse = cls.parse
-
- return DefaultedField(Inner, None)
+def OptionalField[T, U](
+ cls: Type[ConfigField[T, U]],
+) -> Type[ConfigField[T, U | None]]:
+ return DefaultedField(cls, None)
-def DefaultedField[T](
- cls: Type[ConfigField[T]], default: T
-) -> Type[ConfigField[T]]:
+def DefaultedField[T, U](
+ cls: Type[ConfigField[T, U]], default: U
+) -> Type[ConfigField[T, U]]:
class Inner(cls): # type: ignore
- def __init__(
- self,
- name: str,
- default: Callable[[], T] = lambda: default,
- ) -> None:
- super().__init__(name, default)
+ def default(self) -> U:
+ return default
return Inner
-def DefaultedStrField[T](
- cls: Type[ConfigField[T]], default_strs: list[str]
-) -> Type[ConfigField[T]]:
+def DefaultedStrField[T, U](
+ cls: Type[ConfigField[T, U]], default_strs: list[str]
+) -> Type[ConfigField[T, U]]:
class Inner(cls): # type: ignore
- def __init__(
- self,
- name: str,
- default: Callable[[], T] | None = None,
- ) -> None:
- super().__init__(name, default)
-
- def default(self) -> T:
+ def default(self) -> U:
acc = []
for s in default_strs:
res = self.parse(s)
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
+ def __init__(self, name: str) -> None:
+ self.__inner = cls(name)
+ super().__init__(name)
+
+ def parse(self, s: str) -> ParseResult[T]:
+ return self.__inner.parse(s)
+
+ def default(self) -> V:
+ return mapping(self.__inner.default())
+
+ def merge(self, vals: list[T]) -> V:
+ return mapping(self.__inner.merge(vals))
+
+ return Inner
+
+
def ListParser[T](parser: Parser[T]) -> Type[ConfigField[list[T]]]:
class Inner(ConfigField[list[T]]):
- def __init__(
- self, name: str, default: Callable[[], list[T]] | None = lambda: []
- ) -> None:
- super().__init__(name, default)
+ def __init__(self, name: str) -> None:
+ super().__init__(name)
def parse(self, s: str) -> ParseResult[list[T]]:
return parser_map(lambda e: [e], parser)(s)
+ def default(self) -> list[T]:
+ return []
+
def merge(self, vals: list[list[T]]) -> list[T]:
return (
[e for val in vals for e in val]
return Inner
-ColoredLineField = ListParser(parse_colored_line)
+def map_grouped[T](vals: list[Grouped[T]]) -> list[list[T]]:
+ res: dict[int, list[T]] = {}
+ for group, elem in vals:
+ if group not in res:
+ res[group] = []
+ res[group].append(elem)
+ return list(res.values())
+
+
+ColoredLineField = MappedField(
+ ListParser(grouped_parser(parse_colored_line)), map_grouped
+)
PatternField = ListParser(parse_str_line)
interactive: bool
tilemap_wall_size: IVec2
tilemap_cell_size: IVec2
- tilemap_full: list[ColoredLine]
- tilemap_empty: list[ColoredLine]
+ tilemap_full: list[list[ColoredLine]]
+ tilemap_empty: list[list[ColoredLine]]
tilemap_background_size: IVec2
- tilemap_background: list[ColoredLine]
+ tilemap_background: list[list[ColoredLine]]
maze_pattern: list[str]
def __init__(self) -> None:
def extract_pairs(
config: Config, extra_colors: Iterable[ColorPair] = []
) -> dict[ColorPair, int]:
- all_tilemaps = (
- config.tilemap_empty,
- config.tilemap_full,
- config.tilemap_background,
- )
+ all_tilemaps = [
+ e
+ for tilemaps in (
+ config.tilemap_empty,
+ config.tilemap_full,
+ config.tilemap_background,
+ )
+ for e in tilemaps
+ ]
pairs = {
pair
for tilemap in all_tilemaps
dim,
)
- self.empty: int = backend.add_style(
- new_tilemap(config.tilemap_empty, mazetile_dims)
- )
- self.full: int = backend.add_style(
- new_tilemap(config.tilemap_full, mazetile_dims)
- )
- self.filler: int = backend.add_style(
- new_tilemap(
- config.tilemap_background, config.tilemap_background_size
+ def add_style(tilemap, size=mazetile_dims):
+ return backend.add_style(new_tilemap(tilemap, size))
+
+ self.empty: list[int] = list(map(add_style, config.tilemap_empty))
+ self.full: list[int] = list(map(add_style, config.tilemap_full))
+ self.filler: list[int] = list(
+ map(
+ lambda e: add_style(e, config.tilemap_background_size),
+ config.tilemap_background,
)
)
+class TileCycle:
+ def __init__[T](
+ self, styles: list[T], cb: Callable[[T], None], i=0
+ ) -> None:
+ self.__styles = styles
+ self.__cb = cb
+ self.__i = i
+ cb(styles[i])
+
+ def cycle(self, by: int = 1):
+ self.__i += by
+ self.__i %= len(self.__styles)
+ self.__cb(self.__styles[self.__i])
+
+
class TTYBackend(Backend[int]):
def __init__(
self, maze_dims: IVec2, wall_dim: IVec2, cell_dim: IVec2
curses.endwin()
def set_filler(self, style: int) -> None:
+ if self.__filler == style:
+ return
self.__filler = style
for box in self.__filler_boxes:
box.mark_dirty()