/// <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()); } }
/// <summary> /// The Move method is called to move the robot. /// The Trémaux's algorithm is used to control the robot. /// https://en.wikipedia.org/wiki/Maze_solving_algorithm#Tr%C3%A9maux's_algorithm /// </summary> /// <param name="mazeSegment"></param> public void Move(MazeSegment mazeSegment) { try { if (mazeSegment == null) { throw new Exception("maze segment can not be null."); } if (CurrentLocation == null) { // The robot is not in the maze - do nothing. return; } // If the robot is NOT at a junction, mark the location. if (mazeSegment.SegmentType != SegmentType.Junction) { CurrentLocation.MarkCell(); } // If the robot is at a dead-end (except for the start of the maze), mark the location a second time. if (mazeSegment.SegmentType == SegmentType.DeadEnd && CurrentLocation.CellRole != CellRole.Start) { CurrentLocation.MarkCell(); } // Turn the robot, based on the type of maze segment. CurrentDirection = mazeSegment.ChooseDirection(CurrentDirection); // Attempt to move the robot forwards. MazeCell newCurrentLocation = null; if (CurrentDirection == Direction.North && mazeSegment.NorthCell.CellMark != CellMark.Twice) { newCurrentLocation = mazeSegment.NorthCell; } else if (CurrentDirection == Direction.East && mazeSegment.EastCell.CellMark != CellMark.Twice) { newCurrentLocation = mazeSegment.EastCell; } else if (CurrentDirection == Direction.South && mazeSegment.SouthCell.CellMark != CellMark.Twice) { newCurrentLocation = mazeSegment.SouthCell; } else if (CurrentDirection == Direction.West && mazeSegment.WestCell.CellMark != CellMark.Twice) { newCurrentLocation = mazeSegment.WestCell; } // Update the current location. CurrentLocation.ContainsRobot = false; CurrentLocation = newCurrentLocation != null ? newCurrentLocation : CurrentLocation; CurrentLocation.ContainsRobot = true; if (CurrentLocation.CellRole == CellRole.End) { OnReachedTheEnd?.Invoke(); // The robot has reached the end of the maze. } } catch (Exception ex) { throw new Exception("Robot.Move(MazeSegment mazeSegment): " + 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()); } }
/// <summary> /// Constructor. /// Creates a new MazeSegment from a center cell and four neighbouring cells. /// </summary> public MazeSegment(MazeCell centerCell, MazeCell northCell, MazeCell eastCell, MazeCell southCell, MazeCell westCell) { try { CenterCell = centerCell ?? throw new Exception("Center cell can not be null."); // Assign the center cell. // Assign the neighbour cells. if (northCell == null && eastCell == null && southCell == null && westCell == null) { throw new Exception("All four neighbour cells can not be null."); } NorthCell = northCell == null ? new MazeCell() { CellType = CellType.Wall } : northCell; EastCell = eastCell == null ? new MazeCell() { CellType = CellType.Wall } : eastCell; SouthCell = southCell == null ? new MazeCell() { CellType = CellType.Wall } : southCell; WestCell = westCell == null ? new MazeCell() { CellType = CellType.Wall } : westCell; // Build the list of cells not visited. _cellsNotVisited = new List <Direction>(); if (NorthCell.CellType == CellType.Passage && NorthCell.CellMark == CellMark.None) { _cellsNotVisited.Add(Direction.North); } if (EastCell.CellType == CellType.Passage && EastCell.CellMark == CellMark.None) { _cellsNotVisited.Add(Direction.East); } if (SouthCell.CellType == CellType.Passage && SouthCell.CellMark == CellMark.None) { _cellsNotVisited.Add(Direction.South); } if (WestCell.CellType == CellType.Passage && WestCell.CellMark == CellMark.None) { _cellsNotVisited.Add(Direction.West); } // Build the list of cells visited once. _cellsVisitedOnce = new List <Direction>(); if (NorthCell.CellType == CellType.Passage && NorthCell.CellMark == CellMark.Once) { _cellsVisitedOnce.Add(Direction.North); } if (EastCell.CellType == CellType.Passage && EastCell.CellMark == CellMark.Once) { _cellsVisitedOnce.Add(Direction.East); } if (SouthCell.CellType == CellType.Passage && SouthCell.CellMark == CellMark.Once) { _cellsVisitedOnce.Add(Direction.South); } if (WestCell.CellType == CellType.Passage && WestCell.CellMark == CellMark.Once) { _cellsVisitedOnce.Add(Direction.West); } SegmentType = DetermineSegmentType(); // Determine the segment type. } catch (Exception ex) { throw new Exception("MazeSegment(MazeCell centerCell, MazeCell northCell, MazeCell eastCell, MazeCell southCell, MazeCell westCell): " + ex.ToString()); } }