private HashSet <Cave> GetConnectionsHashSet(Cave currentCave)
        {
            HashSet <Cave> caveConnections = new HashSet <Cave>();

            // We iterate i number of times through the CaveConnection jagged array,
            // i being the number of caves. Doing this, we can find out what caves can currentCave
            // move to, and we add them to the caveConnection HashSet.
            for (int i = 0; i < CavesGrid.NumberCaves; i++)
            {
                if (CavesGrid.CaveConnections[i][currentCave.CaveNumber - 1] == 1)
                {
                    caveConnections.Add(HashCave.ElementAt(i));
                }
            }
            return(caveConnections);
        }
        public string SearchPath(int startingCave, int finishingCave)
        {
            // We iterate throughout all the caves and assign them a null parent, the cave number, calculate the F cost,
            // and add them to the HashSet HashCave.
            for (int i = 1; i <= finishingCave; i++)
            {
                Cave Cave = GetCaveObject(i);

                Cave.Parent = null;
                Cave.CalculateFCost();
                Cave.CaveNumber = i;
                HashCave.Add(Cave);
            }
            // We then set the starting cave as the first element in the HashSet, and the finishing cave
            // as the last element in the HashSet
            Cave fromCave = HashCave.ElementAt(startingCave - 1);
            Cave toCave   = HashCave.ElementAt(finishingCave - 1);

            // We set the boolean to true to be able to identify the goal cave
            toCave.IsLastCave = true;
            // We set the G Cost of the starting cave to 0, since it has no cost to travel from start cave
            // to start cave. We also calculate the H cost and the F cost.
            fromCave.GCost = 0;
            fromCave.HCost = Euclidean(fromCave, toCave);
            fromCave.CalculateFCost();

            // We initialise the OpenList and add the initial cave to it. We also initialise
            // the ClosedList
            OpenList = new HashSet <Cave> {
                fromCave
            };
            ClosedList = new HashSet <Cave>();

            // Here is where the algorithm takes place.
            // While the OpenList is not empty we will keep running the algorithm. If the OpenList is empty,
            // that means we didn't find a valid path from start to end cave.
            while (OpenList.Count > 0)
            {
                // We set the currentCave as the one with lowest f cost from the OpenList.
                // If the cave with the lowest f cost is the last cave, that means we found a valid path
                // so we call the function CalculatePath passing the last cave parameter in order to
                // track the path backwards, from the last cave to the initial cave
                Cave currentCave = GetLowestFCostCave(OpenList);
                if (currentCave.IsLastCave == true)
                {
                    return(CalculatePath(toCave));
                }

                // If the currentCave is not the last cave, we remove it from the OpenList and
                // add it to the ClosedList, this way we won't have to visit that node again, unless
                // it proves to have a lower f cost than the current cave.
                OpenList.Remove(currentCave);
                ClosedList.Add(currentCave);

                // We search for all the caves that have a connection with the currentCave
                foreach (Cave connectedCave in GetConnectionsHashSet(currentCave))
                {
                    // If the connectedCave is already in the ClosedList, we check if the f cost of the stored version
                    // has a lower or equal f cost, then we discard the currentCave. If no better version has been found, then
                    // we remove it from the ClosedList and set it as a parent of currentCave.
                    if (ClosedList.Contains(connectedCave))
                    {
                        continue;
                    }

                    // We set the travellingCost as the currentCave G cost plus the Euclidean heuristic from the currentCave to the connectedCave
                    double travellingCost = currentCave.GCost + Euclidean(currentCave, connectedCave);

                    // We check if the travellingCost is inferior to the G cost of the connectedCave. If true, we update the G Cost of the connectedCave
                    // to the travellingCost, calculate the H Cost and F Cost of the conectedCave, and set
                    // the currentCave as a pconnectedCave's parent.
                    if (travellingCost < connectedCave.GCost)
                    {
                        connectedCave.GCost = travellingCost;
                        connectedCave.HCost = Euclidean(connectedCave, toCave);
                        connectedCave.CalculateFCost();
                        connectedCave.Parent = currentCave;

                        // If the OpenList doesn't contain the connectedCave, we add it to the OpenList.
                        if (!OpenList.Contains(connectedCave))
                        {
                            OpenList.Add(connectedCave);
                        }
                    }
                }
            }
            // If the OpenList is empty, we return 0 as we didn't find a valid path.
            return("0");
        }