]> Untitled Git - axy/ft/a-maze-ing.git/commitdiff
Dirtiness tracking and config, not yet used
authorAxy <gilliardmarthey.axel@gmail.com>
Wed, 4 Mar 2026 17:52:38 +0000 (18:52 +0100)
committerAxy <gilliardmarthey.axel@gmail.com>
Wed, 4 Mar 2026 17:52:38 +0000 (18:52 +0100)
__main__.py
amazeing/config/config_parser.py
amazeing/maze_class/maze.py
example.conf

index e5b693dfe28cb48e0f50fd5265ac482099b9befe..f71a0d0dfa139288523e6ae5737a07f4eacf6771 100644 (file)
@@ -7,24 +7,19 @@ from amazeing import (
     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)
 
@@ -61,19 +56,32 @@ style_full = backend.add_style(
 
 
 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)
 
@@ -96,7 +104,7 @@ def poll_events(timeout_ms: int = -1) -> None:
 
 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()
index ab3b9bd18b628de39343eadd3a8c045a4a4d1441..2dc9cc6383dd7ff8b94bdc65e81e29959fdc9c3b 100644 (file)
@@ -1,5 +1,5 @@
 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,
@@ -9,6 +9,7 @@ from .parser_combinator import (
     cut,
     delimited,
     fold,
+    many,
     many_count,
     none_of,
     one_of,
@@ -32,7 +33,7 @@ def parse_int(s: str) -> ParseResult[int]:
     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)
 
 
@@ -40,11 +41,15 @@ def parse_comment(s: str) -> ParseResult[str]:
     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)
@@ -54,6 +59,76 @@ def parse_path(s: str) -> ParseResult[str]:
     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
 
@@ -71,9 +146,8 @@ class ConfigField[T](ABC):
     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()
 
@@ -83,7 +157,7 @@ class ConfigField[T](ABC):
         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:
@@ -120,7 +194,7 @@ def OptionalField[T](cls: Type[ConfigField[T]]) -> Type[ConfigField[T | None]]:
 def DefaultedField[T](
     cls: Type[ConfigField[T]], default: T
 ) -> Type[ConfigField[T]]:
-    class Inner(ConfigField[T]):
+    class Inner(cls):
         def __init__(
             self,
             name: str,
@@ -128,11 +202,51 @@ def DefaultedField[T](
         ) -> 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]:
@@ -140,10 +254,10 @@ def line_parser[T](
         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()
@@ -194,6 +308,11 @@ class Config:
     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
@@ -213,6 +332,20 @@ class Config:
                     "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)
index 70b84f89e51d88bd36b07ee0dc104a80ab145f4b..75cebe841736e88d6af686380d0bd4c5a8de4ab4 100644 (file)
@@ -12,9 +12,12 @@ class Maze:
     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)]
@@ -48,6 +51,7 @@ class Maze:
         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]:
@@ -84,26 +88,28 @@ class Maze:
         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:
@@ -130,6 +136,9 @@ class Maze:
     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()
@@ -174,3 +183,6 @@ class Maze:
             else []
         )
         return leaf_f(WallCoord.a_neighbours) + leaf_f(WallCoord.b_neighbours)
+
+    def clear_dirty(self) -> None:
+        self.__dirty = set()
index 4dd6d98498b0d5f8d6b6b1e3a82eda211df8bc5c..826912347d8bec4cc0fb4d678e7485b2ef3ac82c 100644 (file)
@@ -1,7 +1,19 @@
-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}# # "