From 655b1ffd8f83aafd8f956d886b7304e97e1bdd06 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:58:51 -0600 Subject: [PATCH] feat: solved day 16 part 2 --- 16/ReindeerMaze.Tests/PuzzleSolverTests.cs | 69 ++++++++++++- 16/ReindeerMaze/Direction.cs | 9 ++ 16/ReindeerMaze/MazePath.cs | 6 ++ 16/ReindeerMaze/Position.cs | 9 ++ 16/ReindeerMaze/Program.cs | 89 +---------------- 16/ReindeerMaze/PuzzleSolver.cs | 111 +++++++++++++++++++++ 16/ReindeerMaze/Tile.cs | 3 + README.md | 1 + 8 files changed, 209 insertions(+), 88 deletions(-) create mode 100644 16/ReindeerMaze/Direction.cs create mode 100644 16/ReindeerMaze/MazePath.cs create mode 100644 16/ReindeerMaze/Position.cs create mode 100644 16/ReindeerMaze/PuzzleSolver.cs create mode 100644 16/ReindeerMaze/Tile.cs diff --git a/16/ReindeerMaze.Tests/PuzzleSolverTests.cs b/16/ReindeerMaze.Tests/PuzzleSolverTests.cs index 546a3f5..f36293b 100644 --- a/16/ReindeerMaze.Tests/PuzzleSolverTests.cs +++ b/16/ReindeerMaze.Tests/PuzzleSolverTests.cs @@ -2,11 +2,26 @@ public class PuzzleSolverTests { + private async Task 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] [MethodDataSource(nameof(SolveTestCases))] 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); } @@ -30,7 +45,8 @@ public class PuzzleSolverTests "#.###.#.#.#.#.#", "#S..#.....#...#", "###############", - ], + ], + false, 7036 ); @@ -54,9 +70,56 @@ public class PuzzleSolverTests "#S#.............#", "#################", ], + false, 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); } \ No newline at end of file diff --git a/16/ReindeerMaze/Direction.cs b/16/ReindeerMaze/Direction.cs new file mode 100644 index 0000000..0679c80 --- /dev/null +++ b/16/ReindeerMaze/Direction.cs @@ -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); +} \ No newline at end of file diff --git a/16/ReindeerMaze/MazePath.cs b/16/ReindeerMaze/MazePath.cs new file mode 100644 index 0000000..f181327 --- /dev/null +++ b/16/ReindeerMaze/MazePath.cs @@ -0,0 +1,6 @@ +namespace ReindeerMaze; + +record MazePath(List Tiles, int Score) +{ + public Tile CurrentTile => Tiles.Last(); +} \ No newline at end of file diff --git a/16/ReindeerMaze/Position.cs b/16/ReindeerMaze/Position.cs new file mode 100644 index 0000000..204fd5c --- /dev/null +++ b/16/ReindeerMaze/Position.cs @@ -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); + } +} \ No newline at end of file diff --git a/16/ReindeerMaze/Program.cs b/16/ReindeerMaze/Program.cs index be6e40e..a0b536e 100644 --- a/16/ReindeerMaze/Program.cs +++ b/16/ReindeerMaze/Program.cs @@ -1,5 +1,7 @@ using System.Diagnostics; +using ReindeerMaze; + if (args.Length is 0) { 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(); stopwatch.Start(); -var result = PuzzleSolver.Solve(input); +var result = PuzzleSolver.Solve(input, isPart2); stopwatch.Stop(); -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; - } -} \ No newline at end of file +Console.WriteLine($"The lowest score a Reindeer could get is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file diff --git a/16/ReindeerMaze/PuzzleSolver.cs b/16/ReindeerMaze/PuzzleSolver.cs new file mode 100644 index 0000000..16d3850 --- /dev/null +++ b/16/ReindeerMaze/PuzzleSolver.cs @@ -0,0 +1,111 @@ +namespace ReindeerMaze; + +static class PuzzleSolver +{ + public static int Solve(string[] input, bool isPart2 = false) + { + List 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(); + var visitedTiles = new HashSet(); + var initialScore = 0; + var initialDirection = 0; + var startTile = new Tile(startPosition, initialDirection); + var bestScore = int.MaxValue; + var bestPath = new MazePath([startTile], initialScore); + HashSet 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; + } +} \ No newline at end of file diff --git a/16/ReindeerMaze/Tile.cs b/16/ReindeerMaze/Tile.cs new file mode 100644 index 0000000..c87978c --- /dev/null +++ b/16/ReindeerMaze/Tile.cs @@ -0,0 +1,3 @@ +namespace ReindeerMaze; + +record Tile(Position Position, int DirectionIndex); \ No newline at end of file diff --git a/README.md b/README.md index 12338ca..d0933fa 100644 --- a/README.md +++ b/README.md @@ -58,3 +58,4 @@ dotnet build ./bin/Debug/net9.0/ | 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 | | 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. |