/// <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()); } }
/// <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()); } }
/// <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()); } }
/// <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 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()); } }