public RecursiveDonutSearchNode(DonutGraph graph, string portal, int layer, string target, int cost, RecursiveDonutSearchNode parent = null) : base(cost, parent)
 {
     this.graph  = graph;
     this.portal = portal;
     this.layer  = layer;
     this.target = target;
 }
        private static (Maze <string>, DonutGraph) BuildDonutMaze(string[] input)
        {
            IEnumerable <IEnumerable <string> > mazeInput = input.Reverse().Select(s => s.Select(c => c.ToString()));

            Maze <string> maze = new Maze <string>(mazeInput, " ", new List <string> {
                "."
            }, false);

            //Set up some properties of the maze
            int thickness  = FindDonutThickness(input);
            int outerLeft  = 2;
            int outerRight = input[0].Length - 3;
            int outerTop   = input.Count() - 3;
            int outerBot   = 2;

            int innerLeft  = outerLeft + thickness;
            int innerRight = outerRight - thickness;
            int innerTop   = outerTop - thickness;
            int innerBot   = outerBot + thickness;

            var innerXBorder = new HashSet <int> {
                innerLeft, innerRight
            };
            var innerYBorder = new HashSet <int> {
                innerTop, innerBot
            };
            var outerXBorder = new HashSet <int> {
                outerLeft, outerRight
            };
            var outerYBorder = new HashSet <int> {
                outerTop, outerBot
            };

            //Locate the portals
            var floors = maze.FindAllMatchingTiles(".").ToList();

            HashSet <string> nonPortalLabels = new HashSet <string> {
                ".", " ", "#"
            };
            HashSet <string> portals = new HashSet <string>();

            foreach ((int x, int y) in floors)
            {
                bool isOuterPortal = outerXBorder.Contains(x) || outerYBorder.Contains(y);
                bool isInnerPortal = (innerXBorder.Contains(x) && y <innerTop && y> innerBot) ||
                                     (innerYBorder.Contains(y) && x <innerRight && x> innerLeft);
                if (isInnerPortal || isOuterPortal)
                {
                    var    part1 = maze.GetNeighbours(x, y).Where(kvp => !nonPortalLabels.Contains(kvp.Value)).First();
                    string portalName;
                    if (part1.Key == Direction.North)
                    {
                        portalName     = maze[x, y + 2] + maze[x, y + 1];
                        maze[x, y + 2] = " ";
                        maze[x, y + 1] = " ";
                    }
                    else if (part1.Key == Direction.East)
                    {
                        portalName     = maze[x + 1, y] + maze[x + 2, y];
                        maze[x + 1, y] = " ";
                        maze[x + 2, y] = " ";
                    }
                    else if (part1.Key == Direction.South)
                    {
                        portalName     = maze[x, y - 1] + maze[x, y - 2];
                        maze[x, y - 2] = " ";
                        maze[x, y - 1] = " ";
                    }
                    else if (part1.Key == Direction.West)
                    {
                        portalName     = maze[x - 2, y] + maze[x - 1, y];
                        maze[x - 1, y] = " ";
                        maze[x - 2, y] = " ";
                    }
                    else
                    {
                        throw new Exception("Shouldn't reach this");
                    }
                    portals.Add(portalName);
                    maze[x, y] = isInnerPortal ? portalName + ":IN" : portalName + ":OUT";
                }
            }

            //Create the graph
            var graph           = new DonutGraph(thickness);
            var existingPortals = maze.GetAllTileTypes().Where(tile => tile.Length > 1).ToHashSet();

            foreach (string portal in existingPortals)
            {
                graph.AddNode(portal);
            }

            foreach (var innerPortal in graph.Nodes().Where(portal => graph.IsInnerPortal(portal)))
            {
                var otherSide = graph.GetLabel(innerPortal) + ":OUT";
                if (graph.ContainsNode(otherSide))
                {
                    graph.AddEdge(innerPortal, otherSide, 1);
                }
            }

            //For every portal set up the reachable locations
            foreach (string portal in existingPortals)
            {
                (int x, int y) = ((int, int))maze.FindFirstMatchingTile(portal);
                (int _, List <(string tile, int distance)> reachablePortals) = maze.FloodFillDistanceFinder(x, y, existingPortals);
                foreach ((string reachable, int distance) in reachablePortals)
                {
                    graph.AddEdge(portal, reachable, distance);
                }
            }


            return(maze, graph);
        }