maze.outline()
-pattern = Pattern(Pattern.FT_PATTERN).centered_for(dims)
+pattern = Pattern(config.maze_pattern).centered_for(dims)
pattern.fill(maze)
walls_const = set(maze.walls_full())
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)
backend.set_style(tilemaps.empty)
for wall in maze.all_walls():
"""
returns a list of a color pair variable associated with its string
"""
+
color_prefix = delimited(
tag("{"), cut(spaced(parse_color_pair)), cut(tag("}"))
)
),
),
lambda a, b: a + b,
- "",
+ lambda: "",
)
return spaced(
)(s)
+def parse_str_line(s: str) -> ParseResult[str]:
+ return spaced(
+ delimited(
+ tag('"'),
+ recognize(
+ many_count(
+ none_of('"\n'),
+ )
+ ),
+ tag('"'),
+ )
+ )(s)
+
+
class ConfigException(Exception):
pass
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 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 l in vals for e in l]
+ return (
+ [e for l in vals for e in l]
+ if len(vals) > 0
+ else self.default()
+ )
return Inner
ColoredLineField = ListParser(parse_colored_line)
+PatternField = ListParser(parse_str_line)
+
def line_parser[T](
fields: dict[str, ConfigField[T]],
fold(
parse_line,
fold_fn,
- {name: [] for name in fields.keys()},
+ lambda: {name: [] for name in fields.keys()},
),
)(s)
tilemap_cell_size: IVec2
tilemap_full: list[ColoredLine]
tilemap_empty: list[ColoredLine]
+ tilemap_background_size: IVec2
tilemap_background: list[ColoredLine]
+ maze_pattern: list[str]
def __init__(self) -> None:
pass
@staticmethod
def parse(s: str) -> "Config":
+ from amazeing.maze_class import maze_pattern
+
fields = parser_complete(
fields_parser(
{
ColoredLineField,
['"{BLACK:BLACK} "', '"{BLACK:BLACK} "'],
),
+ "TILEMAP_BACKGROUND_SIZE": DefaultedField(
+ CoordField, IVec2(4, 2)
+ ),
"TILEMAP_BACKGROUND": DefaultedStrField(
ColoredLineField,
['"{BLACK:BLACK} "', '"{BLACK:BLACK} "'],
),
+ "MAZE_PATTERN": DefaultedField(
+ PatternField, maze_pattern.Pattern.FT_PATTERN
+ ),
}
)
)(s)
def fold[T, R](
p: Parser[T],
f: Callable[[R, T], R],
- acc: R,
+ acc_init: Callable[[], R],
min_n: int = 0,
max_n: int | None = None,
sep: Parser[Any] = null_parser,
) -> Parser[R]:
# no clean way to do this with lambdas i could figure out :<
def inner(s: str) -> ParseResult[R]:
- nonlocal acc
+ acc = acc_init()
count: int = 0
curr_p: Parser[T] = p
while max_n is None or count < max_n:
sep: Parser[Any] = null_parser,
) -> Parser[list[T]]:
return fold(
- parser_map(lambda e: [e], p), list.__add__, [], min_n, max_n, sep
+ parser_map(lambda e: [e], p),
+ list.__add__,
+ lambda: [],
+ min_n,
+ max_n,
+ sep,
)
max_n: int | None = None,
sep: Parser[Any] = null_parser,
) -> Parser[int]:
- return fold(value(1, p), int.__add__, 0, min_n, max_n, sep)
+ return fold(value(1, p), int.__add__, lambda: 0, min_n, max_n, sep)
def seq[T](*parsers: Parser[T]) -> Parser[str]:
-from collections.abc import Generator, Iterable
+from collections.abc import Callable, Generator, Iterable
+from sys import stderr
from ..config.config_parser import Color, Config, ColoredLine, ColorPair
from amazeing.maze_display.layout import (
BInt,
HBox,
VBox,
layout_fair,
+ layout_priority,
+ layout_sort_chunked,
+ layout_sort_shuffled,
+ layout_split,
vpad_box,
hpad_box,
)
) -> None:
if size.x <= 0 or size.y <= 0:
return
+ print(src, dst, size, window.getmaxyx(), file=stderr)
self.__pad.overwrite(
window, *src.yx(), *dst.yx(), *(dst + size - IVec2.splat(1)).yx()
)
for y_size, y_offset in size_offset_iter(src.y, size.y, dims.y):
sub_size = IVec2(x_size, y_size)
offset = IVec2(x_offset, y_offset)
- self.blit(src + offset, dst + offset, sub_size, window)
+ self.blit(
+ (src + offset) % dims, dst + offset, sub_size, window
+ )
class SubTile(Tile):
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:
+ def draw_at(self, at: IVec2, idx: int, window: curses.window) -> None:
self.__tiles[idx].blit(
- self.src_coord(pos),
- self.dst_coord(pos),
- self.tile_size(pos),
+ self.src_coord(at),
+ self.dst_coord(at),
+ self.tile_size(at),
window,
)
+ def draw_at_wrapping(
+ self,
+ start: IVec2,
+ at: IVec2,
+ into: IVec2,
+ idx: int,
+ window: curses.window,
+ ) -> None:
+ self.__tiles[idx].blit_wrapping(start, at, into, window)
+
class ScrollablePad:
def __init__(
IVec2.with_op(max)(self.__pos, dims - self.dims()), IVec2.splat(0)
)
- def refresh(self, at: IVec2, into: IVec2) -> None:
+ def present(self, at: IVec2, into: IVec2, window: curses.window) -> None:
if self.constrained:
self.clamp(into)
return
draw_start = at + win_start
draw_end = draw_start + draw_dim - IVec2.splat(1)
- self.pad.refresh(
+ self.pad.overwrite(
+ window,
*pad_start.yx(),
*draw_start.yx(),
*draw_end.yx(),
) -> None:
mazetile_dims = config.tilemap_wall_size + config.tilemap_cell_size
- def new_tilemap(lines: list[ColoredLine]) -> Tile:
+ def new_tilemap(lines: list[ColoredLine], dim: IVec2) -> Tile:
return Tile(
[
[(s, pair_map[color_pair]) for color_pair, s in line]
for line in lines
],
- mazetile_dims,
+ dim,
)
- self.empty: int = backend.add_style(new_tilemap(config.tilemap_empty))
- self.full: int = backend.add_style(new_tilemap(config.tilemap_full))
+ 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
+ )
+ )
class TTYBackend(Backend[int]):
curses.curs_set(0)
self.__screen.keypad(True)
+ self.__scratch: curses.window = curses.newpad(1, 1)
self.__pad: ScrollablePad = ScrollablePad(dims)
self.__dims = maze_dims
maze_box = FBox(
IVec2(BInt(dims.x), BInt(dims.y)),
- self.__pad.refresh,
+ lambda at, into: self.__pad.present(at, into, self.__scratch),
+ )
+ filler_box = FBox(
+ IVec2(BInt(0, True), BInt(0, True)),
+ lambda at, into: (
+ None
+ if self.__filler is None
+ else self.__tilemap.draw_at_wrapping(
+ at, at, into, self.__filler, self.__scratch
+ )
+ ),
)
- self.__layout: Box = VBox.noassoc(
- layout_fair,
+ f: Callable[[int], int] = lambda e: e
+ layout = layout_split(
+ layout_fair, layout_sort_chunked(layout_fair, layout_priority, f)
+ )
+ self.__layout: Box = VBox(
+ layout,
[
- vpad_box(),
- HBox.noassoc(layout_fair, [hpad_box(), maze_box, hpad_box()]),
- vpad_box(),
+ (filler_box, 0),
+ (
+ HBox(
+ layout,
+ [(filler_box, 0), (maze_box, 1), (filler_box, 0)],
+ ),
+ 1,
+ ),
+ (filler_box, 0),
],
)
self.__resize: bool = False
+ self.__filler: None | int = None
+
def __del__(self):
curses.curs_set(1)
curses.nocbreak()
curses.echo()
curses.endwin()
+ def set_filler(self, style: int) -> None:
+ self.__filler = style
+
def add_style(self, style: Tile) -> int:
return self.__tilemap.add_tile(style)
self.__screen.erase()
self.__screen.refresh()
y, x = self.__screen.getmaxyx()
+ self.__scratch.resize(y, x)
self.__layout.laid_out(IVec2(0, 0), IVec2(x, y))
+ self.__scratch.overwrite(self.__screen)
def event(self, timeout_ms: int = -1) -> BackendEvent | None:
self.__screen.timeout(timeout_ms)
class IVec2[T = int]:
+ def copy(self, inner_copy: Callable[[T], T] = lambda e: e) -> "IVec2[T]":
+ return IVec2(inner_copy(self.x), inner_copy(self.y))
+
def __init__(self, x: T, y: T) -> None:
self.x: T = x
self.y: T = y
def splat(n: T) -> "IVec2[T]":
return IVec2(n, n)
+ def __repr__(self) -> str:
+ return f"{self.x, self.y}"
+
@staticmethod
def with_op(
op: Callable[[T, T], T],
from abc import ABC, abstractmethod
from collections.abc import Callable
+from sys import stderr
from .backend import IVec2
self.val: int = val
self.has_flex: bool = has_flex
+ def __repr__(self) -> str:
+ return f"{self.val}" + (" flexible" if self.has_flex else "")
+
+ @staticmethod
+ def vector_sum(elems: list["BInt"]) -> "BInt":
+ res = BInt(
+ sum(map(lambda e: e.val, elems)),
+ any(map(lambda e: e.has_flex, elems)),
+ )
+ return res
+
+ @staticmethod
+ def vector_max(elems: list["BInt"]) -> "BInt":
+ res = BInt(
+ max(map(lambda e: e.val, elems), default=0),
+ any(map(lambda e: e.has_flex, elems)),
+ )
+ return res
+
type BVec2 = IVec2[BInt]
type Layout[T] = Callable[[list[tuple[BInt, T]], int], list[int]]
-def layout_priority(sizes: list[BInt], available: int) -> list[int]:
+def layout_priority[T](
+ sizes: list[tuple[BInt, T]], available: int
+) -> list[int]:
res = []
- for size in sizes:
+ for size, _ in sizes:
size_scaled = min(size.val, available)
res.append(size_scaled)
available -= size_scaled
+ for i, (size, _) in enumerate(sizes):
+ if size.has_flex:
+ res[i] += available
+ break
return res
def layout_fair[T](sizes: list[tuple[BInt, T]], available: int) -> list[int]:
res = [0 for _ in sizes]
count = len(sizes)
- for idx, size in sorted(enumerate(sizes), key=lambda e: e[1][0].val):
- size_scaled = min(size[0].val, rdiv(available, count))
+ for idx, (size, _) in sorted(enumerate(sizes), key=lambda e: e[1][0].val):
+ size_scaled = min(size.val, rdiv(available, count))
res[idx] += size_scaled
available -= size_scaled
count -= 1
- count = sum(1 for e in sizes if e[0].has_flex)
- for idx, size in enumerate(sizes):
- if not size[0].has_flex:
+ count = sum(1 for (e, _) in sizes if e.has_flex)
+ for idx, (size, _) in enumerate(sizes):
+ if not size.has_flex:
continue
size_scaled = rdiv(available, count)
res[idx] += size_scaled
return res
+def layout_split[T](sized: Layout[T], flexed: Layout[T]) -> Layout[T]:
+ def inner(sizes: list[tuple[BInt, T]], available: int) -> list[int]:
+ flexes = [(BInt(0, e[0].has_flex), e[1]) for e in sizes]
+ sizes = [(BInt(e[0].val), e[1]) for e in sizes]
+ res_sizes = sized(sizes, available)
+ res_flexes = flexed(flexes, available - sum(res_sizes))
+ return [a + b for a, b in zip(res_sizes, res_flexes)]
+
+ return inner
+
+
+def layout_sort_shuffled[T](
+ init: Layout[T], extract: Callable[[T], int]
+) -> Layout[T]:
+ def inner(sizes: list[tuple[BInt, T]], available: int) -> list[int]:
+ mapping = [(i, extract(assoc)) for i, (_, assoc) in enumerate(sizes)]
+ mapping.sort(key=lambda e: e[1])
+ sizes = [e for e in sizes]
+ sizes.sort(key=lambda e: extract(e[1]))
+ res_init = init(sizes, available)
+ res = [0 for _ in res_init]
+ for src, (dst, _) in enumerate(mapping):
+ res[dst] = res_init[src]
+ return res
+
+ return inner
+
+
+def layout_mapped[T, U](init: Layout[T], f: Callable[[U], T]) -> Layout[U]:
+ return lambda sizes, available: init(
+ list(map(lambda e: (e[0], f(e[1])), sizes)), available
+ )
+
+
+def layout_sort_chunked[T](
+ per_chunk: Layout[T],
+ chunk_layout: Layout[list[tuple[BInt, T]]],
+ extract: Callable[[T], int],
+) -> Layout[T]:
+ def layout_chunk_seq(
+ sizes: list[tuple[BInt, T]], available: int
+ ) -> list[int]:
+ chunks: list[tuple[BInt, list[tuple[BInt, T]]]] = []
+ i = 0
+ curr_chunk = None
+
+ def try_add_curr() -> None:
+ nonlocal curr_chunk
+ if curr_chunk is None:
+ return
+ chunk = curr_chunk[0]
+ chunks.append(
+ (
+ BInt.vector_sum([e[0] for e in chunk]),
+ chunk,
+ )
+ )
+ curr_chunk = None
+
+ while i < len(sizes):
+ val = sizes[i]
+ _, assoc = val
+ extracted = extract(assoc)
+ if curr_chunk is None:
+ curr_chunk = ([val], extracted)
+ else:
+ if extracted == curr_chunk[1]:
+ curr_chunk[0].append(val)
+ else:
+ try_add_curr()
+ continue
+ i += 1
+ try_add_curr()
+
+ chunk_sizes = chunk_layout(chunks, available)
+ res = [
+ size
+ for (_, chunk), chunk_layout_size in zip(chunks, chunk_sizes)
+ for size in per_chunk(chunk, chunk_layout_size)
+ ]
+ return res
+
+ return layout_sort_shuffled(layout_chunk_seq, extract)
+
+
class Box(ABC):
@abstractmethod
def dims(self) -> BVec2: ...
def dims(self) -> BVec2:
dims = [box.dims() for box, _ in self.boxes]
return IVec2(
- BInt(
- max(map(lambda e: e.x.val, dims)),
- any(map(lambda e: e.x.has_flex, dims)),
- ),
- BInt(
- sum(map(lambda e: e.y.val, dims)),
- any(map(lambda e: e.y.has_flex, dims)),
- ),
+ BInt.vector_max([e.x for e in dims]),
+ BInt.vector_sum([e.y for e in dims]),
)
def laid_out(self, at: IVec2, into: IVec2) -> None:
widths = [(get_width(dim.x), assoc) for dim, assoc in dims]
for height, (width, _), (box, _) in zip(heights, widths, self.boxes):
- box.laid_out(at, IVec2(width, height))
+ # that copy cost me half an hour, thank you pass by reference
+ # rust would have prevented that :D
+ box.laid_out(at.copy(), IVec2(width, height))
at.y += height
def dims(self) -> BVec2:
dims = [box.dims() for box, _ in self.boxes]
return IVec2(
- BInt(
- sum(map(lambda e: e.x.val, dims)),
- any(map(lambda e: e.x.has_flex, dims)),
- ),
- BInt(
- max(map(lambda e: e.y.val, dims)),
- any(map(lambda e: e.y.has_flex, dims)),
- ),
+ BInt.vector_sum([e.x for e in dims]),
+ BInt.vector_max([e.y for e in dims]),
)
def laid_out(self, at: IVec2, into: IVec2) -> None:
self.__cb(at, into)
-def vpad_box[T](min_pad: int = 0) -> FBox:
- return FBox(IVec2(BInt(0), BInt(min_pad, True)), lambda _at, _into: None)
+def vpad_box(min_pad: int = 0, cb=lambda _at, _into: None) -> FBox:
+ return FBox(IVec2(BInt(0), BInt(min_pad, True)), cb)
-def hpad_box(min_pad: int = 0) -> FBox:
- return FBox(IVec2(BInt(min_pad, True), BInt(0)), lambda _at, _into: None)
+def hpad_box(min_pad: int = 0, cb=lambda _at, _into: None) -> FBox:
+ return FBox(IVec2(BInt(min_pad, True), BInt(0)), cb)
def print_cb(at: IVec2, into: IVec2) -> None:
def example() -> None:
- a = FBox(IVec2(BInt(8, False), BInt(4, True)), print_cb)
- b = FBox(IVec2(BInt(4, False), BInt(8, False)), print_cb)
- c = VBox.noassoc(layout_fair, [a, b])
+ a = FBox(IVec2(BInt(8, False), BInt(4, False)), print_cb)
+ c = HBox.noassoc(
+ layout_fair,
+ [
+ hpad_box(),
+ VBox.noassoc(layout_fair, [vpad_box(), a, vpad_box()]),
+ hpad_box(),
+ ],
+ )
c.laid_out(IVec2(0, 0), IVec2(3, 4))
c.laid_out(IVec2(0, 0), IVec2(8, 30))
c.laid_out(IVec2(0, 0), IVec2(12, 30))
-WIDTH=25
-HEIGHT=25
+WIDTH=50
+HEIGHT=50
ENTRY=2,5
#EXIT=100,100
OUTPUT_FILE=test
SEED=111
TILEMAP_WALL_SIZE=2,1
TILEMAP_CELL_SIZE=4,2
-TILEMAP_FULL="{100,100,100:1000,1000,1000}######"
-TILEMAP_FULL="{100,100,100:1000,1000,1000}######"
-TILEMAP_FULL="{100,100,100:1000,1000,1000}######"
+TILEMAP_FULL="{1000,100,100:1000,1000,1000}###{1000,1000,1000:1000,1000,1000}###"
+TILEMAP_FULL="{100,1000,100:1000,1000,1000}###{1000,1000,1000:1000,1000,1000}###"
+TILEMAP_FULL="{100,100,1000:1000,1000,1000}###{1000,1000,1000:1000,1000,1000}###"
TILEMAP_EMPTY="{0,0,0:0,0,0} "
TILEMAP_EMPTY="{0,0,0:0,0,0} "
TILEMAP_EMPTY="{0,0,0:0,0,0} "
+TILEMAP_BACKGROUND_SIZE=4,4
TILEMAP_BACKGROUND="{100,100,100:0,0,0}# "
TILEMAP_BACKGROUND="{100,100,100:0,0,0}### "
TILEMAP_BACKGROUND="{100,100,100:0,0,0} # "
TILEMAP_BACKGROUND="{100,100,100:0,0,0}# # "
+MAZE_PATTERN=" # # "
+MAZE_PATTERN=" # # "
+MAZE_PATTERN=" # "
+MAZE_PATTERN=" "
+MAZE_PATTERN=" # # "
+MAZE_PATTERN=" "
+MAZE_PATTERN="# # #"
+MAZE_PATTERN=" ## ## "