diff --git a/maze.py b/maze.py index 80af3cb..3edc472 100644 --- a/maze.py +++ b/maze.py @@ -1013,3 +1013,163 @@ class AldousBroder(VectorMaze): if start[1] > end[1]: return VectorEnum.Down if start[1] < end[1]: return VectorEnum.Up return VectorEnum.Zero + + +class AldousBroderWilson(VectorMaze): + + __cells: list[list[VectorEnum]] + + __width: int + __height: int + + __path: dict[tuple[int,int], VectorEnum] + __start: tuple[int,int] | None + + __current: tuple[int,int] + __remaining: int + + __switch: float + + def __init__( + self, + width: int, + height: int | None = None, + *, switch: float = 0.5 + ): + self.__width = width + self.__height = height or width + + self.__cells = [ + list([VectorEnum.Null for _ in range(self.width)]) + for _ in range(self.height)] + + x, y = random.randint(0,self.width-1), random.randint(0,self.height-1) + self.__cells[y][x] = VectorEnum.Zero + + self.__start = None + self.__path = {} + + self.__current = (x,y) + self.__remaining = self.width * self.height - 1 + + self.__switch = switch + + @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.__start is not None: + for key, value in self.__path.items(): + if value == VectorEnum.Zero: + return key + elif self.__remaining > 0: return self.__current + return None + + def __getitem__(self, index: tuple[int,int]) -> VectorEnum: + x, y = index + return self.__cells[y][x] + + def secondary(self, index: tuple[int,int]) -> VectorEnum: + if self.__start is not None: + if index in self.__path.keys(): + cell: tuple[int,int] = self.__start + while self.__path.get(cell, VectorEnum.Zero) != VectorEnum.Zero: + if cell == index: return self.__path[cell] + cell = self.__next(cell, self.__path[cell]) + return VectorEnum.Null + + def step(self) -> bool: + if self.highlighted is not None and self.__start is not None: + highlighted = self.highlighted + neighbors = self._neighbors(highlighted) + cell = neighbors[random.randint(0, len(neighbors)-1)] + self.__path[highlighted] = self.__direction_wilson( + highlighted, cell) + if self.__cells[cell[1]][cell[0]] == VectorEnum.Null: + self.__path[cell] = VectorEnum.Zero + self.__optimize_path() + else: + cell: tuple[int,int] = self.__start + while self.__path.get(cell, VectorEnum.Zero) != VectorEnum.Zero: + self.__cells[cell[1]][cell[0]] = self.__path[cell] + cell = self.__next(cell, self.__path[cell]) + self.__remaining -= len(self.__path) + self.__start = self.__new_start() + if self.__start is not None: + self.__path = {self.__start: VectorEnum.Zero} + else: + self.__path = {} + return True + elif self.highlighted is not None: + neighbors = self._neighbors(self.highlighted) + cell = neighbors[random.randint(0,len(neighbors)-1)] + if self.__cells[cell[1]][cell[0]] == VectorEnum.Null: + self.__cells[cell[1]][cell[0]] = self.__direction_aldous_broder( + self.highlighted, cell) + self.__remaining -= 1 + self.__current = cell + if self.__remaining / (self.width * self.height) < self.__switch: + self.__start = self.__new_start() + if self.__start is not None: + self.__path = {self.__start: VectorEnum.Zero} + return True + else: return False + + def __optimize_path(self): + if self.__start is not None: + purge = set(self.__path.keys()) + cell: tuple[int,int] = self.__start + purge.remove(cell) + while self.__path.get(cell, VectorEnum.Zero) != VectorEnum.Zero: + cell = self.__next(cell, self.__path[cell]) + purge.remove(cell) + for cell in purge: + del self.__path[cell] + + def __new_start(self) -> tuple[int, int] | None: + empty = [] + for x in range(self.width): + for y in range(self.height): + if self.__cells[y][x] == VectorEnum.Null: + empty.append((x,y)) + if empty: return empty[random.randint(0,len(empty)-1)] + else: return None + + def __direction_wilson( + self, + start: tuple[int,int], + end: tuple[int,int], + ) -> VectorEnum: + if start[0] > end[0]: return VectorEnum.Left + if start[0] < end[0]: return VectorEnum.Right + if start[1] > end[1]: return VectorEnum.Up + if start[1] < end[1]: return VectorEnum.Down + return VectorEnum.Zero + + def __direction_aldous_broder( + self, + start: tuple[int,int], + end: tuple[int,int], + ) -> VectorEnum: + if start[0] > end[0]: return VectorEnum.Right + if start[0] < end[0]: return VectorEnum.Left + if start[1] > end[1]: return VectorEnum.Down + if start[1] < end[1]: return VectorEnum.Up + return VectorEnum.Zero + + def __next( + self, + index: tuple[int,int], + direction: VectorEnum, + ) -> tuple[int,int]: + if direction == VectorEnum.Up: return index[0], index[1]-1 + elif direction == VectorEnum.Down: return index[0], index[1]+1 + elif direction == VectorEnum.Left: return index[0]-1, index[1] + elif direction == VectorEnum.Right: return index[0]+1, index[1] + else: return index[0], index[1] diff --git a/notes/algs.txt b/notes/algs.txt index 552dfc0..ad717b6 100644 --- a/notes/algs.txt +++ b/notes/algs.txt @@ -7,6 +7,6 @@ + Prim's Algorithm + Wilson's Algorithm + Aldous-Broder Algorithm -- Aldous-Broder-Wilson Algorithm ++ Aldous-Broder-Wilson Algorithm - Kruskal's Algorithm - Eller's Algorithm diff --git a/visualize.py b/visualize.py index d0269c7..64271f2 100644 --- a/visualize.py +++ b/visualize.py @@ -124,7 +124,8 @@ VEC_MAZE_SIZE = (MAZE_SIZE + 1) // 2 # my_maze = maze.RecursiveDivision(MAZE_SIZE, binary=True) # my_maze = maze.VectorWrapper(maze.Prim(VEC_MAZE_SIZE)) # my_maze = maze.VectorWrapper(maze.Wilson(VEC_MAZE_SIZE)) -my_maze = maze.VectorWrapper(maze.AldousBroder(VEC_MAZE_SIZE)) +# my_maze = maze.VectorWrapper(maze.AldousBroder(VEC_MAZE_SIZE)) +my_maze = maze.VectorWrapper(maze.AldousBroderWilson(VEC_MAZE_SIZE)) # for _ in range(512): my_maze.step() # for _ in range(2048): my_maze.step()