diff --git a/src/AdventOfCode/Day17.cs b/src/AdventOfCode/Day17.cs index af24856..9a1d91f 100644 --- a/src/AdventOfCode/Day17.cs +++ b/src/AdventOfCode/Day17.cs @@ -12,20 +12,28 @@ public class Day17 public int Part1(string[] input) { LavaGrid grid = LavaGrid.Parse(input); - - // 870 - too low - someone else's answer :D so we're close - // 877 -- too low (just a guess from other possible answers from above - return grid.ShortestPath((0, 0), (grid.Width - 1, grid.Height - 1), LavaGrid.NextMovesPart1); + return grid.ShortestPath(0, 3); } public int Part2(string[] input) { LavaGrid grid = LavaGrid.Parse(input); - return grid.ShortestPath((0, 0), (grid.Width - 1, grid.Height - 1), LavaGrid.NextMovesPart2); + return grid.ShortestPath(4, 10); } + /// + /// Lava grid + /// + /// Grid of tile costs + /// Grid width + /// Grid height private record LavaGrid(int[,] Grid, int Width, int Height) { + /// + /// Parse the grid + /// + /// Input + /// Lava grid public static LavaGrid Parse(IReadOnlyList input) { int[,] grid = input.ToGrid(); @@ -33,18 +41,31 @@ public static LavaGrid Parse(IReadOnlyList input) return new LavaGrid(grid, input[0].Length, input.Count); } - public int ShortestPath(Point2D start, Point2D target, Func> nextMoves) + /// + /// Calculate the shortest valid path from the top left of the grid to the bottom right + /// within the given constraints + /// + /// Minimum moves before we're allowed to make a turn + /// Maximum moves before we must make a turn + /// Shortest valid path + /// No path found + public int ShortestPath(int minimumMoves, int maximumMoves) { - HashSet visited = new(); - Dictionary distance = new() + StepState startSouth = new((0, 1), Bearing.South, 1); + StepState startEast = new((1, 0), Bearing.East, 1); + Point2D target = (this.Width - 1, this.Height - 1); + + Dictionary distances = new() { - [new StepState(start, Bearing.South, 0)] = 0, - [new StepState(start, Bearing.East, 0)] = 0, + [startSouth] = this.Grid[1, 0], + [startEast] = this.Grid[0, 1], }; PriorityQueue queue = new(); - queue.Enqueue(new StepState(start, Bearing.South, 0), this.Grid[1, 0]); - queue.Enqueue(new StepState(start, Bearing.East, 0), this.Grid[0, 1]); + queue.Enqueue(startSouth, this.Grid[1, 0]); + queue.Enqueue(startEast, this.Grid[0, 1]); + + HashSet visited = new(); while (queue.Count > 0) { @@ -54,10 +75,10 @@ [new StepState(start, Bearing.East, 0)] = 0, if (current.Point == target) { // guaranteed to be shortest because the queue is ordered by cost - return distance[current]; + return distances[current]; } - foreach (StepState next in nextMoves(current)) + foreach (StepState next in NextMoves(current, minimumMoves, maximumMoves)) { if (!this.InBounds(next.Point)) { @@ -69,50 +90,38 @@ [new StepState(start, Bearing.East, 0)] = 0, continue; } - int newDistance = distance[current] + (this.Grid[next.Point.Y, next.Point.X]); + int nextDistance = distances[current] + this.Grid[next.Point.Y, next.Point.X]; // only move if it's cheaper than the current best path - if (!distance.TryGetValue(next, out int value) || newDistance < value) + if (!distances.TryGetValue(next, out int currentBest) || nextDistance < currentBest) { - distance[next] = newDistance; - queue.Enqueue(next, newDistance); + distances[next] = nextDistance; + queue.Enqueue(next, nextDistance); } } } - throw new InvalidOperationException($"No path found from {start} to {target}"); + throw new InvalidOperationException("No path found"); } - public static IEnumerable NextMovesPart1(StepState state) + /// + /// Enumerable the possible next moves from the current state + /// + /// Current state + /// Minimum moves before we're allowed to make a turn + /// Maximum moves before we must make a turn + /// Valid next moves + private static IEnumerable NextMoves(StepState state, int minimumMoves, int maximumMoves) { - if (state.Consecutive < 2) + if (state.Consecutive < minimumMoves) { yield return new StepState(state.Point.Move(state.Bearing), state.Bearing, state.Consecutive + 1); - } - switch (state.Bearing) - { - case Bearing.South or Bearing.North: - yield return new StepState(state.Point.Move(Bearing.West), Bearing.West, 0); - yield return new StepState(state.Point.Move(Bearing.East), Bearing.East, 0); - break; - - case Bearing.East or Bearing.West: - yield return new StepState(state.Point.Move(Bearing.North), Bearing.North, 0); - yield return new StepState(state.Point.Move(Bearing.South), Bearing.South, 0); - break; - } - } - - public static IEnumerable NextMovesPart2(StepState state) - { - if (state.Consecutive < 3) - { - yield return new StepState(state.Point.Move(state.Bearing), state.Bearing, state.Consecutive + 1); + // not met minimum yet, so can't make any further moves yield break; } - if (state.Consecutive < 9) + if (state.Consecutive < maximumMoves) { yield return new StepState(state.Point.Move(state.Bearing), state.Bearing, state.Consecutive + 1); } @@ -120,13 +129,13 @@ public static IEnumerable NextMovesPart2(StepState state) switch (state.Bearing) { case Bearing.South or Bearing.North: - yield return new StepState(state.Point.Move(Bearing.West), Bearing.West, 0); - yield return new StepState(state.Point.Move(Bearing.East), Bearing.East, 0); + yield return new StepState(state.Point.Move(Bearing.West), Bearing.West, 1); + yield return new StepState(state.Point.Move(Bearing.East), Bearing.East, 1); break; case Bearing.East or Bearing.West: - yield return new StepState(state.Point.Move(Bearing.North), Bearing.North, 0); - yield return new StepState(state.Point.Move(Bearing.South), Bearing.South, 0); + yield return new StepState(state.Point.Move(Bearing.North), Bearing.North, 1); + yield return new StepState(state.Point.Move(Bearing.South), Bearing.South, 1); break; } } @@ -140,6 +149,12 @@ private bool InBounds(Point2D point) => point.X >= 0 && point.X < this.Width && point.Y >= 0 && point.Y < this.Height; } + /// + /// Step state + /// + /// Current point + /// Bearing taken to enter this point (to prevent backtracking, which is not allowed) + /// Number of consecutive steps taken on this bearing private record StepState(Point2D Point, Bearing Bearing, int Consecutive); } }