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);
}
}