// See https://aka.ms/new-console-template for more information using System.Runtime.CompilerServices; Part01(); Part02(); void Part01() { Console.WriteLine("!! === Part 1 === !!"); Console.WriteLine(" === Example ==="); Tuple exampleStart, exampleEnd; var exampleHeightMap = HeightMap.LoadHeightMap("example-input.txt", out exampleStart, out exampleEnd); var exampleGraph = exampleHeightMap.ToGraph(); exampleGraph.PrintShortestPath(exampleStart, exampleEnd); Console.WriteLine(); Console.WriteLine(" === Puzzle ==="); Tuple puzzleStart, puzzleEnd; var puzzleHeightMap = HeightMap.LoadHeightMap("puzzle-input.txt", out puzzleStart, out puzzleEnd); var puzzleGraph = puzzleHeightMap.ToGraph(); puzzleGraph.PrintShortestPath(puzzleStart, puzzleEnd); } void Part02() { Console.WriteLine("!! === Part 2 === !!"); Console.WriteLine(" === Example ==="); Tuple exampleStart, exampleEnd; var exampleHeightMap = HeightMap.LoadHeightMap("example-input.txt", out exampleStart, out exampleEnd); var exampleGraph = exampleHeightMap.ToGraph(); Console.WriteLine("Shortest steps from anywhere: {0}", exampleGraph.GetShortestNumberOfStepsAnywhere(0, exampleEnd)); Console.WriteLine(); Console.WriteLine(" === Puzzle ==="); Tuple puzzleStart, puzzleEnd; var puzzleHeightMap = HeightMap.LoadHeightMap("puzzle-input.txt", out puzzleStart, out puzzleEnd); var puzzleGraph = puzzleHeightMap.ToGraph(); Console.WriteLine("Shortest steps from anywhere: {0}", puzzleGraph.GetShortestNumberOfStepsAnywhere(0, puzzleEnd)); } class GraphNode { public int Height { get; set; } public Tuple Coordinates { get; set; } public List AdjacentNodes { get; set; } = new List(); public bool Explored { get; set; } public GraphNode(int height, Tuple coordinates) { Height = height; Coordinates = coordinates; } public void Reset() { Explored = false; } } class Graph { public Dictionary, GraphNode> Vertices { get; set; } = new Dictionary, GraphNode>(); public GraphNode GetAt(Tuple index) { return Vertices[index]; } public int GetShortestNumberOfStepsAnywhere(int startingHeight, Tuple end) { int minSteps = int.MaxValue; foreach (var g in Vertices) { if (g.Value.Height == startingHeight) { minSteps = Math.Min(minSteps, GetShortestNumberOfSteps(g.Value.Coordinates, end)); } } return minSteps; } public GraphNode GetAt(int x, int y) { return GetAt(new Tuple(x, y)); } public int GetShortestNumberOfSteps(Tuple start, Tuple end) { Dictionary, Tuple> path = new Dictionary, Tuple>(); BFS(start, end, path); return path[end].Item2; } public void PrintShortestPath(Tuple start, Tuple end) { Console.WriteLine("Shortest path from ({0},{1}) to ({2},{3}): {4} steps", start.Item1, start.Item2, end.Item1, end.Item2, GetShortestNumberOfSteps(start, end)); } public bool BFS(Tuple start, Tuple end, Dictionary, Tuple> path) { Queue queue = new Queue(); int visited = 0; // label all verts as unexplored foreach (var g in Vertices) { g.Value.Reset(); path.Add(g.Value.Coordinates, new Tuple(null, int.MaxValue)); } // Get the root GraphNode root = GetAt(start); Vertices[root.Coordinates].Explored = true; path[root.Coordinates] = new Tuple(path[root.Coordinates].Item1, 0); queue.Enqueue(root); while (queue.Count != 0) { GraphNode v = queue.Dequeue(); // We done if we found it if (v.Coordinates.Equals(end)) { return true; } foreach (var w in v.AdjacentNodes) { if (!w.Explored) { visited++; //Console.WriteLine("Visited: ({0},{1}). Count: {2}", w.Coordinates.Item1, w.Coordinates.Item2, visited); w.Explored = true; path[w.Coordinates] = new Tuple(v, path[v.Coordinates].Item2 + 1); queue.Enqueue(w); } } } return false; } } class HeightMap { public Graph ToGraph() { Graph graph = new Graph(); // Add in all the verts for (int j = 0; j < heightmap.Count; j++) { for (int i = 0; i < heightmap[0].Count; i++) { GraphNode gn = new GraphNode(GetHeight(i, j), new Tuple(i, j)); graph.Vertices.Add(gn.Coordinates, gn); } } // Create the edges for (int j = 0; j < heightmap.Count; j++) { for (int i = 0; i < heightmap[0].Count; i++) { Tuple current = new Tuple(i, j); Tuple result = new Tuple(0, 0); char[] edges = { 'u', 'd', 'l', 'r' }; foreach (var edge in edges) { if (CanGo(edge, i, j, out result)) { graph.Vertices[current].AdjacentNodes.Add(graph.Vertices[result]); } } } } return graph; } private HeightMap() { } public int GetHeight(int x, int y) { return heightmap[y][x]; } public bool CanGo(char direction, int x, int y, out Tuple newSpot) { int currentHeight = GetHeight(x, y); switch (direction) { case 'u': newSpot = new Tuple(x, y - 1); return (y > 0 && currentHeight >= GetHeight(x, y - 1) - 1); case 'd': newSpot = new Tuple(x, y + 1); return (y < heightmap.Count - 1 && currentHeight >= GetHeight(x, y + 1) - 1); case 'l': newSpot = new Tuple(x - 1, y); return (x > 0 && currentHeight >= GetHeight(x - 1, y) - 1); case 'r': newSpot = new Tuple(x + 1, y); return (x < heightmap[0].Count - 1 && currentHeight >= GetHeight(x + 1, y) - 1); default: throw new ArgumentException(); } } private List> heightmap = new List>(); public static HeightMap LoadHeightMap(string filename, out Tuple outStartPoint, out Tuple outEndPoint) { HeightMap hm = new HeightMap(); Tuple? startPoint = null; Tuple? endPoint = null; using (StreamReader reader = System.IO.File.OpenText(filename)) { while (!reader.EndOfStream) { string? line = reader.ReadLine(); if (line == null) throw new InvalidDataException(); List row = new List(); foreach (char c in line) { if (!char.IsLetter(c)) throw new InvalidDataException(); if (c == 'S') { row.Add('a' - 'a'); startPoint = new Tuple(row.Count - 1, hm.heightmap.Count); } else if (c == 'E') { row.Add('z' - 'a'); endPoint = new Tuple(row.Count - 1, hm.heightmap.Count); } else { row.Add(c - 'a'); } } hm.heightmap.Add(row); } } if (startPoint == null || endPoint == null) throw new InvalidDataException(); outStartPoint = startPoint; outEndPoint = endPoint; return hm; } }