/// <summary> /// This is the key method and houses the key parts of the algorithm. Initially, /// checks are made to the maze parameter that has been inputted from the 'Start' /// class. These either fail and return the relevant error message or pass and /// the process begins. The details of each part will be discussed in their /// relevant sections. /// </summary> /// <param name="maze"> </param> /// <returns> /// A string. /// If the maze parameter is valid, the maze directions. /// If not, an error message, the type of which depends on the maze parameter. /// </returns> public static string AStarSearch(List <String> maze) { // Checks if maze is empty. if (maze.Count == 0) { return("\nThe maze provided is empty, please input a valid Maze.\n"); } /* * Nested foreach loops, the first converts each string (line) to a char * array, allowing the second to go through each character and check if * the correct characters for this maze type are within (in this case; * A, B, x and .). */ var isAInMaze = false; var isBInMaze = false; var isXInMaze = false; var isDotInMaze = false; foreach (var line in maze) { var ca = line.ToCharArray(); foreach (var c in ca) { if (c.Equals('A')) { isAInMaze = true; } if (c.Equals('B')) { isBInMaze = true; } if (c.Equals('x')) { isXInMaze = true; } if (c.Equals('.')) { isDotInMaze = true; } } } // Checks if the maze is in invalid. if (isAInMaze == false | isBInMaze == false | isXInMaze == false | isDotInMaze == false) { return("\nThe maze provided is not valid, please input a valid Maze.\n"); } /* * Creates a new Node object called start and assigns it's Y value to * the index value of that which contains 'A' - the start point. This * process takes advantage of the C# LINQ framework (although the use * of LINQ is minimised as much possible for efficiency purposes). The * X value is then assigned using the value of Y as a reference point. */ var start = new Node { Y = maze.FindIndex(s => s.Contains("A")) }; start.X = maze[start.Y].IndexOf("A"); // This process is identical to the above. var end = new Node { Y = maze.FindIndex(s => s.Contains("B")) }; end.X = maze[end.Y].IndexOf("B"); /* * Here we create two queues, one called openPriorityQueue which is a * SimplePriorityQueue (this is a third-party data structure, designed * for pathfinding use cases - the reference of which can be found in * the ReadMe) and is used for queuing the nodes that may be visited. * This queue has a priority level for each node enqueued and it is * this priority level which is used to dictate which nodes shall be * visited at any given time. * * The second queue is called the ClosedQueue and it is a HashSet. This * queue is used to store all the previously visited nodes, and as * such grows rapidly in size in relation to maze complexity. A HashSet * was therefore chosen for its performance, and from testing the * difference between implementing it as a HashSet rather than a * SimplePriorityQueue was around 2-3x faster. */ var openPriorityQueue = new SimplePriorityQueue <Node>(); var closedQueue = new HashSet <Node>(); // Adds the start node to the openPriorityQueue. openPriorityQueue.Enqueue(start, 0); // Initialises an int called gCost. var gCost = 0; // The start of the main loop. while (openPriorityQueue.Count > 0) { /* * This assigns the node current to the value of the node at the top * of the queue, which should have the lowest fCost. */ var current = openPriorityQueue.First; /* * If the final node has been reached, assign the string variable to * the result of the PrintDirections method. This will contain the * NSEW directions and then return them. */ if (current.X == end.X && current.Y == end.Y) { var mazeDirections = PrintDirections(current); return(mazeDirections); } // Adds the current node to the closed queue (visited). closedQueue.Add(current); // Removes the head of the queue from the open queue. openPriorityQueue.Dequeue(); /* * If the destination is within the closed list, then the Path has * been found, and the while loop is broken. */ //var endFound = false; foreach (var node in closedQueue) { if ((node.X == end.X) && (node.Y == end.Y)) { var mazeDirections = PrintDirections(current); return(mazeDirections); } } /* * Assign the HashSet neighbours to the value of the GetNeighbours * method, which will contain 0-4 of the surrounding nodes. */ var neighbours = GetNeighbours(maze, current); // Increment the gCost. gCost = current.gCost + 1; /* * A foreach loop is used to iterate through the neighbours to find * the best path moving forward. */ foreach (var neighbour in neighbours) { // If the neighbour is already in the closed queue, it's ignored. var neighbourInClosedQueue = false; foreach (var node in closedQueue) { if ((node.X == neighbour.X) && (node.Y == neighbour.Y)) { neighbourInClosedQueue = true; break; } } if (neighbourInClosedQueue == true) { continue; } /* * If the neighbour is not within the open queue, then CalculateNodeCost * is called, which generates the costs of the node. The parent of the * neighbour is set as the current node being dealt with. Following this * a foreach loop is used to sort through the nodes of the open queue and * if the neighbour fCost is lower, then it's assigned a higher priority * (lower priority number), if the fCost is equal, it remains at the same * priority, and if the fCost is higher, it's assigned a lower priority * (high priority number). The initial if statement uses LINQ, as it was * the only appropriate way to check if a node within the open queue is * equal to neighbour's X and Y value. */ if (openPriorityQueue.FirstOrDefault(node => node.X == neighbour.X && node.Y == neighbour.Y) == null) { CalculateNodeCost(gCost, end, neighbour); neighbour.parent = current; openPriorityQueue.Enqueue(neighbour, 1); foreach (var node in openPriorityQueue) { if (node.fCost < neighbour.fCost) { openPriorityQueue.UpdatePriority(node, 0); } else if (node.fCost > neighbour.fCost) { openPriorityQueue.UpdatePriority(node, 2); } } } } } // If the maze cannot be solved, a message is returned saying so. return("\nThis maze cannot be escaped from...\n"); }
public ActionResult _CrearCSPF(CSPFViewModel newModel) { //Proyecto proyectoActual = new Proyecto(newModel.idProyecto); NodoDijkstra RouterOrigen = new NodoDijkstra(newModel.nRouterOrigen, newModel.idProyecto); SimplePriorityQueue<NodoDijkstra> routerQueue = new SimplePriorityQueue<NodoDijkstra>(); routerQueue = Dijkstra.GenerarRutas(RouterOrigen, newModel.idProyecto); NodoDijkstra RouterDestino = routerQueue.FirstOrDefault(x => x.idRouter == newModel.nRouterDestino); List<NodoDijkstra> result = new List<NodoDijkstra>(); result = Dijkstra.GetRutaMasCortaHasta(RouterDestino); return Json(1); }