From: Axy Date: Fri, 6 Feb 2026 16:42:35 +0000 (+0100) Subject: impass removal X-Git-Url: https://git.uwuaxy.net/sitemap.xml?a=commitdiff_plain;h=69dd3295f828a91a1ef40400ad75916206cf0265;p=axy%2Fft%2Fa-maze-ing.git impass removal --- diff --git a/__main__.py b/__main__.py index 4ccbd6f..949c77f 100644 --- a/__main__.py +++ b/__main__.py @@ -1,26 +1,60 @@ from amazeing import Maze, TTYBackend, Pattern import random +from time import sleep +# random.seed(42) -dims = (30, 30) +dims = (25, 25) -maze = Maze(*dims) +maze = Maze(dims) maze.outline() pattern = Pattern(Pattern.FT_PATTERN).centered_for(dims) pattern.fill(maze) +walls_const = set(maze.walls_full()) + + +def display_maze(maze: Maze) -> None: + backend = TTYBackend(*dims, style="\x1b[48;5;240m \x1b[0m") + backend.set_style("\x1b[48;5;248m \x1b[0m") + for wall in maze.walls_full(): + for pixel in wall.pixel_coords(): + backend.draw_pixel(pixel) + backend.present() + sleep(0.05) + + empty = list(maze.walls_empty()) random.shuffle(empty) for wall in empty: - if maze.wall_bisects(wall): # or maze.wall_cuts_cycle(wall): + if maze.wall_bisects(wall): continue maze.fill_wall(wall) + # display_maze(maze) -backend = TTYBackend(*dims, style="\x1b[48;5;240m \x1b[0m") -backend.set_style("\x1b[48;5;248m \x1b[0m") -for wall in maze.walls_full(): - for pixel in wall.pixel_coords(): - backend.draw_pixel(pixel) -backend.present() +iterations = 0 +for _ in range(0, iterations): + walls = [wall for wall in maze.walls_full() if wall not in walls_const] + random.shuffle(walls) + for wall in walls: + if not maze.wall_cuts_cycle(wall): + continue + if maze.wall_leaf_neighbours(wall): + continue + maze._remove_wall(wall) + # display_maze(maze) + walls = [wall for wall in maze.walls_full() if wall not in walls_const] + random.shuffle(walls) + for wall in walls: + if not maze.wall_cuts_cycle(wall): + continue + leaf_neighbours = maze.wall_leaf_neighbours(wall) + if len(leaf_neighbours) == 0: + continue + maze._remove_wall(wall) + maze.fill_wall(random.choice(leaf_neighbours)) + # display_maze(maze) +maze._rebuild() +display_maze(maze) diff --git a/amazeing/maze.py b/amazeing/maze.py index 9777e4b..e0c1174 100644 --- a/amazeing/maze.py +++ b/amazeing/maze.py @@ -1,5 +1,6 @@ from enum import Enum, auto from typing import Callable, Generator, Iterable, Optional, cast +import random from amazeing.display import PixelCoord @@ -36,6 +37,19 @@ class WallCoord: self.a: int = a self.b: int = b + def __members(self) -> tuple[int, int, Orientation]: + return (self.a, self.b, self.orientation) + + def __eq__(self, value: object, /) -> bool: + return ( + self.__members() == cast(WallCoord, value).__members() + if type(self) is type(value) + else False + ) + + def __hash__(self) -> int: + return hash(self.__members()) + def a_neighbours(self) -> list["WallCoord"]: return [ WallCoord(self.orientation.opposite(), self.b, self.a - 1), @@ -66,8 +80,14 @@ class WallCoord: def neighbour_cells(self) -> list["CellCoord"]: if self.orientation == Orientation.HORIZONTAL: - return [CellCoord(self.b, self.a), CellCoord(self.b, self.a - 1)] - return [CellCoord(self.a, self.b), CellCoord(self.a - 1, self.b)] + return [ + CellCoord(self.b, self.a), + CellCoord(self.b, self.a - 1), + ] + return [ + CellCoord(self.a, self.b), + CellCoord(self.a - 1, self.b), + ] class CellCoord: @@ -170,49 +190,75 @@ class WallNetwork: def add_wall(self, id: WallCoord) -> None: self.walls.add(id) + def remove_wall(self, id: WallCoord) -> None: + self.walls.remove(id) + class Maze: - def __init__(self, width: int, height: int) -> None: - self.width: int = width - self.height: int = height + def __init__(self, dims: tuple[int, int]) -> None: + self.__width: int = dims[0] + self.__height: int = dims[1] + self._clear() + + def _clear(self) -> None: # list of lines self.horizontal: list[list[MazeWall]] = [ - [MazeWall() for _ in range(0, width)] for _ in range(0, height + 1) + [MazeWall() for _ in range(0, self.__width)] + for _ in range(0, self.__height + 1) ] # list of lines self.vertical: list[list[MazeWall]] = [ - [MazeWall() for _ in range(0, height)] for _ in range(0, width + 1) + [MazeWall() for _ in range(0, self.__height)] + for _ in range(0, self.__width + 1) ] self.networks: dict[NetworkID, WallNetwork] = {} - def _get_wall(self, id: WallCoord) -> MazeWall: - if id.orientation == Orientation.HORIZONTAL: - return self.horizontal[id.a][id.b] - return self.vertical[id.a][id.b] + def _rebuild(self) -> None: + """ + rebuilds the maze to recompute proper connectivity values + """ + walls: set[WallCoord] = {wall for wall in self.walls_full()} + self._clear() + for wall in walls: + self.fill_wall(wall) + + def __get_wall(self, coord: WallCoord) -> MazeWall: + if coord.orientation == Orientation.HORIZONTAL: + return self.horizontal[coord.a][coord.b] + return self.vertical[coord.a][coord.b] + + def _remove_wall(self, coord: WallCoord) -> None: + """ + removes the wall, without updating network connectivity + """ + wall: MazeWall = self.__get_wall(coord) + if wall.network_id is not None: + self.networks[wall.network_id].remove_wall(coord) + wall.network_id = None 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.__height + 1, self.__width), + (Orientation.VERTICAL, self.__width + 1, self.__height), ]: for a in range(0, a_count): for b in range(0, b_count): yield WallCoord(orientation, a, b) - def _check_id(self, id: WallCoord) -> bool: - if id.a < 0 or id.b < 0: + def _check_coord(self, coord: WallCoord) -> bool: + if coord.a < 0 or coord.b < 0: return False (a_max, b_max) = ( - (self.height, self.width - 1) - if id.orientation == Orientation.HORIZONTAL - else (self.width, self.height - 1) + (self.__height, self.__width - 1) + if coord.orientation == Orientation.HORIZONTAL + else (self.__width, self.__height - 1) ) - if id.a > a_max or id.b > b_max: + if coord.a > a_max or coord.b > b_max: return False return True def get_walls_checked(self, ids: list[WallCoord]) -> list[MazeWall]: - return [self._get_wall(id) for id in ids if self._check_id(id)] + return [self.__get_wall(id) for id in ids if self._check_coord(id)] def get_neighbours(self, id: WallCoord) -> list[MazeWall]: return self.get_walls_checked(id.neighbours()) @@ -225,7 +271,7 @@ class Maze: self.networks[network_id] = network def fill_wall(self, id: WallCoord) -> None: - wall: MazeWall = self._get_wall(id) + wall: MazeWall = self.__get_wall(id) if wall.is_full(): return @@ -247,28 +293,32 @@ class Maze: for to_merge in filter(lambda n: n != dest_id, networks): for curr in self.networks[to_merge].walls: - self._get_wall(curr).network_id = dest_id + self.__get_wall(curr).network_id = dest_id dest.add_wall(curr) del self.networks[to_merge] def outline(self) -> None: - if self.width < 1 or self.height < 1: + if self.__width < 1 or self.__height < 1: return for orientation, a_iter, b_iter in [ - (Orientation.VERTICAL, (0, self.width), range(0, self.height)), - (Orientation.HORIZONTAL, (0, self.height), range(0, self.width)), + (Orientation.VERTICAL, (0, self.__width), range(0, self.__height)), + ( + Orientation.HORIZONTAL, + (0, self.__height), + range(0, self.__width), + ), ]: for a in a_iter: for b in b_iter: self.fill_wall(WallCoord(orientation, a, b)) def walls_full(self) -> Iterable[WallCoord]: - return filter(lambda w: self._get_wall(w).is_full(), self.all_walls()) + return filter(lambda w: self.__get_wall(w).is_full(), self.all_walls()) def walls_empty(self) -> Iterable[WallCoord]: return filter( - lambda w: not self._get_wall(w).is_full(), self.all_walls() + lambda w: not self.__get_wall(w).is_full(), self.all_walls() ) def wall_bisects(self, wall: WallCoord) -> bool: @@ -286,13 +336,29 @@ class Maze: def wall_cuts_cycle(self, wall: WallCoord) -> bool: return any( - len( - [ - () - for wall in self.get_walls_checked(list(cell.walls())) - if wall.is_full() - ] + ( + len( + [ + () + for wall in self.get_walls_checked(list(cell.walls())) + if wall.is_full() + ] + ) + >= 3 + if self.__get_wall(wall).is_full() + else 2 ) - >= 2 for cell in wall.neighbour_cells() ) + + def wall_leaf_neighbours(self, coord: WallCoord) -> list[WallCoord]: + leaf_f: Callable[ + [Callable[[WallCoord], list[WallCoord]]], list[WallCoord] + ] = lambda f: ( + list(filter(lambda c: self._check_coord(c), f(coord))) + if all( + not wall.is_full() for wall in self.get_walls_checked(f(coord)) + ) + else [] + ) + return leaf_f(WallCoord.a_neighbours) + leaf_f(WallCoord.b_neighbours)