/// <summary> /// The GenerateNewMaze method is called to generate a new maze. /// </summary> /// <returns></returns> public async Task GenerateNewMaze() { try { if (CanGenerateMaze) { MazeState = MazeState.MazeGenerating; // Clear the maze and stack. ResetMaze(); _mazeGeneratorStack = new Stack <MazeCell>(); // Select a random cell to start. MazeCell startCell = ChooseRandomCell(); startCell.CellState = CellState.Visited; // Generate the new maze. await GenerateNewMaze(startCell); // Set the start/end cells. if (MazeCells.Count > 0) { MazeCells.First().CellType = CellType.Start; MazeCells.Last().CellType = CellType.End; } MazeState = MazeState.MazeGenerated; } } catch (Exception ex) { throw new Exception("Maze.GenerateNewMaze(): " + ex.ToString()); } }
private void DoNextGenerationStep(List <MazeCells> activeCells) //Continues the Generation { int currentIndex = activeCells.Count - 1; MazeCells currentCell = activeCells [currentIndex]; if (currentCell.IsFullyInitialized) { activeCells.RemoveAt(currentIndex); return; } MazeDirection direction = currentCell.RandomUninitializedDirection; IntVector2 coordinates = currentCell.coordinates + direction.ToIntVector2(); if (ContainsCoordinates(coordinates)) { MazeCells neighbor = GetCell(coordinates); if (neighbor == null) { neighbor = CreateCell(coordinates); CreatePassage(currentCell, neighbor, direction); activeCells.Add(neighbor); } else { CreateWall(currentCell, neighbor, direction); } } else { CreateWall(currentCell, null, direction); } }
private void DoNextGenerationStep(List <MazeCells> activeCells) { int currentIndex = activeCells.Count - 1; MazeCells currentCell = activeCells[currentIndex]; MazeDirection direction = MazeDirections.RandomValue; IntVector2 coordinates = currentCell.coordinates + direction.ToIntVector2(); if (ContainsCoordinates(coordinates)) { MazeCells neighbor = GetCell(coordinates); if (neighbor == null) { neighbor = CreateCell(coordinates); CreatePassage(currentCell, neighbor, direction); activeCells.Add(neighbor); } else { CreateWall(currentCell, neighbor, direction); activeCells.RemoveAt(currentIndex); } } else { CreateWall(currentCell, null, direction); activeCells.RemoveAt(currentIndex); } }
/// <summary> /// The SimulationTimerEventHandler method is called when the simulation timer expires. /// It updates the current state of the simulation. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SimulationTimerEventHandler(object sender, EventArgs e) { try { if (SimulationState == SimulationState.Running) { MazeCell robotLocation = MazeCells.First(x => x.ContainsRobot); // Retrieve the current location of the robot. int cellIndex = MazeCells.IndexOf(robotLocation); // Retrieve the index of the cell. // Determine the indexes for the cell's neighbours. int northNeighbourIndex = cellIndex - MazeWidthHeightCells; int eastNeighbourIndex = cellIndex + 1; int southNeighbourIndex = cellIndex + MazeWidthHeightCells; int westNeighbourIndex = cellIndex - 1; // Determine if the cell is on the north/east/south/west edge of the maze - certain neighbours must be ignored if the current cell is on an edge. bool northEdge = cellIndex < MazeWidthHeightCells ? true : false; bool eastEdge = ((cellIndex + 1) % MazeWidthHeightCells) == 0 ? true : false; bool westEdge = (cellIndex % MazeWidthHeightCells) == 0 ? true : false; bool southEdge = (cellIndex + MazeWidthHeightCells) >= (MazeWidthHeightCells * MazeWidthHeightCells) ? true : false; // Retrieve the cell's neighbours. MazeCell northCell = null; MazeCell eastCell = null; MazeCell southCell = null; MazeCell westCell = null; // North cell. if (!northEdge && IsCellIndexValid(northNeighbourIndex)) { northCell = MazeCells[northNeighbourIndex]; } // East cell. if (!eastEdge && IsCellIndexValid(eastNeighbourIndex)) { eastCell = MazeCells[eastNeighbourIndex]; } // South cell. if (!southEdge && IsCellIndexValid(southNeighbourIndex)) { southCell = MazeCells[southNeighbourIndex]; } // West cell. if (!westEdge && IsCellIndexValid(westNeighbourIndex)) { westCell = MazeCells[westNeighbourIndex]; } // Create a maze segment at the robot's current location. MazeSegment mazeSegment = new MazeSegment(robotLocation, northCell, eastCell, southCell, westCell); _robot.Move(mazeSegment); // Move the robot. SimulationTime = SimulationTime.Add(TimeSpan.FromMilliseconds(Constants.DefaultStepIntervalMilliSeconds)); } } catch (Exception ex) { throw new Exception("Maze.SimulationTimerEventHandler(object sender, EventArgs e): " + ex.ToString()); } }
private void SetNewPosition(Vector2 newPosition) { // Check if we are not going out the map fist if (newPosition.x < 0 || newPosition.y < 0 || newPosition.x > Columns - 1 || newPosition.y > Rows - 1) { return; } // Check for the right or left wall switch ((int)Position.x - (int)newPosition.x) { case -1: if (MazeCells.ElementAt((int)(Position.x + Position.y * Columns)).Value.GetWallStatus(Cell.CellWalls.RightWall)) { return; } break; case 1: if (MazeCells.ElementAt((int)(Position.x + Position.y * Columns)).Value.GetWallStatus(Cell.CellWalls.LeftWall)) { return; } break; } // Check for the top or bottom wall switch ((int)Position.y - (int)newPosition.y) { case -1: if (MazeCells.ElementAt((int)(Position.x + Position.y * Columns)).Value.GetWallStatus(Cell.CellWalls.TopWall)) { return; } break; case 1: if (MazeCells.ElementAt((int)(Position.x + Position.y * Columns)).Value.GetWallStatus(Cell.CellWalls.BottomWall)) { return; } break; } Position = newPosition; transform.position = new Vector2(newPosition.x * Width + (Width / 2), newPosition.y * Height + (Height / 2)); // Show the menu when we reach the end of the maze if (Position == new Vector2(Columns - 1, 0)) { MazeManager.ShowMenu(); } nextMove = Time.time + moveInterval; }
private void CreatePassage(MazeCells cell, MazeCells otherCell, MazeDirection direction) { MazePassage passage = Instantiate(passagePrefab) as MazePassage; passage.Initialize(cell, otherCell, direction); passage = Instantiate(passagePrefab) as MazePassage; passage.Initialize(otherCell, cell, direction.GetOpposite()); }
public string GetCellString(GridPoint point) { if (!MazeCells.ContainsKey(point)) { return(" "); } return(DonutMazeCell.GetCellString(MazeCells[point])); }
/// <summary> /// The RetrieveNeighbours method is called to retrieve the neighbour cells for the provided cell. /// The neighbour cells are "offset" cells from the provided cell, in all 4 directions. /// </summary> /// <param name="cell"></param> /// <returns></returns> private List <MazeCell> RetrieveNeighbours(MazeCell cell, int offset = 1) { try { if (cell == null) { throw new Exception("cell can not be null."); } if (offset < 1) { throw new Exception("offset must be greater than or equal to 1."); } int cellIndex = MazeCells.IndexOf(cell); // Retrieve the index of the cell. // Determine the indexes for the current cell's neighbours. int northNeighbourIndex = cellIndex - offset * MazeWidthHeightCells; int eastNeighbourIndex = cellIndex + offset; int southNeighbourIndex = cellIndex + offset * MazeWidthHeightCells; int westNeighbourIndex = cellIndex - offset; // Determine if the current cell is on the north/east/south/west edge of the maze - certain neighbours must be ignored if the current cell is on an edge. bool northEdge = cellIndex < MazeWidthHeightCells ? true : false; bool eastEdge = ((cellIndex + 1) % MazeWidthHeightCells) == 0 ? true : false; bool westEdge = (cellIndex % MazeWidthHeightCells) == 0 ? true : false; bool southEdge = (cellIndex + MazeWidthHeightCells) >= (MazeWidthHeightCells * MazeWidthHeightCells) ? true : false; // Retrieve the current cell's neighbours. List <MazeCell> cellNeightbours = new List <MazeCell>(); // North cell. if (!northEdge && IsCellIndexValid(northNeighbourIndex)) { cellNeightbours.Add(MazeCells[northNeighbourIndex]); } // East cell. if (!eastEdge && IsCellIndexValid(eastNeighbourIndex)) { cellNeightbours.Add(MazeCells[eastNeighbourIndex]); } // South cell. if (!southEdge && IsCellIndexValid(southNeighbourIndex)) { cellNeightbours.Add(MazeCells[southNeighbourIndex]); } // West cell. if (!westEdge && IsCellIndexValid(westNeighbourIndex)) { cellNeightbours.Add(MazeCells[westNeighbourIndex]); } return(cellNeightbours); } catch (Exception ex) { throw new Exception("Maze.RetrieveNeighbours(MazeCell cell): " + ex.ToString()); } }
public void Initialize(MazeCells cell, MazeCells otherCell, MazeDirection direction) { this.cell = cell; this.otherCell = otherCell; this.direction = direction; cell.SetEdge(direction, this); transform.parent = cell.transform; transform.localPosition = Vector3.zero; transform.localRotation = direction.ToRotation(); }
private void ConstructPortals() { PortalCells = new Dictionary <GridPoint, string>(); Portals = new Dictionary <string, IList <GridPoint> >(); var emptyCells = MazeCells .Where(kvp => DonutMazeCellType.Empty.Equals(kvp.Value.Type)); foreach (var emptyCell in emptyCells) { var point = emptyCell.Key; var movementDirections = new List <MovementDirection>() { MovementDirection.Down, MovementDirection.Left, MovementDirection.Right, MovementDirection.Up }; foreach (var direction in movementDirections) { // Assumption: One point can only be associated with at // most one portal. var neighbor1 = point.Move(direction, 1); if (!MazeCells.ContainsKey(neighbor1) || !DonutMazeCellType.Portal.Equals(MazeCells[neighbor1].Type)) { continue; } var neighbor2 = point.Move(direction, 2); if (!MazeCells.ContainsKey(neighbor2) || !DonutMazeCellType.Portal.Equals(MazeCells[neighbor2].Type)) { throw new Exception("Only one neighboring portal cell found"); } var portalId = string.Empty; if (MovementDirection.Right.Equals(direction) || MovementDirection.Up.Equals(direction)) { portalId = MazeCells[neighbor1].PortalLetter + MazeCells[neighbor2].PortalLetter; } else { portalId = MazeCells[neighbor2].PortalLetter + MazeCells[neighbor1].PortalLetter; } PortalCells.Add(point, portalId); if (!Portals.ContainsKey(portalId)) { Portals.Add(portalId, new List <GridPoint>()); } Portals[portalId].Add(point); } } EntrancePoint = new GridPoint3D(Portals[EntrancePortalId][0], 0); ExitPoint = new GridPoint3D(Portals[ExitPortalId][0], 0); }
private void CreateWall(MazeCells cell, MazeCells otherCell, MazeDirection direction) { MazeWall wall = Instantiate(wallPrefab) as MazeWall; wall.Initialize(cell, otherCell, direction); if (otherCell != null) { wall = Instantiate(wallPrefab) as MazeWall; wall.Initialize(otherCell, cell, direction.GetOpposite()); } }
private MazeCells CreateCell(IntVector2 coordinates) { MazeCells newCell = Instantiate(cellPrefab) as MazeCells; cells[coordinates.x, coordinates.z] = newCell; newCell.coordinates = coordinates; newCell.name = "Maze Cell " + coordinates.x + ", " + coordinates.z; newCell.transform.parent = transform; newCell.transform.localPosition = new Vector3(coordinates.x - size.x * 0.5f + 0.5f, 0f, coordinates.z - size.z * 0.5f + 0.5f); return(newCell); }
/// <summary> /// The ConnectCells method is called to connect two cells. /// The wall cell that exists between the two cells becomes a passage. /// </summary> /// <param name="cellOne"></param> /// <param name="cellTwo"></param> private void ConnectCells(MazeCell cellOne, MazeCell cellTwo) { try { if (cellOne == null) { throw new Exception("cellOne can not be null."); } if (cellTwo == null) { throw new Exception("cellTwo can not be null."); } int indexOne = MazeCells.IndexOf(cellOne); int indexTwo = MazeCells.IndexOf(cellTwo); if (!IsCellIndexValid(indexOne)) { throw new Exception("can not determine index for cellOne."); } if (!IsCellIndexValid(indexTwo)) { throw new Exception("can not determine index for cellTwo."); } int offset = indexOne - indexTwo; int wallIndex = indexOne - (offset / 2); if (IsCellIndexValid(wallIndex)) { MazeCell wallCell = MazeCells[wallIndex]; wallCell.CellType = CellType.Passage; } else { throw new Exception("unable to retireve cell between cellOne and cellTwo."); } } catch (Exception ex) { throw new Exception("Maze.ConnectCells(MazeCell cellOne, MazeCell cellTwo): " + ex.ToString()); } }
public void InitVars() { // Remove all the cells we currently have if (previousCells.Count > 0) { foreach (GameObject cell in previousCells) { Destroy(cell); } } // Recalculate the cell width and height CellWidth = (float)Screen.width / (float)MazeInput.Instance.MazeColumns; CellHeight = (float)Screen.height / (float)MazeInput.Instance.MazeRows; // Empty the cells list for repopulation previousCells.Clear(); MazeCells.Clear(); }
/// <summary> /// The ChooseRandomCell method is called to choose a random cell in the maze. /// </summary> /// <returns></returns> private MazeCell ChooseRandomCell() { try { // Select a random cell to start. int cellIndex = _randomNumberGenerator.Next(Constants.MazeHeight * Constants.MazeWidth); if (IsCellIndexValid(cellIndex)) { return(MazeCells.ElementAt(cellIndex)); } else { throw new Exception("Unable to choose a randmom cell."); } } catch (Exception ex) { throw new Exception("Maze.ChooseRandomCell(): " + ex.ToString()); } }
public GameObject GetRandomAvailableNeighbour() { List <GameObject> neighbours = new List <GameObject>(); // Check if the position is available first and then add it Cell top = GetIndex(Position.x, Position.y + 1) != -1 ? MazeCells.ElementAt(GetIndex(Position.x, Position.y + 1)).Value : null; Cell right = GetIndex(Position.x + 1, Position.y) != -1 ? MazeCells.ElementAt(GetIndex(Position.x + 1, Position.y)).Value : null; Cell bottom = GetIndex(Position.x, Position.y - 1) != -1 ? MazeCells.ElementAt(GetIndex(Position.x, Position.y - 1)).Value : null; Cell left = GetIndex(Position.x - 1, Position.y) != -1 ? MazeCells.ElementAt(GetIndex(Position.x - 1, Position.y)).Value : null; // If the neighbours are available and not visited add them to our neighbours list if (top && !top.IsVisited) { neighbours.Add(top.gameObject); } if (right && !right.IsVisited) { neighbours.Add(right.gameObject); } if (bottom && !bottom.IsVisited) { neighbours.Add(bottom.gameObject); } if (left && !left.IsVisited) { neighbours.Add(left.gameObject); } // Choose one at random if we have neighbours if (neighbours.Count > 0) { return(neighbours[Random.Range(0, neighbours.Count)]); } else { return(null); } }
/// <summary> /// The ChooseRandomCell method is called to choose a random cell in the maze. /// This random cell can not be on the edge of the maze, as the maze edges are all walls. /// </summary> /// <returns></returns> private MazeCell ChooseRandomCell() { try { // X and Y indexes must not be on the edges - a offset of 2 is used to ensure this. int cellIndexX = _randomNumberGenerator.Next(2, MazeWidthHeightCells - 2); int cellIndexY = _randomNumberGenerator.Next(2, MazeWidthHeightCells - 2); if (IsCellIndexValid(cellIndexX * cellIndexY)) { return(MazeCells.ElementAt(cellIndexX * cellIndexY)); } else { throw new Exception("Unable to choose a random cell."); } } catch (Exception ex) { throw new Exception("Maze.ChooseRandomCell(): " + ex.ToString()); } }
public void GenerateGrid() { // Nested for-loop to fill the screen with cells for (int y = 0; y < MazeInput.Instance.MazeRows; y++) { for (int x = 0; x < MazeInput.Instance.MazeColumns; x++) { // Make the cell and set the properties GameObject cell = Instantiate(MazeInput.Instance.CellPrefab); // We set the parent first because we want to set the position relative to the canvas cell.transform.SetParent(MazeInput.Instance.MazeCanvas.transform); // Set the position with a small offset because the pivot point of the cell is in the center cell.transform.position = new Vector2(x * CellWidth + (CellWidth / 2), y * CellHeight + (CellHeight / 2)); cell.transform.localScale = new Vector2(CellWidth, CellHeight); cell.name = "Cell: " + (x + (y * MazeInput.Instance.MazeColumns)); // Set the index of the cell Cell cellScript = cell.GetComponent <Cell>(); cellScript.Position = new Vector2(x, y); cellScript.MazeRows = MazeInput.Instance.MazeRows; cellScript.MazeColumns = MazeInput.Instance.MazeColumns; // Set the cell colors cellScript.BackgroundColor = MazeInput.Instance.CellBackgroundColor; cellScript.WallColor = MazeInput.Instance.CellWallColor; cellScript.HighlightColor = MazeInput.Instance.CellHighlightColor; cellScript.VisitedColor = MazeInput.Instance.CellVisitedColor; previousCells.Add(cell); MazeCells.Add(cell, cellScript); } } // We do this at the end of generating the maze just so we have a start and end from the top left cell and bottom right cell MazeCells.ElementAt((MazeInput.Instance.MazeRows - 1) * MazeInput.Instance.MazeColumns).Value.RemoveWall(Cell.CellWalls.LeftWall); MazeCells.ElementAt(MazeInput.Instance.MazeColumns - 1).Value.RemoveWall(Cell.CellWalls.RightWall); }
private void CalculateMazeBoundaries() { OuterWallLeft = MazeCells .Where(kvp => !DonutMazeCellType.Portal.Equals(kvp.Value.Type)) .Min(kvp => kvp.Key.X); OuterWallRight = MazeCells .Where(kvp => !DonutMazeCellType.Portal.Equals(kvp.Value.Type)) .Max(kvp => kvp.Key.X); OuterWallTop = MazeCells .Where(kvp => !DonutMazeCellType.Portal.Equals(kvp.Value.Type)) .Min(kvp => kvp.Key.Y); OuterWallBottom = MazeCells .Where(kvp => !DonutMazeCellType.Portal.Equals(kvp.Value.Type)) .Max(kvp => kvp.Key.Y); int midX = (OuterWallRight - OuterWallLeft) / 2; int midY = (OuterWallBottom - OuterWallTop) / 2; InnerWallLeft = MazeCells .Where(kvp => !DonutMazeCellType.Portal.Equals(kvp.Value.Type) && kvp.Key.X <= midX && kvp.Key.Y == midY) .Max(kvp => kvp.Key.X); InnerWallRight = MazeCells .Where(kvp => !DonutMazeCellType.Portal.Equals(kvp.Value.Type) && kvp.Key.X >= midX && kvp.Key.Y == midY) .Min(kvp => kvp.Key.X); InnerWallTop = MazeCells .Where(kvp => !DonutMazeCellType.Portal.Equals(kvp.Value.Type) && kvp.Key.Y <= midY && kvp.Key.X == midX) .Max(kvp => kvp.Key.Y); InnerWallBottom = MazeCells .Where(kvp => !DonutMazeCellType.Portal.Equals(kvp.Value.Type) && kvp.Key.Y >= midY && kvp.Key.X == midX) .Min(kvp => kvp.Key.Y); }
public bool GetCanEnterCell( GridPoint point, SortedDictionary <string, string> keysCollected, bool ignoreDoors = false) { if (!MazeCells.ContainsKey(point)) { return(false); } var cell = MazeCells[point]; if (MazeCellType.Wall.Equals(cell.Type)) { return(false); } if (!ignoreDoors && CellsWithDoors.ContainsKey(point) && !keysCollected.ContainsKey(CellsWithDoors[point].ToLower())) { return(false); } return(true); }
/// <summary> /// The IsCellOnTheEdge method is called to determine if the provided cell is on the edge of the maze grid. /// </summary> /// <param name="cell"></param> /// <returns></returns> private bool IsCellOnTheEdge(MazeCell cell) { try { if (cell == null) { throw new Exception("cell can not be null."); } int cellIndex = MazeCells.IndexOf(cell); // Retrieve the index of the cell. // Determine if the current cell is on the north/east/south/west edge of the maze. bool northEdge = cellIndex < MazeWidthHeightCells ? true : false; bool eastEdge = ((cellIndex + 1) % MazeWidthHeightCells) == 0 ? true : false; bool westEdge = (cellIndex % MazeWidthHeightCells) == 0 ? true : false; bool southEdge = (cellIndex + MazeWidthHeightCells) >= (MazeWidthHeightCells * MazeWidthHeightCells) ? true : false; return(northEdge || eastEdge || westEdge || southEdge); } catch (Exception ex) { throw new Exception("Maze.IsCellOnTheEdge(MazeCell cell): " + ex.ToString()); } }
public IList <GridPoint> GetPointNeighbors( GridPoint point, SortedDictionary <string, string> keysCollected, GridPoint specificNeighborToAllow = null, bool ignoreDoors = false) { var neighbors = new List <GridPoint>(); var candidates = new List <GridPoint>() { point.MoveLeft(1), point.MoveRight(1), point.MoveUp(1), point.MoveDown(1) }; foreach (var candidate in candidates) { if (!MazeCells.ContainsKey(candidate)) { continue; } var candidateCell = MazeCells[candidate]; bool checkIfCanCenterCell = !( specificNeighborToAllow != null && specificNeighborToAllow.Equals(candidate)); if (checkIfCanCenterCell) { if (!GetCanEnterCell(candidate, keysCollected, ignoreDoors)) { continue; } } neighbors.Add(candidate); } return(neighbors); }
public IList <GridPoint3D> GetEmptyNeighbors(GridPoint3D currentPosition) { var result = new List <GridPoint3D>(); var movementDirections = new List <MovementDirection>() { MovementDirection.Down, MovementDirection.Left, MovementDirection.Right, MovementDirection.Up }; foreach (var movementDirection in movementDirections) { var neighborPoint = currentPosition.Move(movementDirection, 1); if (!MazeCells.ContainsKey(neighborPoint.XYPoint)) { continue; } var type = MazeCells[neighborPoint.XYPoint].Type; if (DonutMazeCellType.Wall.Equals(type)) { continue; } if (DonutMazeCellType.Empty.Equals(type)) { result.Add(neighborPoint); } else if (DonutMazeCellType.Portal.Equals(type)) { // Assumption: One point can be associated with at most // one portal var portalId = PortalCells[currentPosition.XYPoint]; if (EntrancePortalId.Equals(portalId) || ExitPortalId.Equals(portalId)) { continue; } bool isOnOuterEdge = GetIsOnOuterWall(currentPosition); // If this is a recursive maze, then outer portals only // work for z > 1 if (IsRecursive && isOnOuterEdge && currentPosition.Z == 0) { continue; } var connectedPoint = Portals[portalId] .Where(p => !p .Equals(currentPosition.XYPoint)) .FirstOrDefault(); if (connectedPoint == null) { throw new Exception("Portal found with no connected point"); } // Handle recursive maze - if this is an inner portal, then // we are going in a level, so increment z // If this is an outer portal, then we are going out a // level, so decrease z int connectedPointZ = currentPosition.Z; if (IsRecursive) { if (isOnOuterEdge) { connectedPointZ--; } else if (GetIsOnInnerWall(currentPosition)) { connectedPointZ++; } } // The connected point is stored in the maze at z=0, so // move it to the appropriate z index result.Add(new GridPoint3D(connectedPoint, connectedPointZ)); } else { throw new Exception($"Invalid cell type {type}"); } } return(result); }
public void DrawMaze() { GridHelper.DrawGrid2D( gridPoints: MazeCells.Select(kvp => kvp.Key).ToList(), GetPointString: GetCellString); }
public IList <Tuple <string, ConsoleColor> > GetMazeRenderingData() { return(GridHelper.GetGridRenderingData( gridPoints: MazeCells.Select(kvp => kvp.Key).ToList(), GetPointString: GetCellString)); }
/// <summary> /// The GenerateNewMaze method is called to generate a new maze. /// The Recursive Backtracker algorithm is used to generate the maze. /// http://weblog.jamisbuck.org/2010/12/27/maze-generation-recursive-backtracking /// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker /// </summary> private async Task GenerateNewMaze(MazeCell currentCell) { try { await Task.Delay(Constants.MazeGenerationDelayMilliSeconds); if (MazeCells.Any(x => x.CellState == CellState.Default) || _mazeGeneratorStack.Count > 0) // The maze contains unvisited cells or the stack is not empty. { int cellIndex = MazeCells.IndexOf(currentCell); // Retrieve the index of the current cell. // Determine the indexes for the current cell's neighbours. int northNeighbourIndex = cellIndex - Constants.MazeWidth; int eastNeighbourIndex = cellIndex + 1; int southNeighbourIndex = cellIndex + Constants.MazeHeight; int westNeighbourIndex = cellIndex - 1; // Determine if the current cell is on the north/east/south/west edge of the maze - certain neighbours must be ignored if the current cell is on an edge. bool northEdge = cellIndex < Constants.MazeWidth ? true : false; bool eastEdge = ((cellIndex + 1) % Constants.MazeWidth) == 0 ? true : false; bool westEdge = (cellIndex % Constants.MazeWidth) == 0 ? true : false; bool southEdge = (cellIndex + Constants.MazeWidth) >= (Constants.MazeWidth * Constants.MazeHeight) ? true : false; // Retrieve the current cell's unvisited neighbours. List <MazeCell> unvisitedNeighbours = new List <MazeCell>(); // North cell. if (!northEdge && IsCellIndexValid(northNeighbourIndex) && MazeCells[northNeighbourIndex].CellState == CellState.Default) { unvisitedNeighbours.Add(MazeCells[northNeighbourIndex]); } // East cell. if (!eastEdge && IsCellIndexValid(eastNeighbourIndex) && MazeCells[eastNeighbourIndex].CellState == CellState.Default) { unvisitedNeighbours.Add(MazeCells[eastNeighbourIndex]); } // South cell. if (!southEdge && IsCellIndexValid(southNeighbourIndex) && MazeCells[southNeighbourIndex].CellState == CellState.Default) { unvisitedNeighbours.Add(MazeCells[southNeighbourIndex]); } // West cell. if (!westEdge && IsCellIndexValid(westNeighbourIndex) && MazeCells[westNeighbourIndex].CellState == CellState.Default) { unvisitedNeighbours.Add(MazeCells[westNeighbourIndex]); } if (unvisitedNeighbours.Count > 0) { // The current cell has unvisited neighbours - select a random unvisited neighbour. MazeCell selectedNeighbour = unvisitedNeighbours.ElementAt(_randomNumberGenerator.Next(unvisitedNeighbours.Count)); // Remove the wall between the current cell and the selected neighbour. int selectedNeightbourIndex = MazeCells.IndexOf(selectedNeighbour); if (selectedNeightbourIndex == northNeighbourIndex) { currentCell.RemoveWall(Direction.North); selectedNeighbour.RemoveWall(Direction.South); } else if (selectedNeightbourIndex == eastNeighbourIndex) { currentCell.RemoveWall(Direction.East); selectedNeighbour.RemoveWall(Direction.West); } else if (selectedNeightbourIndex == southNeighbourIndex) { currentCell.RemoveWall(Direction.South); selectedNeighbour.RemoveWall(Direction.North); } else if (selectedNeightbourIndex == westNeighbourIndex) { currentCell.RemoveWall(Direction.West); selectedNeighbour.RemoveWall(Direction.East); } // Put the current cell on the stack. _mazeGeneratorStack.Push(currentCell); // Set the selected neighbour as visited. selectedNeighbour.CellState = CellState.Visited; // Repeat the process with the selected neighbour as the new current cell. await GenerateNewMaze(selectedNeighbour); } else { // Set the current cell to empty - it is now part of the maze. currentCell.CellState = CellState.Empty; // The current cell has no unvisited neighbours - pop a cell from the stack. MazeCell previousCell = _mazeGeneratorStack.Pop(); previousCell.CellState = CellState.Empty; // Set the popped cell to empty - it is now part of the maze. // Repeat the process with the popped cell as the new current cell. await GenerateNewMaze(previousCell); } } } catch (Exception ex) { throw new Exception("Maze.GenerateNewMaze(MazeCell currentCell): " + ex.ToString()); } }
/// <summary> /// The GenerateNewMaze method is called to generate a new maze. /// The Randomized Prim's algorithm is used to generate the maze. /// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Prim's_algorithm /// </summary> /// <returns></returns> public async Task GenerateNewMaze() { try { if (CanGenerateMaze) { ResetMaze(); // Clear the maze. SimulationState = SimulationState.MazeGenerating; // Update the simulation state. List <MazeCell> frontierCells = new List <MazeCell>(); // Create a collection of maze walls. // Select a random cell to start. MazeCell initialCell = ChooseRandomCell(); initialCell.CellType = CellType.Passage; frontierCells.AddRange(RetrieveFrontierNeighbours(initialCell)); // Add the neighbouring cells to the frontier. while (frontierCells.Count > 0) { // Choose a random cell from the frontier. MazeCell currentCell = frontierCells.ElementAt(_randomNumberGenerator.Next(frontierCells.Count)); frontierCells.Remove(currentCell); // Remove this cell from the frontier. if (currentCell.CellType == CellType.Passage) { // If the cell is already a passage, move on to the next frontier cell. continue; } // Retrieve the "passage" neighbours for the current cell (these neighbours are already part of the maze). List <MazeCell> passageNeighbours = RetrievePassageNeighbours(currentCell); if (passageNeighbours.Count > 0) { // Select a random "passage" neighbour. MazeCell selectedNeighbour = passageNeighbours.ElementAt(_randomNumberGenerator.Next(passageNeighbours.Count)); // Connect the current cell to the selected neighbour. ConnectCells(currentCell, selectedNeighbour); currentCell.CellType = CellType.Passage; // Retrieve the frontier neighbours for the current cell, and add them to the frontier. frontierCells.AddRange(RetrieveFrontierNeighbours(currentCell)); } await Task.Delay(Constants.MazeGenerationDelayMilliSeconds); } // Set the start/end cells. MazeCell startCell = MazeCells.First(x => x.CellType == CellType.Passage); startCell.CellRole = CellRole.Start; MazeCell endCell = MazeCells.Last(x => x.CellType == CellType.Passage); endCell.CellRole = CellRole.End; // Place the robot at the start cell. _robot.SetLocation(startCell); SimulationState = SimulationState.MazeGenerated; // Update the simulation state. } } catch (Exception ex) { throw new Exception("Maze.GenerateNewMaze(): " + ex.ToString()); } }