feat: solved day 16 part 2
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ReindeerMaze;
|
||||||
|
|
||||||
|
record MazePath(List<Tile> Tiles, int Score)
|
||||||
|
{
|
||||||
|
public Tile CurrentTile => Tiles.Last();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ReindeerMaze;
|
||||||
|
|
||||||
|
record Tile(Position Position, int DirectionIndex);
|
||||||
@@ -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. |
|
||||||
|
|||||||
Reference in New Issue
Block a user