feat: solved day 16 part 2

This commit is contained in:
Stevan Freeborn
2024-12-17 22:58:51 -06:00
parent 5b7dbd5b1d
commit 655b1ffd8f
8 changed files with 209 additions and 88 deletions
+66 -3
View File
@@ -2,11 +2,26 @@
public class PuzzleSolverTests public class PuzzleSolverTests
{ {
private async Task<string[]> GetPuzzleInput()
{
return await File.ReadAllLinesAsync(Path.Combine(AppContext.BaseDirectory, "INPUT.txt"));
}
[Test]
public async Task PartOneSolution_WhenCalledWithInput_ItShouldReturnExpectedValue()
{
var input = await GetPuzzleInput();
var result = PuzzleSolver.Solve(input);
await Assert.That(result).IsEqualTo(90460);
}
[Test] [Test]
[MethodDataSource(nameof(SolveTestCases))] [MethodDataSource(nameof(SolveTestCases))]
public async Task Solve_WhenCalledWithInput_ItShouldReturnExpectedValue(SolveTestCase testCase) public async Task Solve_WhenCalledWithInput_ItShouldReturnExpectedValue(SolveTestCase testCase)
{ {
var result = PuzzleSolver.Solve(testCase.Input); var result = PuzzleSolver.Solve(testCase.Input, testCase.IsPart2);
await Assert.That(result).IsEqualTo(testCase.ExpectedValue); await Assert.That(result).IsEqualTo(testCase.ExpectedValue);
} }
@@ -30,7 +45,8 @@ public class PuzzleSolverTests
"#.###.#.#.#.#.#", "#.###.#.#.#.#.#",
"#S..#.....#...#", "#S..#.....#...#",
"###############", "###############",
], ],
false,
7036 7036
); );
@@ -54,9 +70,56 @@ public class PuzzleSolverTests
"#S#.............#", "#S#.............#",
"#################", "#################",
], ],
false,
11048 11048
); );
yield return () => new(
[
"###############",
"#.......#....E#",
"#.#.###.#.###.#",
"#.....#.#...#.#",
"#.###.#####.#.#",
"#.#.#.......#.#",
"#.#.#####.###.#",
"#...........#.#",
"###.#.#####.#.#",
"#...#.....#.#.#",
"#.#.#.###.#.#.#",
"#.....#...#.#.#",
"#.###.#.#.#.#.#",
"#S..#.....#...#",
"###############",
],
true,
45
);
yield return () => new(
[
"#################",
"#...#...#...#..E#",
"#.#.#.#.#.#.#.#.#",
"#.#.#.#...#...#.#",
"#.#.#.#.###.#.#.#",
"#...#.#.#.....#.#",
"#.#.#.#.#.#####.#",
"#.#...#.#.#.....#",
"#.#.#####.#.###.#",
"#.#.#.......#...#",
"#.#.###.#####.###",
"#.#.#...#.....#.#",
"#.#.#.#####.###.#",
"#.#.#.........#.#",
"#.#.#.#########.#",
"#S#.............#",
"#################",
],
true,
64
);
} }
public record SolveTestCase(string[] Input, int ExpectedValue); public record SolveTestCase(string[] Input, bool IsPart2, int ExpectedValue);
} }
+9
View File
@@ -0,0 +1,9 @@
namespace ReindeerMaze;
record Direction(int Xd, int Yd)
{
public static readonly Direction Up = new(0, -1);
public static readonly Direction Down = new(0, 1);
public static readonly Direction Left = new(-1, 0);
public static readonly Direction Right = new(1, 0);
}
+6
View File
@@ -0,0 +1,6 @@
namespace ReindeerMaze;
record MazePath(List<Tile> Tiles, int Score)
{
public Tile CurrentTile => Tiles.Last();
}
+9
View File
@@ -0,0 +1,9 @@
namespace ReindeerMaze;
record Position(int X, int Y)
{
public Position GetNextPosition(Direction direction)
{
return new(X + direction.Xd, Y + direction.Yd);
}
}
+4 -85
View File
@@ -1,5 +1,7 @@
using System.Diagnostics; using System.Diagnostics;
using ReindeerMaze;
if (args.Length is 0) if (args.Length is 0)
{ {
Console.WriteLine("Please provide a path to the input file."); Console.WriteLine("Please provide a path to the input file.");
@@ -18,90 +20,7 @@ var input = await File.ReadAllLinesAsync(args[0]);
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Start();
var result = PuzzleSolver.Solve(input); var result = PuzzleSolver.Solve(input, isPart2);
stopwatch.Stop(); stopwatch.Stop();
Console.WriteLine($"The lowest score a Reindeer could get is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); Console.WriteLine($"The lowest score a Reindeer could get is {result}. ({stopwatch.ElapsedMilliseconds}ms)");
static class PuzzleSolver
{
public static int Solve(string[] input)
{
List<(int Xd, int Yd)> directions = [
(1, 0),
(0, 1),
(-1, 0),
(0, -1),
];
var rows = input.Length;
var columns = input[0].Length;
(int X, int Y) start = (-1, -1);
(int X, int Y) end = (-1, -1);
for (var row = 0; row < rows; row++)
{
for (var column = 0; column < columns; column++)
{
var currentValue = input[row][column];
switch (currentValue)
{
case 'S':
start = (column, row);
break;
case 'E':
end = (column, row);
break;
}
}
}
var queue = new PriorityQueue<(int X, int Y, int Direction, int Score), int>();
var visited = new HashSet<(int X, int Y, int Direction)>();
var initialScore = 0;
var initialDirection = 0;
queue.Enqueue((start.X, start.Y, initialDirection, initialScore), initialScore);
while (queue.Count is not 0)
{
var current = queue.Dequeue();
var possibleVisited = (current.X, current.Y, current.Direction);
if (visited.Contains((possibleVisited)))
{
continue;
}
visited.Add(possibleVisited);
if (current.X == end.X && current.Y == end.Y)
{
return current.Score;
}
var currentDirection = directions[current.Direction];
var nextX = current.X + currentDirection.Xd;
var nextY = current.Y + currentDirection.Yd;
if (nextX >= 0 && nextX < columns && nextY >= 0 && nextY < rows && input[nextY][nextX] != '#')
{
var newScoreAfterMovingForward = current.Score + 1;
queue.Enqueue((nextX, nextY, current.Direction, newScoreAfterMovingForward), newScoreAfterMovingForward);
}
var newScoreAfterTurning = current.Score + 1000;
var directionAfterClockWiseTurn = (current.Direction + 1) % directions.Count;
queue.Enqueue((current.X, current.Y, directionAfterClockWiseTurn, newScoreAfterTurning), newScoreAfterTurning);
var directionAfterCounterClockWiseTurn = (current.Direction + 3) % directions.Count;
queue.Enqueue((current.X, current.Y, directionAfterCounterClockWiseTurn, newScoreAfterTurning), newScoreAfterTurning);
}
return 0;
}
}
+111
View File
@@ -0,0 +1,111 @@
namespace ReindeerMaze;
static class PuzzleSolver
{
public static int Solve(string[] input, bool isPart2 = false)
{
List<Direction> directions = [
Direction.Right,
Direction.Down,
Direction.Left,
Direction.Up,
];
var rows = input.Length;
var columns = input[0].Length;
var startPosition = new Position(-1, -1);
var endPosition = new Position(-1, -1);
for (var row = 0; row < rows; row++)
{
for (var column = 0; column < columns; column++)
{
var currentValue = input[row][column];
switch (currentValue)
{
case 'S':
startPosition = new(column, row);
break;
case 'E':
endPosition = new(column, row);
break;
}
}
}
var queue = new PriorityQueue<MazePath, int>();
var visitedTiles = new HashSet<Tile>();
var initialScore = 0;
var initialDirection = 0;
var startTile = new Tile(startPosition, initialDirection);
var bestScore = int.MaxValue;
var bestPath = new MazePath([startTile], initialScore);
HashSet<Position> bestSeats = [startPosition];
queue.Enqueue(bestPath, initialScore);
while (queue.Count is not 0)
{
var path = queue.Dequeue();
var currentTile = path.CurrentTile;
var currentScore = path.Score;
if (currentScore > bestScore)
{
break;
}
if (currentTile.Position == endPosition)
{
bestScore = currentScore;
bestSeats.UnionWith(path.Tiles.Select(t => t.Position));
continue;
}
visitedTiles.Add(currentTile);
var currentDirection = directions[currentTile.DirectionIndex];
var nextPosition = currentTile.Position.GetNextPosition(currentDirection);
if (
nextPosition.X >= 0 &&
nextPosition.X < columns &&
nextPosition.Y >= 0 &&
nextPosition.Y < rows &&
input[nextPosition.Y][nextPosition.X] != '#'
)
{
var newScoreAfterMovingForward = currentScore + 1;
var nextTileAfterMovingForward = currentTile with { Position = nextPosition };
var nextPath = new MazePath([..path.Tiles, nextTileAfterMovingForward], newScoreAfterMovingForward);
if (visitedTiles.Contains(nextTileAfterMovingForward) is false)
{
queue.Enqueue(nextPath, newScoreAfterMovingForward);
}
}
var newScoreAfterTurning = currentScore + 1000;
var directionAfterClockWiseTurn = (currentTile.DirectionIndex + 1) % directions.Count;
var nextTileAfterClockWiseTurn = currentTile with { DirectionIndex = directionAfterClockWiseTurn };
if (visitedTiles.Contains(nextTileAfterClockWiseTurn) is false)
{
queue.Enqueue(new([..path.Tiles, nextTileAfterClockWiseTurn], newScoreAfterTurning), newScoreAfterTurning);
}
var directionAfterCounterClockWiseTurn = (currentTile.DirectionIndex + 3) % directions.Count;
var nextTileAfterCounterClockWiseTurn = currentTile with { DirectionIndex = directionAfterCounterClockWiseTurn };
if (visitedTiles.Contains(nextTileAfterCounterClockWiseTurn) is false)
{
queue.Enqueue(new([..path.Tiles, nextTileAfterCounterClockWiseTurn], newScoreAfterTurning), newScoreAfterTurning);
}
}
return isPart2 ? bestSeats.Count : bestScore;
}
}
+3
View File
@@ -0,0 +1,3 @@
namespace ReindeerMaze;
record Tile(Position Position, int DirectionIndex);
+1
View File
@@ -58,3 +58,4 @@ dotnet build ./bin/Debug/net9.0/<project-name> <path-to-input-file>
| 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/ClawContraption/) | Yay for algebra | | 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/ClawContraption/) | Yay for algebra |
| 14 | [PROBLEM](./14/PROBLEM.md) | [Solution](./14/RestroomRedoubt/) | Part 2 is not as complicated as you think. The answer is using the safety factor | | 14 | [PROBLEM](./14/PROBLEM.md) | [Solution](./14/RestroomRedoubt/) | Part 2 is not as complicated as you think. The answer is using the safety factor |
| 15 | [PROBLEM](./15/PROBLEM.md) | [Solution](./15/WarehouseWoes/) | This breaks my previous star record! | | 15 | [PROBLEM](./15/PROBLEM.md) | [Solution](./15/WarehouseWoes/) | This breaks my previous star record! |
| 16 | [PROBLEM](./16/PROBLEM.md) | [Solution](./16/ReindeerMaze/) | Bruh....part 2 can fuck off...but so glad I did not give up on it. |