import abc, enum, random class Maze(abc.ABC): @property @abc.abstractmethod def width(self) -> int: pass @property @abc.abstractmethod def height(self) -> int: pass @property def highlighted(self) -> tuple[int,int] | None: pass @abc.abstractmethod def __getitem__(self, index: tuple[int,int]) -> bool: pass @abc.abstractmethod def step(self) -> bool: # returns False when algorithm is done pass def __str__(self) -> str: s = "" for y in range(self.height): for x in range(self.width): s += ' ' if self[x,y] else 'x' s += '\n' return s def __list__(self) -> list[list[bool]]: return [list([self[x,y] for x in range(self.width)]) for y in range(self.height)] def _neighbors(self, index: tuple[int,int]) -> list[tuple[int,int]]: neighbors: list[tuple[int,int]] = [] x, y = index if x - 2 >= 0: neighbors.append((x - 2,y)) if x + 2 < self.width: neighbors.append((x + 2,y)) if y - 2 >= 0: neighbors.append((x,y - 2)) if y + 2 < self.height: neighbors.append((x,y + 2)) return neighbors class VectorEnum(enum.Enum): Null = -1 Zero = 0 Up = 2 Down = 4 Left = 8 Right = 16 def __or__(self, value: "VectorEnum | int") -> int: if not isinstance(value, int): value = value.value return self.value | value def __ror__(self, value: "VectorEnum | int") -> int: if not isinstance(value, int): value = value.value return self.value | value def __and__(self, value: "VectorEnum | int") -> "VectorEnum": if not isinstance(value, int): value = value.value return VectorEnum(self.value & value) def __rand__(self, value: "VectorEnum | int") -> "VectorEnum": if not isinstance(value, int): value = value.value return VectorEnum(self.value & value) class VectorMaze(Maze): @abc.abstractmethod def __getitem__(self, index: tuple[int,int]) -> VectorEnum: pass def __list__(self) -> list[list[VectorEnum]]: return [list([self[x,y] for x in range(self.width)]) for y in range(self.height)] def _neighbors(self, index: tuple[int,int]) -> list[tuple[int,int]]: neighbors: list[tuple[int,int]] = [] x, y = index if x - 1 >= 0: neighbors.append((x - 1,y)) if x + 1 < self.width: neighbors.append((x + 1,y)) if y - 1 >= 0: neighbors.append((x,y - 1)) if y + 1 < self.height: neighbors.append((x,y + 1)) return neighbors class VectorWrapper(Maze): __maze: VectorMaze def __init__(self, maze: VectorMaze): self.__maze = maze @property def width(self) -> int: return self.__from_vec(self.__maze.width) @property def height(self) -> int: return self.__from_vec(self.__maze.height) @property def highlighted(self) -> tuple[int,int] | None: if self.__maze.highlighted is not None: return self.__from_vec(self.__maze.highlighted[0]), self.__from_vec(self.__maze.highlighted[1]) else: return None def __getitem__(self, index: tuple[int,int]) -> bool: x, y = index if x % 2 and y % 2: if self.__maze[self.__to_vec(x), self.__to_vec(y)] != VectorEnum.Null: return True elif x % 2: if self.__to_vec(y-1) >= 0: if self.__maze[self.__to_vec(x), self.__to_vec(y-1)] == VectorEnum.Down: return True if self.__to_vec(y+1) < self.__maze.height: if self.__maze[self.__to_vec(x), self.__to_vec(y+1)] == VectorEnum.Up: return True elif y % 2: if self.__to_vec(x-1) >= 0: if self.__maze[self.__to_vec(x-1), self.__to_vec(y)] == VectorEnum.Right: return True if self.__to_vec(x+1) < self.__maze.width: if self.__maze[self.__to_vec(x+1), self.__to_vec(y)] == VectorEnum.Left: return True return False def step(self) -> bool: return self.__maze.step() @property def maze(self) -> VectorMaze: return self.__maze @staticmethod def __from_vec(i: int) -> int: return ((i * 2) + 1) @staticmethod def __to_vec(i: int) -> int: return ((i - 1) // 2) class RecursiveBacktracker(Maze): __cells: list[list[bool]] __stack: list[tuple[int,int]] __width: int __height: int def __init__(self, width: int, height: int | None = None): self.__width = width self.__height = height or width self.__cells = [list([False for _ in range(self.width)]) for _ in range(self.height)] self.__stack = [( (random.randint(0, ((self.width - 1) // 2) - 1) * 2) + 1, (random.randint(0, ((self.height - 1) // 2) - 1) * 2) + 1 )] self.__cells[self.highlighted[1]][self.highlighted[0]] = True # type: ignore @property def width(self) -> int: return self.__width @property def height(self) -> int: return self.__height @property def highlighted(self) -> tuple[int,int] | None: if self.__stack: return self.__stack[-1] else: return None def __getitem__(self, index: tuple[int,int]) -> bool: return self.__cells[index[1]][index[0]] def step(self) -> bool: if self.highlighted is not None: neighbors = self.__unvisited_neighbors(self.highlighted) if neighbors: cell = neighbors[random.randint(0,len(neighbors)-1)] x = (self.highlighted[0] + cell[0]) // 2 y = (self.highlighted[1] + cell[1]) // 2 self.__cells[y][x] = True self.__cells[cell[1]][cell[0]] = True self.__stack.append(cell) else: self.__stack.pop() return True else: return False def __unvisited_neighbors(self, index: tuple[int,int]) -> list[tuple[int,int]]: return [(x,y) for x,y in self._neighbors(index) if not self[x,y]] class OriginShift(VectorMaze): __cells: list[list[VectorEnum]] __width: int __height: int def __init__(self, width: int, height: int | None = None): self.__width = width self.__height = height or width self.__cells = [list([self.__start(x,y) for x in range(self.width)]) for y in range(self.height)] @property def width(self) -> int: return self.__width @property def height(self) -> int: return self.__height @property def highlighted(self) -> tuple[int,int] | None: for y, row in enumerate(self.__cells): for x, cell in enumerate(row): if cell == VectorEnum.Zero: return (x, y) def __getitem__(self, index: tuple[int,int]) -> VectorEnum: x, y = index return self.__cells[y][x] def step(self) -> bool: if self.highlighted is not None: x, y = self.highlighted neighbors = self._neighbors((x,y)) if neighbors: cell = neighbors[random.randint(0,len(neighbors)-1)] self.__cells[y][x] = self.__direction((x,y), cell) self.__cells[cell[1]][cell[0]] = VectorEnum.Zero return True else: return False @classmethod def clone(cls, other: VectorMaze) -> "OriginShift": self = cls(other.width, other.height) self.__cells = [list([other[x,y] for x in range(other.width)]) for y in range(other.height)] return self def __direction(self, start: tuple[int,int], end: tuple[int,int]) -> VectorEnum: if start[0] - end[0] > 0: return VectorEnum.Left if start[0] - end[0] < 0: return VectorEnum.Right if start[1] - end[1] > 0: return VectorEnum.Up if start[1] - end[1] < 0: return VectorEnum.Down return VectorEnum.Zero def __start(self, x: int, y: int) -> VectorEnum: if x == self.width - 1 and y == self.height - 1: return VectorEnum.Zero elif x == self.width - 1: return VectorEnum.Down else: return VectorEnum.Right class BinaryTree(VectorMaze): __cells: list[list[VectorEnum]] __width: int __height: int __bias: int __x: int __y: int def __init__(self, width: int, height: int | None = None, *, bias: int | None = None ): self.__width = width self.__height = height or width self.__bias = bias or (VectorEnum.Up | VectorEnum.Left) self.__x = 0 self.__y = 0 self.__cells = [list([VectorEnum.Null for x in range(self.width)]) for y in range(self.height)] @property def width(self) -> int: return self.__width @property def height(self) -> int: return self.__height @property def highlighted(self) -> tuple[int,int] | None: if self.__x < self.width and self.__y < self.height: return self.__x, self.__y def __getitem__(self, index: tuple[int,int]) -> VectorEnum: x, y = index return self.__cells[y][x] def step(self) -> bool: if self.highlighted is not None: neighbors = self._neighbors(self.highlighted) if neighbors: cell = neighbors[random.randint(0,len(neighbors)-1)] self.__cells[self.__y][self.__x] = self.__direction(self.highlighted, cell) else: self.__cells[self.__y][self.__x] = VectorEnum.Zero self.__x += 1 if self.__x >= self.width: self.__x = 0 self.__y += 1 return True else: return False def __direction(self, start: tuple[int,int], end: tuple[int,int]) -> VectorEnum: if start[0] - end[0] > 0: return VectorEnum.Left if start[0] - end[0] < 0: return VectorEnum.Right if start[1] - end[1] > 0: return VectorEnum.Up if start[1] - end[1] < 0: return VectorEnum.Down return VectorEnum.Zero def _neighbors(self, index: tuple[int,int]) -> list[tuple[int,int]]: neighbors: list[tuple[int,int]] = [] x, y = index if x - 1 >= 0 and (self.__bias & VectorEnum.Left) == VectorEnum.Left: neighbors.append((x - 1,y)) if x + 1 < self.width and (self.__bias & VectorEnum.Right) == VectorEnum.Right: neighbors.append((x + 1,y)) if y - 1 >= 0 and (self.__bias & VectorEnum.Up) == VectorEnum.Up: neighbors.append((x,y - 1)) if y + 1 < self.height and (self.__bias & VectorEnum.Down) == VectorEnum.Down: neighbors.append((x,y + 1)) return neighbors class Sidewinder(Maze): __cells: list[list[bool]] __run: list[tuple[int,int]] __run_param: float __start: VectorEnum __width: int __height: int def __init__( self, width: int, height: int | None = None, *, run_param: float = 0.5, start: VectorEnum = VectorEnum.Up, ): self.__width = width self.__height = height or width self.__cells = [list([False for _ in range(self.width)]) for _ in range(self.height)] self.__start = start self.__run_param = run_param if self.__start == VectorEnum.Up: self.__run = [(1,1)] elif self.__start == VectorEnum.Down: self.__run = [(self.width-2,self.height-2)] elif self.__start == VectorEnum.Right: self.__run = [(self.width-2,self.height-2)] elif self.__start == VectorEnum.Left: self.__run = [(1,1)] else: self.__run = [] self.__cells[self.highlighted[1]][self.highlighted[0]] = True # type: ignore @property def width(self) -> int: return self.__width @property def height(self) -> int: return self.__height @property def highlighted(self) -> tuple[int,int] | None: if self.__run: return self.__run[-1] else: return None def __getitem__(self, index: tuple[int,int]) -> bool: return self.__cells[index[1]][index[0]] def step(self) -> bool: if self.highlighted is not None: hx, hy = self.highlighted if hx - 2 >= 0 and self.__start == VectorEnum.Down: cell = (hx - 2, hy) carve = hy + 2 >= self.height or random.random() > self.__run_param elif hx + 2 < self.width and self.__start == VectorEnum.Up: cell = (hx + 2, hy) carve = hy - 2 < 0 or random.random() > self.__run_param elif hy - 2 >= 0 and self.__start == VectorEnum.Right: cell = (hx, hy - 2) carve = hx + 2 >= self.width or random.random() > self.__run_param elif hy + 2 < self.height and self.__start == VectorEnum.Left: cell = (hx, hy + 2) carve = hx - 2 < 0 or random.random() > self.__run_param else: cell = None carve = False if cell is not None and carve: x = (hx + cell[0]) // 2 y = (hy + cell[1]) // 2 self.__cells[y][x] = True self.__cells[cell[1]][cell[0]] = True self.__run.append(cell) else: hx, hy = self.highlighted sx, sy = self.__run[random.randint(0,len(self.__run)-1)] self.__run.clear() if self.__start == VectorEnum.Down: if hx - 2 >= 0: x, y = (hx - 2, hy) self.__run.append((x,y)) self.__cells[y][x] = True else: if hy - 2 >= 0: x, y = (self.width - 2, hy - 2) self.__run.append((x,y)) self.__cells[y][x] = True if sy + 2 < self.height: cell = (sx, sy + 2) else: cell = None elif self.__start == VectorEnum.Up: if hx + 2 < self.width: x, y = (hx + 2, hy) self.__run.append((x,y)) self.__cells[y][x] = True else: if hy + 2 < self.height: x, y = (1, hy + 2) self.__run.append((x,y)) self.__cells[y][x] = True if sy - 2 >= 0: cell = (sx, sy - 2) else: cell = None elif self.__start == VectorEnum.Right: if hy - 2 >= 0: x, y = (hx, hy - 2) self.__run.append((x,y)) self.__cells[y][x] = True else: if hx - 2 >= 0: x, y = (hx - 2, self.height - 2) self.__run.append((x,y)) self.__cells[y][x] = True if sx + 2 < self.height: cell = (sx + 2, sy) else: cell = None elif self.__start == VectorEnum.Left: if hy + 2 < self.height: x, y = (hx, hy + 2) self.__run.append((x,y)) self.__cells[y][x] = True else: if hx + 2 < self.width: x, y = (hx + 2, 1) self.__run.append((x,y)) self.__cells[y][x] = True if sx - 2 >= 0: cell = (sx - 2, sy) else: cell = None if cell is not None: x = (sx + cell[0]) // 2 y = (sy + cell[1]) // 2 self.__cells[y][x] = True return True else: return False class _Window: __maze: "RecursiveDivision" __x_start: int __x_stop: int __y_start: int __y_stop: int def __init__(self, maze: "RecursiveDivision", x_start: int, x_stop: int, y_start: int, y_stop: int): self.__maze = maze self.__x_start = x_start self.__x_stop = x_stop self.__y_start = y_start self.__y_stop = y_stop @property def width(self) -> int: return self.__x_stop - self.__x_start @property def height(self) -> int: return self.__y_stop - self.__y_start def __getitem__(self, index: tuple[int,int]) -> bool: return self.__maze[index[1] - self.__x_start, index[0] - self.__y_start] def x(self, x: int) -> int: return x + self.__x_start def y(self, y: int) -> int: return y + self.__y_start def vertical_bisect(self, x: int) -> "tuple[_Window, _Window]": return _Window( self.__maze, self.__x_start, self.__x_start + x, self.__y_start, self.__y_stop, ), _Window( self.__maze, self.__x_start + x + 1, self.__x_stop, self.__y_start, self.__y_stop, ) def horizontal_bisect(self, y: int) -> "tuple[_Window, _Window]": return _Window( self.__maze, self.__x_start, self.__x_stop, self.__y_start, self.__y_start + y, ), _Window( self.__maze, self.__x_start, self.__x_stop, self.__y_start + y + 1, self.__y_stop, ) class RecursiveDivision(Maze): __cells: list[list[bool]] __stack: list[tuple[bool | None, _Window]] __split: tuple[bool, int, _Window] | None __binary: bool __uniform: bool __depth_first: bool __width: int __height: int def __init__( self, width: int, height: int | None = None, *, binary: bool = False, uniform: bool = True, depth_first: bool = True, ): self.__width = width self.__height = height or width self.__binary = binary self.__uniform = uniform self.__depth_first = depth_first self.__cells = [ list([(0 < x < self.width-1 and 0 < y < self.height-1) for x in range(self.width)]) for y in range(self.height)] self.__stack = [ ((True if self.__uniform else None), _Window(self,1,self.width,1,self.height))] self.__split = None @property def width(self) -> int: return self.__width @property def height(self) -> int: return self.__height def __getitem__(self, index: tuple[int,int]) -> bool: return self.__cells[index[1]][index[0]] def step(self) -> bool: if self.__split is not None: if self.__split[0]: axis, y, window = self.__split x = (random.randint(0, (window.width - 1) // 2) * 2) self.__cells[window.y(y)][window.x(x)] = True a, b = [ ((not axis if self.__uniform else None), w) for w in window.horizontal_bisect(y)] else: axis, x, window = self.__split y = (random.randint(0, (window.height - 1) // 2) * 2) self.__cells[window.y(y)][window.x(x)] = True a, b = [ ((not axis if self.__uniform else None), w) for w in window.vertical_bisect(x)] if a[1].width > 2 and a[1].height > 2: self.__stack.append(a) if b[1].width > 2 and b[1].height > 2: self.__stack.append(b) self.__split = None return True elif self.__stack: axis, window = self.__stack.pop(-1 if self.__depth_first else 0) if axis is None: axis = bool(random.randint(0,1)) if axis: if self.__binary: y = (((window.height - 3) // 4) * 2) + 1 else: y = (random.randint(0, (window.height - 3) // 2) * 2) + 1 for x in range(window.width): self.__cells[window.y(y)][window.x(x)] = False self.__split = axis, y, window else: if self.__binary: x = (((window.width - 3) // 4) * 2) + 1 else: x = (random.randint(0, (window.width - 3) // 2) * 2) + 1 for y in range(window.height): self.__cells[window.y(y)][window.x(x)] = False self.__split = axis, x, window return True else: return False