diff --git a/Days/Day12.cs b/Days/Day12.cs new file mode 100644 index 0000000..708005b --- /dev/null +++ b/Days/Day12.cs @@ -0,0 +1,327 @@ +using Spectre.Console; + +namespace AdventOfCode.Days; + +public class Day12 : Day +{ + public override int Number => 12; + public override string Name => "Hill Climbing Algorithm"; + + public const int EndElevation = 25; + + // Color map of elevation: blue -> green -> yellow -> orange -> red + private readonly Color[] _colorMap = + { + Color.Blue1, + Color.DodgerBlue1, + Color.DeepSkyBlue1, + Color.Turquoise2, + Color.Cyan1, + Color.Cyan2, + Color.MediumSpringGreen, + Color.SpringGreen1, + Color.SpringGreen2_1, + Color.Green1, + Color.DarkOliveGreen2, + Color.GreenYellow, + Color.DarkOliveGreen1, + Color.Yellow2, + Color.Khaki1, + Color.Yellow1, + Color.NavajoWhite1, + Color.LightGoldenrod2_1, + Color.Gold1, + Color.SandyBrown, + Color.Orange1, + Color.DarkOrange, + Color.OrangeRed1, + Color.IndianRed1, + Color.HotPink, + Color.MediumOrchid1_1 + }; + + private (int x, int y) _end; + private (int x, int y) _start; + + public override void RunPart1() + { + var map = ParseMap(); + var canvas = DrawMap(map); + + // Test all possible solutions + var minimumPathLength = ExplorePath(map); + + // Draw solution path + foreach (var (x, y) in minimumPathLength.path) + { + canvas.SetPixel(x, y, Color.White); + } + + AnsiConsole.Write(canvas); + AnsiConsole.WriteLine(); + + AnsiConsole.MarkupLine($"[green]Minimum path length: [yellow]{minimumPathLength.length}[/][/]"); + } + + public override void RunPart2() + { + var map = ParseMap(); + var canvas = DrawMap(map); + + // Test all possible solutions + var minimumPathLength = FindNearestStart(map); + + // Draw solution path + foreach (var (x, y) in minimumPathLength.path) + { + canvas.SetPixel(x, y, Color.White); + } + + AnsiConsole.Write(canvas); + AnsiConsole.WriteLine(); + + AnsiConsole.MarkupLine($"[green]Minimum path length: [yellow]{minimumPathLength.length}[/][/]"); + } + + private (int length, List<(int x, int y)> path) ExplorePath(byte[,] map) + { + var height = map.GetLength(0); + var width = map.GetLength(1); + + // Store seen positions with number of steps to reach this position + var exploredPositions = new Dictionary<(int x, int y), int>(); + var toExplore = new Queue<(int x, int y, int elevation, int steps, List<(int x, int y)> path)>(); + var pathLengths = new List<(int length, List<(int x, int y)> path)>(); + + toExplore.Enqueue((_start.x, _start.y, 0, 0, new())); + + // Explore all possible path + while (toExplore.Count > 0) + { + var (x, y, previousElevation, steps, path) = toExplore.Dequeue(); + + // If we went out of bound + if (x < 0 || x >= width || y < 0 || y >= height) + { + continue; + } + + // If new elevation is too high + var newElevation = map[y, x]; + if (newElevation - previousElevation > 1) + { + continue; + } + + // If new path is not better than old path, just skip + if (exploredPositions.TryGetValue((x, y), out var oldSteps)) + { + if (oldSteps <= steps) + { + continue; + } + } + + // Mark this position as explored and store required number of steps to reach it + exploredPositions[(x, y)] = steps; + + // Update path + var newPath = new List<(int x, int y)>(path) { (x, y) }; + + // If we found the end, note that we could end the search here since it's a bfs, there can't be + // any shorter path to this point + if ((x, y) == _end) + { + pathLengths.Add((steps, newPath)); + + continue; + } + + var nextPositions = new (int x, int y)[] + { + (x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1) + }; + + foreach (var nextPosition in nextPositions) + { + toExplore.Enqueue((nextPosition.x, nextPosition.y, newElevation, steps + 1, newPath)); + } + } + + return pathLengths.MinBy(p => p.length); + } + + private (int length, List<(int x, int y)> path) FindNearestStart(byte[,] map) + { + var height = map.GetLength(0); + var width = map.GetLength(1); + + // Store seen positions with number of steps to reach this position + var exploredPositions = new Dictionary<(int x, int y), int>(); + var toExplore = new Queue<(int x, int y, int elevation, int steps, List<(int x, int y)> path)>(); + var pathLengths = new List<(int length, List<(int x, int y)> path)>(); + + toExplore.Enqueue((_end.x, _end.y, EndElevation, 0, new())); + + // Explore all possible path + while (toExplore.Count > 0) + { + var (x, y, previousElevation, steps, path) = toExplore.Dequeue(); + + // If we went out of bound + if (x < 0 || x >= width || y < 0 || y >= height) + { + continue; + } + + // If new elevation is too low, skip + var newElevation = map[y, x]; + if (previousElevation - newElevation > 1) + { + continue; + } + + // If new path is not better than old path, just skip + if (exploredPositions.TryGetValue((x, y), out var oldSteps)) + { + if (oldSteps <= steps) + { + continue; + } + } + + // Mark this position as explored and store required number of steps to reach it + exploredPositions[(x, y)] = steps; + + // Update path + var newPath = new List<(int x, int y)>(path) { (x, y) }; + + // If we found a possible start, same as in the 1st part, we could stop here + if (newElevation == 0) + { + pathLengths.Add((steps, newPath)); + + continue; + } + + var nextPositions = new (int x, int y)[] + { + (x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1) + }; + + foreach (var nextPosition in nextPositions) + { + toExplore.Enqueue((nextPosition.x, nextPosition.y, newElevation, steps + 1, newPath)); + } + } + + return pathLengths.MinBy(p => p.length); + } + + private byte[,] ParseMap() + { + var lines = Input.ReadAllLines().ToArray(); + var lineCount = lines.Length; + var columnCount = lines[0].Length; + + var map = new byte[lineCount, columnCount]; + + for (int i = 0; i < lineCount; i++) + { + var line = lines[i]; + + for (int j = 0; j < columnCount; j++) + { + var elevation = line[j]; + + map[i, j] = elevation switch + { + 'S' => 0, + 'E' => EndElevation, + _ => (byte) (elevation - 'a') + }; + + // Store the end + if (elevation == 'E') + { + _end = (j, i); + } + + // Store the start + if (elevation == 'S') + { + _start = (j, i); + } + } + } + + return map; + } + + private Canvas DrawMap(byte[,] map) + { + var height = map.GetLength(0); + var width = map.GetLength(1); + + var canvas = new Canvas(width, height); + + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + canvas.SetPixel(x, y, _colorMap[map[y, x]]); + } + } + + return canvas; + } + + #region Input + + public const string Input = + """ + abccccccaaccaaccccaaaaacccccaaaaccccccccccccccccccccccccccccccccaaaaaaaaaaaaaaaaaaaccccccccccccccccaaaccccccccccccaacccccccccccccccccccccccccccccccccccccccccaaaa + abaaaaccaaaaaccccaaaaaccccccaaaaccccccccccccccccccccaaacccccccccccaaaaaaaaaaaaaaaaaaccccccccccccccccaaaaccccccaaacaaccccccccccccccccccccccccccccccccccccccccaaaaa + abaaacccaaaaaaaacaaaaaacccccaaaaccccccccccccccccccccaaaaacccccccccaaaaaaaaaaaaaaaaacccccccccaaccccaaaaaacccccccaaaaaccccaaccccccccccccccacccccccccccccccccccaaaaa + abaaacccccaaaaaccccaaaaccccccaaacccccccccccccccccccaaaaaccccccccccaaaaaacacaaaaaacccccccccccaaccccaaaaacccccccccaaaaaaccaaaaaaccccccccccaaaccccacccccccccccaaaaaa + abaacccccaaaaaccccaacccccccccccccccaaaaacccccccccccaaaaacccccccccaaaaaaaaccaaaaaaacccccccaaaaaaaaccaaaaacccccccaaaaaaaccaaaaacccccccccccaaacccaaaccccccccccccccaa + abaaacccaaacaacccccccccccccccccccccaaaaaccccccccccccaaaaacccccccaaaaaaaaaccaaccaaacccccccaaaaaaaaccaacccccccccaaaaaaccaaaaaaccccccccccccaaaacaaaaccccccccccccccaa + abaaacccccccaaccccccccccccccccccccaaaaaaccccccccccccaaccccccaacccaaaccaaaaccccccaacccccccccaaaacccccccccccccccaacaaaccaaaaaaaccccccccccccajjjjjjjcccccccccccccccc + abcaacccccccccccccccccccccccccccccaaaaaaccccccccccccccccccccaaaaccccccaaaaccccccccccccccaacaaaaaccccccccccccccccccaaccccaaaaaacccccccccccjjjjjjjjjcccccaaaccccccc + abccccccccccccccccccccccccccccccccaaaaaaccaaccccccccccccccaaaaaacccccccaaacccccccccccaacaaaaaaaaccccccccccccccccccccccccaaccaaccccccccaiijjjjojjjjcccccaaacaccccc + abcccccccccccccccccccccccaaacccccccaaacacaaacccccccccccccccaaaaccccaaccccccccccccccccaaaaaaacccaccccccccccccccccccccccccaacccccccccccaiiijjooooojjkccaaaaaaaacccc + abccccccccccccccccccccccaaaaccccccccccaaaaaccccccccccccccccaaaaacccaaaaaccccccccccccccaaaaaacccccccccccccccccccccccccccccccccccccciiiiiiiioooooookkkcaaaaaaaacccc + abccccccccccccccccccccccaaaaccccccccccaaaaaaaacccccccccccccaacaaccaaaaacccccccaaacccaaaaaaaaccccccccccccccccccccccccccccccccccchiiiiiiiiooooouoookkkccaaaaaaccccc + abcccccccccaaccccccccccccaaaccccccccccccaaaaacccccccccccccccccccccaaaaaccccccaaaacccaaaaacaacccccccccccccaacaacccccccccccccccchhhiiiinnnooouuuuoookkkccaaaaaccccc + abcccccccccaaacccccccccccccccccccccccccaaaaacccccccccccccccccccccccaaaaacccccaaaaccccccaaccccccccccccccccaaaaacccccccccccccccchhhnnnnnnnnouuuuuuppkkkkaaaaaaccccc + abccccccaaaaaaaacccaaccccccccccccccccccaacaaccaacaaccccccccccccccccaacccccccccaaaccccccaacccccccccccccccaaaaacccccccccccccccchhhnnnnnnnnntuuxuuupppkkkkkacccccccc + abccccccaaaaaaaacacaaaacccccccccccccccccccaaccaaaaacccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaacccccccaacccccchhhnnnnttttttuxxxuuppppkkkkkcccccccc + abcccccccaaaaaaccaaaaaaccccccccccaaccccccccccaaaaaccccccccccccccccccccccccaaacccccccccccccccccccccccccccccaaaaccaaccaaacccaaahhhnnntttttttuxxxxuupppppllllccccccc + abcccccccaaaaaacccaaaacccccccccaaaaaaccccccccaaaaaacccccccccccccccccccccccaaacccccccccccccccccccccccccccccacccccaaaaaaacaaaaahhhppntttxxxxxxxxuuuuvpppplllccccccc + abcccccccaaaaaacccaaaacccccccccaaaaaacccccaaaaaaaaaccccccccccccccccccccaaaaaaaacccccccccccccccccccccaaaccccccaacaaaaaaccaaaaahhhpppttxxxxxxxxyuuvvvvvppplllcccccc + abcccccccaaccaacccaacaccaaaaccccaaaaacccccaaaaaaaaaccccccccccccccccccccaaaaaaaacccccccccccccccccccccaaacaaaaaaaccaaaaaaaaaaaaahhppptttxxxxxxyyyyyyvvvppplllcccccc + SbccccccccccccccccccccccaaaacccaaaaacccccccaaaaaaaaacaaaccccccccaacccccccaaaaaccccccccaaaaacccccccccaaaaaaaaaaaaaaaaaaaaaaaaacgggpppttxxxxEzzyyyyyvvvqqqlllcccccc + abccccccccccccccccccccccaaaacccaaaaacccccccaaaaaaaaccaaaccccccccaaacaaccaaaaaaccccccccaaaaacccccccaaaaaaaaaaaaaaaaaaaaaaaaaaacgggpppsssxxxyyyyyyvvvvvqqlllccccccc + abcccaaaccccccccccccccccaaaccccccccccccccccaaaaaaaaaaaaaccccccccaaaaaaccaaaaaacccccccaaaaaacccaaaccaaaaaccaaaaaaaaaaaacccccccccgggppssswwyyyyyyvvvvqqqqlllccccccc + abcaaaaaccccccccccccccccccccccccccccccccccaaaaaaaaaaaaacccccccaaaaaaacccaccaaacccccccaaaaaacccaaacccaaaaaaaaaaaccccaaacccaaaaacgggppsswwwyyyyyyvvqqqqqlllcccccccc + abcaaaaaaccccccccccccccccccccccccccccccccccaaccaaaaaaaaaaaccccaaaaaaacccccccccccccccccaaaaacccaaacaaaacaaaaaaaaccccaaacccaaaaacggpppsswwwywwyyyvvqqqmmmlccccccccc + abcaaaaaacccccccaacaaccccccccccccccccccccccccccaaaaaaaaaaaccccccaaaaacccccccccccccccccaaaccaaaaaaaaaaacccccccaacccccccccaaaaaacggpppsswwwwwwwwyvvqqqmmmcccccccccc + abcaaaaaccccccccaaaaaccccccccccccccccccccccccccccaaaaaaaacccccccaacaaacccccccccccccccccccccaaaaaaaaaccccccccccccccccccccaaaaaagggoossswwwwrrwwwvvqqmmmccccccccccc + abcaaaaacccccccaaaaaccccccccccccccccccccccccccccaaaaaaacccccccccaaccccccccccccccccccccccccccaaaaaaacccccccccccaaaccccccccaaaaagggooosssssrrrrwwwvqqmmmcccaacccccc + abcccccccccccccaaaaaaccccccccccccccccccccaacccccccccaaaccccccccccccccccccccccccccccccccccccccaaaaaaccccccccccccaaaaccccccaaaccgggooosssssrrrrrwwrrqmmmcccaacccccc + abcccccccccccccccaaaacccccccccccccccccccaaaacccccccacaaacccccccccccccccccccccccccccccccccccccaaaaaaacccccccccaaaaaacccccccccccgffoooooosoonrrrrrrrrmmmccaaaaacccc + abcccccccccccccccaccccccccccccccccccccccaaaacccccccaaaaacccccccccccccccccccccccccccccccccccccaaacaaacccccccccaaaaacccccccccccccfffoooooooonnnrrrrrmmmddcaaaaacccc + abccccccccccccccccccccccccccccccccccccccaaaaccccccccaaaaacccccccccccccccccccccccccaaaccccccccaacccccccccccccccaaaaaccccccccccccffffoooooonnnnnnrnnmmmdddaaaaacccc + abcccccccccccccccccccccccccccccccccccccccccccccccccaaaaaacccccccccccccccccaaaaaccaaaacccccccccccccccccccccccccaacccccccccccccccfffffffffeeeennnnnnmmdddaaaacccccc + abcccccccaaaccccccccaccccccccccccccccccccccccccccccaaaaccccccccccccaaaccccaaaaaccaaaaccccccccccccccccccccccccccccccccccccccccccccfffffffeeeeennnnnmddddaaaaaccccc + abcccaaccaaacccccaaaacccccaacccccccccccccccccccccccccaaacccccccccccaaacccaaaaaacccaaaccccccccccccccccccccccccccccccccccccccccccccccffffeeeeeeeedddddddcccaacccccc + abcccaaaaaaacccccaaaaaaccaaacccccccccccccccccccccccccccacccccccccccaaaaccaaaaaaccccccccccccccccccccccccccaacccccccccaaaccccccccccccccaaaaaaeeeeedddddcccccccccccc + abcccaaaaaacccccccaaaacccaaacaaaccccaaaacccccccccaaacaaaccccccaacccaaaacaaaaaaacccccccccccccccccccccccccaaaccccccccaaaacccccccccccccccccccaaaaeeddddccccccccccccc + abccccaaaaaaaacccaaaaaaaaaaaaaaaccccaaaacccccccccaaaaaaacccccaaacccaaaaaaaaaacccccccccccccccccccccccaaacaaaccccccccaaaaccccccccccccccccccccaaaccccccccccccccaaaca + abcccaaaaaaaaacccaacaaaaaaaaaaacccccaaaaccccccccccaaaaaacaaacaaacaaaaaaaaaacccccccccccccccaaacccccccaaaaaaaaaaccccccaaaccccccccccccccccccccaacccccccccccccccaaaaa + abcccaaaaaaaacccccccccccaaaaaacccccccaacccccccccccaaaaaaaaaaaaaaaaaaaaaaaaccccccccccccccccaaaacccccccaaaaaaaaacccccccccccccccccccccccccccccaaacccccccccccccccaaaa + abccaaaaaaacccccccccccccaaaaaaacccccccccccccccccaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccccccccccaaaacccccccaaaaaaaacccccccccccccccccccccccccccccccccccccccccccccccaaaaa + """; + + #endregion +} \ No newline at end of file diff --git a/Preview.png b/Preview.png deleted file mode 100644 index 5f8b62b..0000000 Binary files a/Preview.png and /dev/null differ diff --git a/Preview.webp b/Preview.webp new file mode 100644 index 0000000..f8d1a6d Binary files /dev/null and b/Preview.webp differ diff --git a/README.md b/README.md index 7af425a..80f963a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Advent of Code -![CLI Preview](Preview.png) +![CLI Preview](Preview.webp) Advent of Code made in **C#**, using **[Spectre.Console](https://spectreconsole.net/)** for **console display** and **[BenchmarkDotNet](https://benchmarkdotnet.org/)** for **benchmarks**