private bool IsWallAdjacent(MazeGridpoint mazeGridpoint, DirectionEnum direction) { // Start by proceeding in a direction given away from mazeGridpoint var isWallAdjacent = false; var nextMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(mazeGridpoint, direction); // Now, check each direction around the nextMazeGridpoint foreach (var nextDirection in nextMazeGridpoint.DirectionsAvailable.OpenPaths) { if (!MazeToSolve.IsValidPositionInMazeAndNotBacktracking(mazeGridpoint, nextMazeGridpoint, nextDirection)) { continue; } var nextNextMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(nextMazeGridpoint, nextDirection); if (nextNextMazeGridpoint.HasBeenVisited) { continue; } if (nextNextMazeGridpoint.IsWall) { isWallAdjacent = true; break; } } return(isWallAdjacent); }
/// <summary> /// Picks a direction that is consitent with the last direction proceeded, if possible. /// </summary> public override DirectionEnum PickDirectionToProceed(object mazeSolutionElements, MazeGridpoint mazeGridpoint) { var pathToSolveMaze = mazeSolutionElements as List <MazeSolutionElement>; if (pathToSolveMaze == null) { throw new Exception("Maze solution elements used are not supported by BaseDirectionPicker"); } if (pathToSolveMaze.Any()) { var lastMazeSolutionElement = pathToSolveMaze.Last(); var lastMazeGridpoint = lastMazeSolutionElement.MazeGridpoint; var lastDirectionProceeded = lastMazeSolutionElement.DirectionProceeded; // Find all paths that do not lead back to the last position in the maze on the path traced so far // AND, are valid positions inside the boundaries of the maze var potentialDirectionsToProceed = mazeGridpoint.DirectionsAvailable.OpenPaths .Where(direction => MazeToSolve.IsValidPositionInMazeAndNotBacktracking(lastMazeGridpoint, mazeGridpoint, direction)).ToList(); // If it's possible to keep going in a straight line, continue to do so if (potentialDirectionsToProceed.Contains(lastDirectionProceeded)) { return(lastDirectionProceeded); } return(potentialDirectionsToProceed.FirstOrDefault()); } // Take the first path that is valid return(mazeGridpoint.DirectionsAvailable.OpenPaths .FirstOrDefault(direction => MazeToSolve.IsValidPositionInMaze(mazeGridpoint, direction))); }
private MazeGridpoint SearchForSolution(Queue <MazeGridpoint> queueToSolveMaze) { // As long as there are still elements in the queue to check, keep going while (queueToSolveMaze.Any()) { var currentMazeGridpoint = queueToSolveMaze.Dequeue(); currentMazeGridpoint.HasBeenVisited = true; MazeToSolve.NotifyMazeHasBeenUpdated(); MazeToSolve.NotifyMazeToBeRedrawnUpdated(); _foundMazeFinishPoint = MazeToSolve.CheckIfAtFinish(currentMazeGridpoint); // Quit now if the end of the maze was found if (_foundMazeFinishPoint) { return(currentMazeGridpoint); } // Investigate each direction available at the current gridpoint foreach (var direction in currentMazeGridpoint.DirectionsAvailable.OpenPaths) { if (!MazeToSolve.IsValidPositionInMaze(currentMazeGridpoint, direction)) { continue; } var nextMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(currentMazeGridpoint, direction); if (MazeToSolve.CheckIfAtDeadEnd(nextMazeGridpoint) || nextMazeGridpoint.HasBeenVisited) { continue; } // Set up the parent gridpoint for each direction that is valid var nextMazeSolutionElement = PathToSolveMaze.Single(m => m.MazeGridpoint == nextMazeGridpoint); nextMazeSolutionElement.ParentMazeGridpoint = currentMazeGridpoint; nextMazeGridpoint.HasBeenVisited = true; // Make sure we haven't found the end of the maze for each direction _foundMazeFinishPoint = MazeToSolve.CheckIfAtFinish(nextMazeGridpoint); if (_foundMazeFinishPoint) { return(nextMazeGridpoint); } // If its not the end of the maze, queue it up for another round down the line queueToSolveMaze.Enqueue(nextMazeGridpoint); Console.WriteLine("X : " + nextMazeGridpoint.Position.X + ", Y : " + nextMazeGridpoint.Position.Y + " Direction : " + direction); } } throw new Exception("Maze Solver Error: Sorry, maze could not be solved. The maze image provided may be invalid."); }
private void RecursivelySearchForSolution(MazeGridpoint currentMazeGridpoint, MazeSolutionElementTree parentMazeSolutionElementTree) { // No need to keep going on this call if the finish has already been found if (_foundMazeFinishPoint) { return; } _mazeSolutionElementTree = parentMazeSolutionElementTree; _foundMazeFinishPoint = MazeToSolve.CheckIfAtFinish(currentMazeGridpoint); currentMazeGridpoint.HasBeenVisited = true; MazeToSolve.NotifyMazeHasBeenUpdated(); MazeToSolve.NotifyMazeToBeRedrawnUpdated(); // Now that we've checked the current gridpoint, let's make sure we need to do more work if (_foundMazeFinishPoint) { return; } // For each direction available that is valid, add an element to the tree and recursively // call the search function again. foreach (var direction in currentMazeGridpoint.DirectionsAvailable.OpenPaths) { if (!MazeToSolve.IsValidPositionInMaze(currentMazeGridpoint, direction)) { continue; } var nextMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(currentMazeGridpoint, direction); if (MazeToSolve.CheckIfAtDeadEnd(nextMazeGridpoint) || nextMazeGridpoint.HasBeenVisited) { continue; } var nextMazeSolutionElement = new MazeSolutionElementTree { ParentSolutionElement = parentMazeSolutionElementTree, MazeGridpoint = nextMazeGridpoint }; Console.WriteLine("X : " + nextMazeGridpoint.Position.X + ", Y : " + nextMazeGridpoint.Position.Y + " Direction : " + direction); RecursivelySearchForSolution(nextMazeGridpoint, nextMazeSolutionElement); } }
/// <summary> /// Sets all points in a maze that have only one open path to visited, effectively removing /// them from needing to be explored for a solution. /// </summary> public override void PreTreatMazeToSolve() { var deadEndMazeGridpoints = MazeToSolve.MazeGridpoints .Where(m => !m.Value.IsWall && !m.Value.HasBeenVisited && m.Value.DirectionsAvailable.Count == 1); foreach (var mazeGridpoint in deadEndMazeGridpoints) { if (mazeGridpoint.Value.IsStartPoint || mazeGridpoint.Value.IsFinishPoint) { continue; } mazeGridpoint.Value.HasBeenVisited = true; MazeToSolve.NotifyMazeHasBeenUpdated(); MazeToSolve.NotifyMazeToBeRedrawnUpdated(); } }
/// <summary> /// Given a direction to proceed, this logic determines what the next maze gridpoint will be. In cases /// where we have reached a dead-end, this may mean backtracking to the last gridpoint where another /// choice was available. /// </summary> private MazeGridpoint DetermineNextMazeGridPoint(MazeGridpoint currentMazeGridpoint, DirectionEnum directionToProceed) { var nextMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(currentMazeGridpoint, directionToProceed); if (MazeToSolve.CheckIfAtDeadEnd(nextMazeGridpoint) || nextMazeGridpoint.HasBeenVisited) { // We have arrived at a dead-end, so this cannot have been the right direction to proceed currentMazeGridpoint.DirectionsAvailable[directionToProceed] = false; if (currentMazeGridpoint.DirectionsAvailable.Count == 1) { // Go back to the last gridpoint where there were more than two choices about the direction to proceed var lastValidMazeSolutionElement = PathToSolveMaze.LastOrDefault(p => p.MazeGridpoint.DirectionsAvailable.Count > 2); if (lastValidMazeSolutionElement == null) { throw new Exception("Maze Solver Error: Sorry, maze could not be solved. The maze image provided may be invalid."); } var directionProceededAtLastValidMazeGridPoint = lastValidMazeSolutionElement.DirectionProceeded; // Since the path resulted in a dead-end, it cannot have been the reight direction to proceed lastValidMazeSolutionElement.MazeGridpoint.DirectionsAvailable[directionProceededAtLastValidMazeGridPoint] = false; // Truncate the path back to the last valid point to take a new direction PathToSolveMaze = PathToSolveMaze.Where(p => p.StepNumber < lastValidMazeSolutionElement.StepNumber).ToList(); return(lastValidMazeSolutionElement.MazeGridpoint); } return(currentMazeGridpoint); } // The step numbers will be indexed starting with zero var stepNumber = PathToSolveMaze.Count; var mazeSolutionElement = new MazeSolutionElement { StepNumber = stepNumber, MazeGridpoint = currentMazeGridpoint, DirectionProceeded = directionToProceed }; PathToSolveMaze.Add(mazeSolutionElement); return(nextMazeGridpoint); }
protected void MarkClosedPaths(Dictionary <CartesianCoordinate, MazeGridpoint> mazeGridpoints) { foreach (var mazeGridpoint in mazeGridpoints.Values) { // For each available direction, check what paths are closed and mark them foreach (var direction in mazeGridpoint.DirectionsAvailable.OpenPaths) { if (MazeToSolve.IsValidPositionInMaze(mazeGridpoint, direction)) { var nextMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(mazeGridpoint, direction); if (MazeToSolve.CheckIfAtDeadEnd(nextMazeGridpoint) || nextMazeGridpoint.HasBeenVisited) { mazeGridpoint.DirectionsAvailable[direction] = false; } } } } }
/// <summary> /// Solves the maze using a brute force algorithm approach. /// </summary> public override void SolveMaze() { // Apply all pre-treament logics, if any PreTreatmentLogics.ForEach(p => p.PreTreatMazeToSolve()); // Set all parent gridpoints to null at the start var currentMazeGridpoint = MazeToSolve.MazeGridpoints.Values.First(m => m.IsStartPoint && m.DirectionsAvailable.Count > 0); // Keep on looking so long as we haven't reached the finish while (!MazeToSolve.CheckIfAtFinish(currentMazeGridpoint)) { currentMazeGridpoint.HasBeenVisited = true; // Picked a direction and go in that direction var directionToProceed = DirectionPickerLogic.PickDirectionToProceed(PathToSolveMaze, currentMazeGridpoint); if (directionToProceed == DirectionEnum.None) { throw new Exception("Maze Solver Error: Sorry, maze could not be solved. The maze image provided may be invalid."); } currentMazeGridpoint = DetermineNextMazeGridPoint(currentMazeGridpoint, directionToProceed); Console.WriteLine("X : " + currentMazeGridpoint.Position.X + ", Y : " + currentMazeGridpoint.Position.Y + " Direction : " + directionToProceed); MazeToSolve.NotifyMazeHasBeenUpdated(); MazeToSolve.NotifyMazeToBeRedrawnUpdated(); } // Once out of the loop, the current gridpoint will be the final gridpoint var finalMazeSolutionElement = new MazeSolutionElement { StepNumber = PathToSolveMaze.Count, MazeGridpoint = currentMazeGridpoint, DirectionProceeded = DirectionEnum.None }; PathToSolveMaze.Add(finalMazeSolutionElement); }
/// <summary> /// Removes redundant interior space in a maze. /// </summary> public override void PreTreatMazeToSolve() { var wallMazeGridpoints = MazeToSolve.MazeGridpoints .Where(m => m.Value.IsWall) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); // One round of the outer-loop below takes about 170 ms in debug mode. // For 5,500 gridpoints, that's about 15 minutes. if (wallMazeGridpoints.Count() > _maxiumGridpointsLimit) { return; } // First, focus only on the gridpoints that are walls or have been visited foreach (var mazeGridpoint in wallMazeGridpoints) { // Now for each direction, find the wall-adjacent gridpoints that are not walls themselves foreach (var direction in mazeGridpoint.Value.DirectionsAvailable.OpenPaths) { if (!MazeToSolve.IsValidPositionInMaze(mazeGridpoint.Value, direction)) { continue; } var canAvoidThisGridpoint = true; var wallAdjacentMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(mazeGridpoint.Value, direction); if (MazeToSolve.CheckIfAtDeadEnd(wallAdjacentMazeGridpoint)) { continue; } // For each gridpoint that is wall-adjacent and not a wall itself, check the gridpoints // at each of its four directions. foreach (var nextDirection in wallAdjacentMazeGridpoint.DirectionsAvailable.OpenPaths) { if (!MazeToSolve.IsValidPositionInMaze(wallAdjacentMazeGridpoint, nextDirection)) { continue; } var nextMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(wallAdjacentMazeGridpoint, nextDirection); if (MazeToSolve.CheckIfAtDeadEnd(nextMazeGridpoint)) { wallAdjacentMazeGridpoint.DirectionsAvailable[nextDirection] = false; } else { // If not at a dead-end go out a second layer of gridpoints from the gridpoints that // are around the wall-adjacent gridpoint foreach (var nextNextDirection in nextMazeGridpoint.DirectionsAvailable.OpenPaths) { if (!MazeToSolve.IsValidPositionInMaze(nextMazeGridpoint, nextNextDirection)) { continue; } var nextNextMazeGridpoint = MazeToSolve.ProceedToNewGridpoint(nextMazeGridpoint, nextNextDirection); if (MazeToSolve.CheckIfAtDeadEnd(nextNextMazeGridpoint)) { nextMazeGridpoint.DirectionsAvailable[nextNextDirection] = false; } } // If there's not at least two paths avaiable this far out, the wall-adjacent gridpoint // likely cannot be avoided without closing off a potential solution path if (nextMazeGridpoint.DirectionsAvailable.Count < 2) { canAvoidThisGridpoint = false; break; } } } // Assuming each of the second layer gridpoints had at least two open paths, make sure // that the wall-adjacent gridpoint has at least tree open paths if (!canAvoidThisGridpoint || wallAdjacentMazeGridpoint.DirectionsAvailable.Count < 3) { break; } // If everything above was satisfied, we can safely ignore the wall-adjacent gridpoint // without fear of closing off a solution path wallAdjacentMazeGridpoint.HasBeenVisited = true; MazeToSolve.NotifyMazeHasBeenUpdated(); MazeToSolve.NotifyMazeToBeRedrawnUpdated(); } } }