maze_make_pacman,
maze_make_perfect,
)
-from time import sleep
-from sys import stderr, stdin
+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.backend import BackendEvent, CloseRequested, IVec2
-# from amazeing.maze_display.layout import example
+config = Config.parse(open("./example.conf").read())
-# example()
-# exit(0)
+if config.seed is not None:
+ random.seed(config.seed)
-# random.seed(42)
-
-# print(Config.parse(stdin.read()).__dict__)
-
-dims = (15, 15)
+dims = (config.width, config.height)
maze = Maze(dims)
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))
+ for wall in maze.walls_dirty():
+ if maze.get_wall(wall).is_full():
+ continue
+ for tile in wall.tile_coords():
+ backend.draw_tile(tile)
+ pass
def display_maze(maze: Maze) -> None:
clear_backend()
backend.set_style(style_full)
- for wall in maze.walls_full():
+
+ rewrites = {
+ wall for wall in maze.walls_dirty() if maze.get_wall(wall).is_full()
+ } | {
+ e
+ for wall in maze.walls_dirty()
+ for e in wall.neighbours()
+ if maze._check_coord(e) and maze.get_wall(e).is_full()
+ }
+
+ for wall in rewrites:
for pixel in wall.tile_coords():
backend.draw_tile(pixel)
+ maze.clear_dirty()
backend.present()
poll_events(0)
maze_make_perfect(maze, callback=display_maze)
maze_make_pacman(maze, walls_const, callback=display_maze)
-while False:
+while True:
maze_make_perfect(maze, callback=display_maze)
maze_make_pacman(maze, walls_const, callback=display_maze)
maze._rebuild()
from abc import ABC, abstractmethod
-from collections.abc import Callable
+from collections.abc import Callable, Generator
from typing import Any, Type
from .parser_combinator import (
ParseResult,
cut,
delimited,
fold,
+ many,
many_count,
none_of,
one_of,
return parser_map(int, recognize(many_count(ascii_digit, min_n=1)))(s)
-def parse_space(s: str) -> ParseResult[str]:
+def multispace0(s: str) -> ParseResult[str]:
return recognize(many_count(one_of(" \t")))(s)
return recognize(seq(tag("#"), many_count(none_of("\n"))))(s)
+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(
parse_int,
- delimited(parse_space, tag(","), parse_space),
+ delimited(multispace0, tag(","), multispace0),
),
parse_int,
)(s)
return recognize(many_count(none_of("\n"), min_n=1))(s)
+def char_range(a: str, b: str) -> str:
+ res = ""
+ for c in range(ord(a), ord(b) + 1):
+ res = res + chr(c)
+ return res
+
+
+def parse_varname(s: str) -> ParseResult[str]:
+ varstart = "_" + char_range("a", "z") + char_range("A", "Z")
+ vartail = varstart + char_range("0", "9")
+ return recognize(seq(one_of(varstart), many_count(one_of(vartail))))(s)
+
+
+type Color = tuple[int, int, int] | str
+type ColorPair = tuple[Color, Color]
+
+type ColoredLine = list[tuple[ColorPair, str]]
+
+
+def parse_color(s: str) -> ParseResult[Color]:
+ return alt(
+ parser_map(
+ lambda l: (l[0], l[1], l[2]),
+ many(parse_int, 3, 3, spaced(tag(","))),
+ ),
+ parse_varname,
+ )(s)
+
+
+def parse_color_pair(s: str) -> ParseResult[ColorPair]:
+ return parser_map(
+ lambda l: (l[0], l[1]),
+ many(parse_color, 2, 2, spaced(tag(":"))),
+ )(s)
+
+
+def parse_colored_line(
+ s: str,
+) -> ParseResult[ColoredLine]:
+ """
+ returns a list of a color pair variable associated with its string
+ """
+ color_prefix = delimited(
+ tag("{"), cut(spaced(parse_color_pair)), cut(tag("}"))
+ )
+ noncolor_str = fold(
+ alt(
+ none_of('\n{\\"'),
+ preceeded(
+ tag("\\"),
+ cut(
+ alt(
+ value("{", tag("{")),
+ value("\\", tag("\\")),
+ value('"', tag('"')),
+ )
+ ),
+ ),
+ ),
+ lambda a, b: a + b,
+ "",
+ )
+
+ return spaced(
+ delimited(
+ tag('"'), many(pair(color_prefix, cut(noncolor_str))), tag('"')
+ )
+ )(s)
+
+
class ConfigException(Exception):
pass
def default(self) -> T:
if self.__default is None:
raise ConfigException(
- "Value "
- + self.__name
- + " not provided, and no default value exists"
+ f"Value {self.__name} not provided, "
+ + "and no default value exists"
)
return self.__default()
if len(vals) == 1:
return vals[0]
raise ConfigException(
- "More than one definition of config field " + self.__name
+ f"More than one definition of config field {self.__name}"
)
def name(self) -> str:
def DefaultedField[T](
cls: Type[ConfigField[T]], default: T
) -> Type[ConfigField[T]]:
- class Inner(ConfigField[T]):
+ class Inner(cls):
def __init__(
self,
name: str,
) -> None:
super().__init__(name, default)
- parse = cls.parse
+ return Inner
+
+
+def DefaultedStrField[T](
+ cls: Type[ConfigField[T]], default_strs: list[str]
+) -> Type[ConfigField[T]]:
+ class Inner(cls):
+ def __init__(
+ self,
+ name: str,
+ default: Callable[[], T] | None = None,
+ ) -> None:
+ super().__init__(name, default)
+
+ def default(self) -> T:
+ acc = []
+ for s in default_strs:
+ res = self.parse(s)
+ if res is None or res[1] != "":
+ raise ConfigException(
+ "Failed to construct defaulted field " + self.name()
+ )
+ acc.append(res[0])
+ return self.merge(acc)
return Inner
+def ListParser[T](parser: Parser[T]) -> Type[ConfigField[list[T]]]:
+ class Inner(ConfigField[list[T]]):
+ 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 Inner
+
+
+ColoredLineField = ListParser(parse_colored_line)
+
+
def line_parser[T](
fields: dict[str, ConfigField[T]],
) -> Parser[tuple[str, T] | None]:
parser_map(lambda _: None, parse_comment),
*(
preceeded(
- seq(tag(name), parse_space, tag("="), parse_space),
+ seq(tag(name), multispace0, tag("="), multispace0),
parser_map(
(lambda name: lambda res: (name, res))(name),
- cut(field.parse),
+ cut(terminated(field.parse, multispace0)),
),
)
for name, field in fields.items()
screensaver: bool
visual: bool
interactive: bool
+ tilemap_wall_size: tuple[int, int]
+ tilemap_cell_size: tuple[int, int]
+ tilemap_full: list[ColoredLine]
+ tilemap_empty: list[ColoredLine]
+ tilemap_background: list[ColoredLine]
def __init__(self) -> None:
pass
"SCREENSAVER": DefaultedField(BoolField, False),
"VISUAL": DefaultedField(BoolField, False),
"INTERACTIVE": DefaultedField(BoolField, False),
+ "TILEMAP_WALL_SIZE": DefaultedField(CoordField, (2, 1)),
+ "TILEMAP_CELL_SIZE": DefaultedField(CoordField, (2, 1)),
+ "TILEMAP_FULL": DefaultedStrField(
+ ColoredLineField,
+ ['"{WHITE:WHITE} "', '"{WHITE:WHITE} "'],
+ ),
+ "TILEMAP_EMPTY": DefaultedStrField(
+ ColoredLineField,
+ ['"{BLACK:BLACK} "', '"{BLACK:BLACK} "'],
+ ),
+ "TILEMAP_BACKGROUND": DefaultedStrField(
+ ColoredLineField,
+ ['"{BLACK:BLACK} "', '"{BLACK:BLACK} "'],
+ ),
}
)
)(s)
def __init__(self, dims: tuple[int, int]) -> None:
self.__width: int = dims[0]
self.__height: int = dims[1]
+ self.__dirty: set[WallCoord] = set()
self._clear()
def _clear(self) -> None:
+ if hasattr(self, "horizontal") and hasattr(self, "vertical"):
+ self.__dirty ^= {wall for wall in self.walls_full()}
# list of lines
self.horizontal: list[list[MazeWall]] = [
[MazeWall() for _ in range(0, self.__width)]
wall = self.get_wall(coord)
if wall.network_id is not None:
self.networks[wall.network_id].remove_wall(coord)
+ self.__dirty ^= {coord}
wall.network_id = None
def all_walls(self) -> Generator[WallCoord]:
network.add_wall(id)
self.networks[network_id] = network
- def fill_wall(self, id: WallCoord) -> None:
- wall = self.get_wall(id)
+ def fill_wall(self, coord: WallCoord) -> None:
+ wall = self.get_wall(coord)
if wall.is_full():
return
+ self.__dirty ^= {coord}
+
networks = {
cast(NetworkID, neighbour.network_id)
- for neighbour in self.get_neighbours(id)
+ for neighbour in self.get_neighbours(coord)
if neighbour.is_full()
}
if len(networks) == 0:
- return self._fill_wall_alone(id, wall)
+ return self._fill_wall_alone(coord, wall)
dest_id = max(networks, key=lambda n: self.networks[n].size())
dest = self.networks[dest_id]
wall.network_id = dest_id
- dest.add_wall(id)
+ dest.add_wall(coord)
for to_merge in filter(lambda n: n != dest_id, networks):
for curr in self.networks[to_merge].walls:
def walls_full(self) -> Iterable[WallCoord]:
return filter(lambda w: self.get_wall(w).is_full(), self.all_walls())
+ def walls_dirty(self) -> Iterable[WallCoord]:
+ return self.__dirty
+
def walls_empty(self) -> Iterable[WallCoord]:
return filter(
lambda w: not self.get_wall(w).is_full(), self.all_walls()
else []
)
return leaf_f(WallCoord.a_neighbours) + leaf_f(WallCoord.b_neighbours)
+
+ def clear_dirty(self) -> None:
+ self.__dirty = set()
-WIDTH=250
+WIDTH=100
HEIGHT=100
ENTRY=2,5
#EXIT=100,100
OUTPUT_FILE=test
PERFECT=False
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_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="{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}# # "