279 lines
8.6 KiB
C#
279 lines
8.6 KiB
C#
// 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<int, int> 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<int, int> 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<int, int> 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<int, int> 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<int, int> Coordinates { get; set; }
|
|
|
|
public List<GraphNode> AdjacentNodes { get; set; } = new List<GraphNode>();
|
|
|
|
public bool Explored { get; set; }
|
|
|
|
public GraphNode(int height, Tuple<int, int> coordinates)
|
|
{
|
|
Height = height;
|
|
Coordinates = coordinates;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
Explored = false;
|
|
}
|
|
}
|
|
|
|
class Graph
|
|
{
|
|
public Dictionary<Tuple<int, int>, GraphNode> Vertices { get; set; } = new Dictionary<Tuple<int, int>, GraphNode>();
|
|
|
|
public GraphNode GetAt(Tuple<int, int> index)
|
|
{
|
|
return Vertices[index];
|
|
}
|
|
|
|
public int GetShortestNumberOfStepsAnywhere(int startingHeight, Tuple<int, int> 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<int, int>(x, y));
|
|
}
|
|
|
|
public int GetShortestNumberOfSteps(Tuple<int, int> start, Tuple<int, int> end)
|
|
{
|
|
Dictionary<Tuple<int, int>, Tuple<GraphNode?, int>> path = new Dictionary<Tuple<int, int>, Tuple<GraphNode?, int>>();
|
|
|
|
BFS(start, end, path);
|
|
|
|
return path[end].Item2;
|
|
}
|
|
|
|
public void PrintShortestPath(Tuple<int, int> start, Tuple<int, int> 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<int,int> start, Tuple<int,int> end, Dictionary<Tuple<int, int>, Tuple<GraphNode?, int>> path)
|
|
{
|
|
Queue<GraphNode> queue = new Queue<GraphNode>();
|
|
int visited = 0;
|
|
|
|
// label all verts as unexplored
|
|
foreach (var g in Vertices)
|
|
{
|
|
g.Value.Reset();
|
|
path.Add(g.Value.Coordinates, new Tuple<GraphNode?, int>(null, int.MaxValue));
|
|
}
|
|
|
|
// Get the root
|
|
GraphNode root = GetAt(start);
|
|
|
|
Vertices[root.Coordinates].Explored = true;
|
|
path[root.Coordinates] = new Tuple<GraphNode?,int>(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<GraphNode?, int>(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<int, int>(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<int, int> current = new Tuple<int,int>(i, j);
|
|
Tuple<int, int> result = new Tuple<int, int>(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<int,int> newSpot)
|
|
{
|
|
int currentHeight = GetHeight(x, y);
|
|
|
|
switch (direction)
|
|
{
|
|
case 'u':
|
|
newSpot = new Tuple<int, int>(x, y - 1);
|
|
return (y > 0 && currentHeight >= GetHeight(x, y - 1) - 1);
|
|
|
|
case 'd':
|
|
newSpot = new Tuple<int, int>(x, y + 1);
|
|
return (y < heightmap.Count - 1 && currentHeight >= GetHeight(x, y + 1) - 1);
|
|
|
|
case 'l':
|
|
newSpot = new Tuple<int, int>(x - 1, y);
|
|
return (x > 0 && currentHeight >= GetHeight(x - 1, y) - 1);
|
|
|
|
case 'r':
|
|
newSpot = new Tuple<int, int>(x + 1, y);
|
|
return (x < heightmap[0].Count - 1 && currentHeight >= GetHeight(x + 1, y) - 1);
|
|
default:
|
|
throw new ArgumentException();
|
|
}
|
|
}
|
|
|
|
private List<List<int>> heightmap = new List<List<int>>();
|
|
|
|
public static HeightMap LoadHeightMap(string filename, out Tuple<int, int> outStartPoint, out Tuple<int, int> outEndPoint)
|
|
{
|
|
HeightMap hm = new HeightMap();
|
|
Tuple<int, int>? startPoint = null;
|
|
Tuple<int, int>? endPoint = null;
|
|
|
|
using (StreamReader reader = System.IO.File.OpenText(filename))
|
|
{
|
|
while (!reader.EndOfStream)
|
|
{
|
|
string? line = reader.ReadLine();
|
|
if (line == null) throw new InvalidDataException();
|
|
|
|
List<int> row = new List<int>();
|
|
|
|
foreach (char c in line)
|
|
{
|
|
if (!char.IsLetter(c)) throw new InvalidDataException();
|
|
|
|
if (c == 'S')
|
|
{
|
|
row.Add('a' - 'a');
|
|
startPoint = new Tuple<int, int>(row.Count - 1, hm.heightmap.Count);
|
|
}
|
|
else if (c == 'E')
|
|
{
|
|
row.Add('z' - 'a');
|
|
endPoint = new Tuple<int, int>(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;
|
|
}
|
|
} |