def testGetNeighbourOfOutOfRangeTooLowCell(self): maze = Maze(4, 4) with self.assertRaises( expected_exception=IndexError, msg="`cellIndex` out of range (-5 not between 0 and 15).", ): _ = maze.getNeighboursOfCell(-5)
def testGetOutOfRangeCellTooHighIndexFromCoordinate(self): maze = Maze(1, 1) with self.assertRaises( ValueError, msg="Coordinate (72, 0) is not valid.", ): _ = maze.getIndexFromCoordinates(XY(72, 0))
def testAddWallFromNonExistentCell(self): maze = Maze(2, 2) with self.assertRaises( IndexError, msg="`cellIndex` out of range (55 not between 0 and 3).", ): maze.addWallBetween(55, 2)
def testRemoveWallToNonExistentCell(self): maze = Maze(2, 2) with self.assertRaises( IndexError, msg="`cellIndex` out of range (55 not between 0 and 3).", ): maze.removeWallBetween(2, 55)
def testGetInvalidCellCoordinatesFromIndexInNonSquareMaze(self): maze = Maze(12, 1) with self.assertRaises( IndexError, msg= "`cellIndex` out of range (761263748 not between 0 and 11).", ): _ = maze.getCoordinatesFromIndex(761263748)
def testGetOutOfRangeTooHighCellCoordinatesFromIndex(self): maze = Maze(1, 1) # get too high cell index coordinates with self.assertRaises( IndexError, msg="`cellIndex` out of range (55 not between 0 and 0).", ): _ = maze.getCoordinatesFromIndex(55)
def testRemoveWallBetweenNonAdjacentCells(self): maze = Maze(3, 3) # remove invalid wall between top left and bottom right cells with self.assertRaises( ValueError, msg="Cell at index 0 is not adjacent to cell at 8.", ): maze.removeWallBetween(0, 8)
def testRemoveWallBetweenSelf(self): maze = Maze(2, 2) # remove a wall between `x` and `x`, i.e., to itself with self.assertRaises( ValueError, msg="Cell at index 0 is not adjacent to cell at 0.", ): maze.removeWallBetween(0, 0)
def testRemoveNonExistentWall(self): maze = Maze(3, 3, False) # remove a wall that doesn't exist with self.assertRaises( Exception, msg="Node index '1' already exists in node 0's connections.", ): maze.removeWallBetween(0, 1)
def testAddWallBetweenSelf(self): maze = Maze(2, 2) # add a wall between `x` and `x`, i.e., to itself with self.assertRaises( Exception, msg= "Node index 0 already does not exist in node at index 0's connections.", ): maze.addWallBetween(0, 0)
def testAddExistentWall(self): maze = Maze(3, 3, True) # add a wall that already exists with self.assertRaises( Exception, msg= "Node index 1 already does not exist in node at index 0's connections.", ): maze.addWallBetween(0, 1)
def __init__(self, size: XY) -> None: # check size is valid self._testSizeIsValidWithException(size) self.__size = size # initialize a Maze full of walls self.__maze = Maze(self.__size.x, self.__size.y, walls=True) # get the last cell index of the maze, so we can initialise __visitedOrNotCells to a list of False (nothing visited yet) lastIndex = self.__maze.getIndexFromCoordinates( XY(self.__size.x - 1, self.__size.y - 1)) # init __visitedOrNotCells to a list of False with the length of the max index of cells self.__visitedOrNotCells = [False] * (lastIndex + 1)
def testRemoveWall(self): maze = Maze(4, 4, True) # remove a wall from first cell maze.removeWallBetween(0, 1) self.assertCountEqual(maze.getConnectionsOfCellAtIndex(0), [1]) # remove another wall from first cell maze.removeWallBetween(0, 4) self.assertCountEqual(maze.getConnectionsOfCellAtIndex(0), [1, 4])
def testAddWallAgain(self): maze = Maze(4, 4, False) # add a wall from first cell maze.addWallBetween(0, 1) self.assertCountEqual(maze.getConnectionsOfCellAtIndex(0), [4]) # add another wall from first cell maze.addWallBetween(0, 4) self.assertCountEqual(maze.getConnectionsOfCellAtIndex(0), [])
def __init__(self, size: XY) -> None: # check size is valid self._testSizeIsValidWithException(size) self.__size = size # initialize a Maze full of walls self.__maze = Maze(self.__size.x, self.__size.y, walls=True)
def testMazeIsSaved(self): FILEPATH = self.FILE_PATH_PREFIX + "test_save.maze" fileHandler = MazeFileHandler(FILEPATH) maze = Maze(4, 4, True) with self.assertLogs(level="DEBUG"): fileHandler.save(maze)
def testMazeIsLoadedCorrectly(self): FILEPATH = self.FILE_PATH_PREFIX + "test_save.maze" size = XY(13, 3) # save the maze fileHandler = MazeFileHandler(FILEPATH) fileHandler.save(Maze(size.x, size.y, False)) # try to load the maze newMaze = fileHandler.load() self.assertEqual(newMaze.size, size)
def testGetLastCellIndexFromCoordinates(self): maze = Maze(3, 3) index = maze.getIndexFromCoordinates(XY(2, 2)) self.assertEqual(index, 8)
def testGetLastCellCoordinatesFromIndexInNonSquareMaze(self): maze = Maze(37, 17) c = maze.getCoordinatesFromIndex(628).toTuple() self.assertEqual(c, (36, 16))
class RecursiveBacktracker(MazeGenerator): """Recursive Backtracker maze generation algorithm implementation. | Here’s the mile-high view of recursive backtracking: | | • Choose a starting point in the field. | • Randomly choose a wall at that point and carve a passage through to the adjacent cell, but only if the adjacent cell has not been visited yet. | This becomes the new current cell. | • If all adjacent cells have been visited, back up to the last cell that has uncarved walls and repeat. | • The algorithm ends when the process has backed all the way up to the starting point. > Source - http://weblog.jamisbuck.org/2010/12/27/maze-generation-recursive-backtracking """ __maze: MazeProtocol __visitedOrNotCells: List[ bool] # the cell index is the position in the list __size: XY # the stack of positions __positionsStack: Stack[int] def __init__(self, size: XY) -> None: # check size is valid self._testSizeIsValidWithException(size) self.__size = size # initialize a Maze full of walls self.__maze = Maze(self.__size.x, self.__size.y, walls=True) # get the last cell index of the maze, so we can initialise __visitedOrNotCells to a list of False (nothing visited yet) lastIndex = self.__maze.getIndexFromCoordinates( XY(self.__size.x - 1, self.__size.y - 1)) # init __visitedOrNotCells to a list of False with the length of the max index of cells self.__visitedOrNotCells = [False] * (lastIndex + 1) def generate(self) -> MazeProtocol: # start our maze generator in the top left of the maze start = XY(0, 0) # init positionsStack to a new empty stack of type int self.__positionsStack = Stack[int]() # Convert the `start` position to the same cell's index in the maze, and push to the positions stack self.__positionsStack.push( # get the index of the `start` posotion self.__maze.getIndexFromCoordinates(start)) # set the starting cell as visited self.__visitedOrNotCells[self.__positionsStack.peek()] = True # while the positions stack is not empty, so we automatically exit the loop when visited all cells and back at start while not self.__positionsStack.isEmpty(): randomCellChoice: Optional[int] = None # this loop tries to find a random cell to visit out of the unvisited neighbours of the current cell while randomCellChoice is None: # if we've ran out of positions to backtrack to, and therefore made the entire maze if self.__positionsStack.isEmpty(): break # get a list of unvisited adjacent cells neighbourCells = self.__maze.getNeighboursOfCell( # get the current position by peeking the stack. # don't pop because we want the item to remain on the stack, # so we can backtrach through it. self.__positionsStack.peek()) # Filter the neighbourCells by whether they've been visited or not # create a lambda to return whether or not a cell at index has been visited, but return the inverse because we are _filtering_ the cells! checkIsVisited: Callable[ [int], bool] = lambda cellIndex: not self.__visitedOrNotCells[ cellIndex] # …and filter the neighbourCells by this lambda, and put it into the list `unvisitedWalls` unvisitedWalls = list(filter(checkIsVisited, neighbourCells)) # check that there are unvisited walls if len(unvisitedWalls) > 0: # choose a random wall randomCellChoice = randomChoice(unvisitedWalls) # set the next cell to visited self.__visitedOrNotCells[randomCellChoice] = True else: # all the cells here have been visited # so back up to the last cell, by popping the positionsStack self.__positionsStack.pop() # if the cell hasn't been chosen, and therefore we've explored the whole maze if randomCellChoice is None: # break so we can return the completed maze break # carve a passage through to the adjacent cell self.__maze.removeWallBetween( cellAIndex=self.__positionsStack.peek(), cellBIndex=randomCellChoice, ) # push the choice to the positionsStack so it is our next one self.__positionsStack.push(randomCellChoice) return self.__maze
forward.success, f"Turned {movementDirection} and attempted to move forward", solver._state, ) command = MazeSolverCommand( "Move randomly", MazeSolverCommandType.movement, result, ) return (command, result) if __name__ == "__main__": # Test out the random mouse maze solver from modules.data_structures.maze.maze import Maze maze = Maze(10, 10, False) rm = RandomMouse(maze, XY(0, 0), XY(9, 9)) FORMAT = "%(asctime)s - %(name)-20s - %(levelname)-5s - %(message)s" LEVEL = 0 logging.basicConfig(format=FORMAT, level=LEVEL) logging.getLogger().setLevel(LEVEL) log = logging.getLogger() while True: rm.advance() print(rm.getCurrentState().currentCell)
def testGetCoordinateOfFirstCell(self): coordinate = Maze(5, 5).getCoordinatesFromIndex(0).toTuple() self.assertEqual(coordinate, (0, 0))
def testInitWithZeroSize(self): maze = Maze(0, 0) self.assertIsInstance(maze, Maze)
def testGetNeighbourOfSurroundedCell(self): neighbours = Maze(20, 20).getNeighboursOfCell(189) actualNeighbours = [169, 209, 188, 190] self.assertCountEqual(neighbours, actualNeighbours)
def testGetFirstCellIndexFromCoordinates(self): maze = Maze(2, 2) index = maze.getIndexFromCoordinates(XY(0, 0)) self.assertEqual(index, 0)
def testGetNeighbourOfWallCell(self): neighbours = Maze(20, 20).getNeighboursOfCell(1) actualNeighbours = [0, 2, 21] self.assertCountEqual(neighbours, actualNeighbours)
def testGetNeighbourOfCornerCell(self): neighbours = Maze(20, 20).getNeighboursOfCell(0) actualNeighbours = [1, 20] self.assertCountEqual(neighbours, actualNeighbours)
def testInitWithInvalidYSize(self): with self.assertRaises( ValueError, msg="Invalid size `(4, -66)` given.", ): _ = Maze(4, -66)
def testInitWithInvalidXSize(self): with self.assertRaises( ValueError, msg="Invalid size `(-21, 7)` given.", ): _ = Maze(-21, 7)
def testGetCentreCellIndexFromCoordinates(self): maze = Maze(3, 3) index = maze.getIndexFromCoordinates(XY(1, 1)) self.assertEqual(index, 4)