From 8e64d65e7da7ee07e0df0f649824a113619eed6a Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Mon, 16 Dec 2024 23:51:43 -0600 Subject: [PATCH] feat: solved day 15 part 2 --- 15/WarehouseWoes.Tests/PuzzleParserTests.cs | 11 + 15/WarehouseWoes.Tests/WarehouseMapTests.cs | 64 ++++ .../WideWarehouseMapTests.cs | 295 ++++++++++++++++++ 15/WarehouseWoes/Direction.cs | 26 ++ 15/WarehouseWoes/Map.cs | 58 ++++ 15/WarehouseWoes/Position.cs | 11 + 15/WarehouseWoes/Program.cs | 212 +------------ 15/WarehouseWoes/PuzzleParser.cs | 22 ++ 15/WarehouseWoes/WarehouseMap.cs | 137 ++++++++ 15/WarehouseWoes/WideWarehouseMap.cs | 129 ++++++++ README.md | 4 +- 11 files changed, 764 insertions(+), 205 deletions(-) create mode 100644 15/WarehouseWoes.Tests/WideWarehouseMapTests.cs create mode 100644 15/WarehouseWoes/Direction.cs create mode 100644 15/WarehouseWoes/Map.cs create mode 100644 15/WarehouseWoes/Position.cs create mode 100644 15/WarehouseWoes/PuzzleParser.cs create mode 100644 15/WarehouseWoes/WarehouseMap.cs create mode 100644 15/WarehouseWoes/WideWarehouseMap.cs diff --git a/15/WarehouseWoes.Tests/PuzzleParserTests.cs b/15/WarehouseWoes.Tests/PuzzleParserTests.cs index 287e42f..25f9ab8 100644 --- a/15/WarehouseWoes.Tests/PuzzleParserTests.cs +++ b/15/WarehouseWoes.Tests/PuzzleParserTests.cs @@ -27,4 +27,15 @@ public class PuzzleParserTests await Assert.That(result).IsEqualTo(1318523); } + + [Test] + public async Task PartTwoSolution_WhenGivenPuzzleInput_ItShouldReturnExpectedSolution() + { + var input = await GetPuzzleInput(); + var (map, directions) = PuzzleParser.Parse(input); + + var result = map.Scale().MoveRobot(directions).CalculateTotalBoxGpsCoordinates(); + + await Assert.That(result).IsEqualTo(1337648); + } } \ No newline at end of file diff --git a/15/WarehouseWoes.Tests/WarehouseMapTests.cs b/15/WarehouseWoes.Tests/WarehouseMapTests.cs index df3e5da..eb12188 100644 --- a/15/WarehouseWoes.Tests/WarehouseMapTests.cs +++ b/15/WarehouseWoes.Tests/WarehouseMapTests.cs @@ -43,6 +43,70 @@ public class WarehouseMapTests "<^^>>>vv>v<<", ]; + [Test] + [MethodDataSource(nameof(ScaleTestCases))] + public async Task Scale_WhenCalled_ItShouldReturnExpectedMap(ScaleTestCase testCase) + { + var expected = WideWarehouseMap.From(testCase.ExpectedMap); + + var result = WarehouseMap.From(testCase.MapInput).Scale(); + + await Assert.That(result.Positions).IsEquivalentTo(expected.Positions); + } + + public static IEnumerable> ScaleTestCases() + { + yield return () => new( + [ + "##########", + "#..O..O.O#", + "#......O.#", + "#.OO..O.O#", + "#..O@..O.#", + "#O#..O...#", + "#O..O..O.#", + "#.OO.O.OO#", + "#....O...#", + "##########", + ], + [ + "####################", + "##....[]....[]..[]##", + "##............[]..##", + "##..[][]....[]..[]##", + "##....[]@.....[]..##", + "##[]##....[]......##", + "##[]....[]....[]..##", + "##..[][]..[]..[][]##", + "##........[]......##", + "####################", + ] + ); + + yield return () => new( + [ + "#######", + "#...#.#", + "#.....#", + "#..OO@#", + "#..O..#", + "#.....#", + "#######", + ], + [ + "##############", + "##......##..##", + "##..........##", + "##....[][]@.##", + "##....[]....##", + "##..........##", + "##############", + ] + ); + } + + public record ScaleTestCase(string[] MapInput, string[] ExpectedMap); + [Test] [MethodDataSource(nameof(MoveTestCases))] public async Task Move_WhenCalled_ItShouldMoveValuesToExpectedPositions(MoveTestCase testCase) diff --git a/15/WarehouseWoes.Tests/WideWarehouseMapTests.cs b/15/WarehouseWoes.Tests/WideWarehouseMapTests.cs new file mode 100644 index 0000000..a90e723 --- /dev/null +++ b/15/WarehouseWoes.Tests/WideWarehouseMapTests.cs @@ -0,0 +1,295 @@ +namespace WarehouseWoes.Tests; + +public class WideWarehouseMapTests +{ + [Test] + [MethodDataSource(nameof(CalculateTotalBoxGpsCoordinatesTestCases))] + public async Task CalculateTotalBoxGpsCoordinates_WhenCalledWithExampleInput_ItShouldReturnExpectedValue(CalculateTotalBoxGpsCoordinatesTestCase testCase) + { + var map = WideWarehouseMap.From(testCase.MapInput); + + var result = map.CalculateTotalBoxGpsCoordinates(); + + await Assert.That(result).IsEqualTo(testCase.ExpectedValue); + } + + public static IEnumerable> CalculateTotalBoxGpsCoordinatesTestCases() + { + yield return () => new( + [ + "####################", + "##[].......[].[][]##", + "##[]...........[].##", + "##[]........[][][]##", + "##[]......[]....[]##", + "##..##......[]....##", + "##..[]............##", + "##..@......[].[][]##", + "##......[][]..[]..##", + "####################", + ], + 9021 + ); + } + + public record CalculateTotalBoxGpsCoordinatesTestCase(string[] MapInput, int ExpectedValue); + + [Test] + [MethodDataSource(nameof(MoveTestCases))] + public async Task Move_WhenCalled_ItShouldMoveValuesToExpectedPositions(MoveTestCase testCase) + { + var expected = WideWarehouseMap.From(testCase.ExpectedMap); + var directions = testCase.Directions.Select(Direction.From).ToList(); + + var result = WideWarehouseMap.From(testCase.MapInput).MoveRobot(directions); + + await Assert.That(result.Positions).IsEquivalentTo(expected.Positions); + } + + public static IEnumerable> MoveTestCases() + { + yield return () => new( + [ + "##############", + "##......##..##", + "##..........##", + "##....[][]@.##", + "##....[]....##", + "##..........##", + "##############", + ], + "<", + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]@..##", + "##....[]....##", + "##..........##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]@..##", + "##....[]....##", + "##..........##", + "##############", + ], + "v", + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]...##", + "##....[].@..##", + "##..........##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]...##", + "##....[].@..##", + "##..........##", + "##############", + ], + "v", + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]...##", + "##....[]....##", + "##.......@..##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]...##", + "##....[]....##", + "##.......@..##", + "##############", + ], + "<", + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]...##", + "##....[]....##", + "##......@...##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]...##", + "##....[]....##", + "##......@...##", + "##############", + ], + "<", + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]...##", + "##....[]....##", + "##.....@....##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##..........##", + "##...[][]...##", + "##....[]....##", + "##.....@....##", + "##############", + ], + "^", + [ + "##############", + "##......##..##", + "##...[][]...##", + "##....[]....##", + "##.....@....##", + "##..........##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##...[][]...##", + "##....[]....##", + "##.....@....##", + "##..........##", + "##############", + ], + "^", + [ + "##############", + "##......##..##", + "##...[][]...##", + "##....[]....##", + "##.....@....##", + "##..........##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##...[][]...##", + "##....[]....##", + "##.....@....##", + "##..........##", + "##############", + ], + "<", + [ + "##############", + "##......##..##", + "##...[][]...##", + "##....[]....##", + "##....@.....##", + "##..........##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##...[][]...##", + "##....[]....##", + "##....@.....##", + "##..........##", + "##############", + ], + "<", + [ + "##############", + "##......##..##", + "##...[][]...##", + "##....[]....##", + "##...@......##", + "##..........##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##...[][]...##", + "##....[]....##", + "##...@......##", + "##..........##", + "##############", + ], + "^", + [ + "##############", + "##......##..##", + "##...[][]...##", + "##...@[]....##", + "##..........##", + "##..........##", + "##############", + ] + ); + + yield return () => new( + [ + "##############", + "##......##..##", + "##...[][]...##", + "##...@[]....##", + "##..........##", + "##..........##", + "##############", + ], + "^", + [ + "##############", + "##...[].##..##", + "##...@.[]...##", + "##....[]....##", + "##..........##", + "##..........##", + "##############", + ] + ); + } + + public record MoveTestCase(string[] MapInput, string Directions, string[] ExpectedMap); +} \ No newline at end of file diff --git a/15/WarehouseWoes/Direction.cs b/15/WarehouseWoes/Direction.cs new file mode 100644 index 0000000..254aec8 --- /dev/null +++ b/15/WarehouseWoes/Direction.cs @@ -0,0 +1,26 @@ +namespace WarehouseWoes; + +record Direction(int Xd, int Yd) +{ + private const char UpCharacter = '^'; + private const char RightCharacter = '>'; + private const char DownCharacter = 'v'; + private const char LeftCharacter = '<'; + + private static readonly Direction Up = new(0, -1); + private static readonly Direction Down = new(0, 1); + public static readonly Direction Left = new(-1, 0); + public static readonly Direction Right = new(1, 0); + + public static Direction From(char character) + { + return character switch + { + UpCharacter => Up, + RightCharacter => Right, + DownCharacter => Down, + LeftCharacter => Left, + _ => throw new ArgumentException(@$"{nameof(character)} is not a valid: {character}"), + }; + } +} \ No newline at end of file diff --git a/15/WarehouseWoes/Map.cs b/15/WarehouseWoes/Map.cs new file mode 100644 index 0000000..978ae05 --- /dev/null +++ b/15/WarehouseWoes/Map.cs @@ -0,0 +1,58 @@ +using System.Text; + +namespace WarehouseWoes; + +abstract class Map(Dictionary positions) +{ + protected const char Robot = '@'; + protected const char Wall = '#'; + protected const char Box = 'O'; + protected const char Empty = '.'; + protected const char LeftBoxSide = '['; + protected const char RightBoxSide = ']'; + + public readonly Dictionary Positions = positions; + protected Position RobotPosition => Positions.First(kvp => kvp.Value is Robot).Key; + private int MaxX => Positions.Select(kvp => kvp.Key).Max(p => p.X); + private int MaxY => Positions.Select(kvp => kvp.Key).Max(p => p.Y); + public string[] MapGrid => ToString().Split(Environment.NewLine); + + public abstract int CalculateTotalBoxGpsCoordinates(); + public abstract Map MoveRobot(List directions); + public virtual Map Scale() => this; + + protected static Dictionary ConvertToDictionary(string[] input) + { + var positions = new Dictionary(); + + for (var y = 0; y < input.Length; y++) + { + for (var x = 0; x < input[0].Length; x++) + { + positions.Add(new(x, y), input[y][x]); + } + } + + return new(positions); + } + + public override string ToString() + { + var map = new StringBuilder(); + + for (var y = 0; y <= MaxY; y++) + { + var row = new StringBuilder(); + + for (var x = 0; x <= MaxX; x++) + { + var value = Positions[new(x, y)]; + row.Append(value); + } + + map.AppendLine(row.ToString()); + } + + return map.ToString(); + } +} \ No newline at end of file diff --git a/15/WarehouseWoes/Position.cs b/15/WarehouseWoes/Position.cs new file mode 100644 index 0000000..8c4e710 --- /dev/null +++ b/15/WarehouseWoes/Position.cs @@ -0,0 +1,11 @@ +namespace WarehouseWoes; + +record Position(int X, int Y) +{ + public int GpsCoordinate => (100 * Y) + X; + + public Position GetNextPosition(Direction direction) + { + return new(X + direction.Xd, Y + direction.Yd); + } +} \ No newline at end of file diff --git a/15/WarehouseWoes/Program.cs b/15/WarehouseWoes/Program.cs index b358dc7..9244d41 100644 --- a/15/WarehouseWoes/Program.cs +++ b/15/WarehouseWoes/Program.cs @@ -1,5 +1,6 @@ using System.Diagnostics; -using System.Text; + +using WarehouseWoes; if (args.Length is 0) { @@ -19,208 +20,13 @@ var input = await File.ReadAllTextAsync(args[0]); var stopwatch = new Stopwatch(); stopwatch.Start(); -var (map, directions) = PuzzleParser.Parse(input); +var (parsedMap, directions) = PuzzleParser.Parse(input); + +var map = isPart2 + ? parsedMap.Scale() + : parsedMap; + var result = map.MoveRobot(directions).CalculateTotalBoxGpsCoordinates(); stopwatch.Stop(); -Console.WriteLine($"The sum of all boxes' GPS coordinates is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); - -static class PuzzleParser -{ - public static (WarehouseMap Map, List Directions) Parse(string input) - { - var inputParts = input.Split($"{Environment.NewLine}{Environment.NewLine}"); - - var map = WarehouseMap.From(inputParts[0].Split(Environment.NewLine)); - - var directions = inputParts[1] - .Where(character => - { - var str = character.ToString(); - return str != "\r" && str != "\n" && str != "\r\n"; - }) - .Select(Direction.From) - .ToList(); - - return (map, directions); - } -} - -class WarehouseMap -{ - private const char Robot = '@'; - private const char Wall = '#'; - private const char Box = 'O'; - private const char Empty = '.'; - - public readonly Dictionary Positions; - - private WarehouseMap(Dictionary positions) - { - Positions = positions; - } - - public static WarehouseMap From(string[] input) - { - var positions = new Dictionary(); - - for (var y = 0; y < input.Length; y++) - { - for (var x = 0; x < input[0].Length; x++) - { - positions.Add(new(x, y), input[y][x]); - } - } - - return new(positions); - } - - public WarehouseMap MoveRobot(List directions) - { - var positions = Positions.ToDictionary(); - - foreach (var direction in directions) - { - var robotPosition = positions.First(kvp => kvp.Value is Robot).Key; - var nextPosition = robotPosition.GetNextPosition(direction); - var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue); - - if (hasNextPosition is false) - { - continue; - } - - switch (nextPositionValue) - { - case Wall: - continue; - case Empty: - positions[robotPosition] = Empty; - positions[nextPosition] = Robot; - continue; - case Box when CanPushBox(nextPosition, direction, positions): - positions = PushBox(nextPosition, direction, positions); - positions[robotPosition] = Empty; - positions[nextPosition] = Robot; - break; - } - } - - return new(positions); - } - - public int CalculateTotalBoxGpsCoordinates() - { - var boxes = Positions.Where(kvp => kvp.Value is Box).ToList(); - return boxes.Select(kvp => kvp.Key).Sum(p => p.GpsCoordinate); - } - - public override string ToString() - { - var maxX = Positions.Select(kvp => kvp.Key).Max(p => p.X); - var maxY = Positions.Select(kvp => kvp.Key).Max(p => p.Y); - - var map = new StringBuilder(); - - for (var y = 0; y <= maxY; y++) - { - var row = new StringBuilder(); - - for (var x = 0; x <= maxX; x++) - { - var value = Positions[new(x, y)]; - row.Append(value); - } - - map.AppendLine(row.ToString()); - } - - return map.ToString(); - } - - private static bool CanPushBox(Position boxPosition, Direction direction, Dictionary positions) - { - while (true) - { - var nextPosition = boxPosition.GetNextPosition(direction); - var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue); - - if (hasNextPosition is false) - { - return false; - } - - switch (nextPositionValue) - { - case Box: - boxPosition = nextPosition; - continue; - case Empty: - return true; - default: - return false; - } - } - } - - private static Dictionary PushBox(Position boxPosition, Direction direction, Dictionary currentPositions) - { - var positions = currentPositions.ToDictionary(); - - while (true) - { - var nextPosition = boxPosition.GetNextPosition(direction); - var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue); - - if (hasNextPosition is false) - { - return positions; - } - - if (nextPositionValue is Box) - { - boxPosition = nextPosition; - continue; - } - - positions[nextPosition] = Box; - - return positions; - } - } -} - -record Position(int X, int Y) -{ - public int GpsCoordinate => (100 * Y) + X; - - public Position GetNextPosition(Direction direction) - { - return new(X + direction.Xd, Y + direction.Yd); - } -} - -record Direction(int Xd, int Yd) -{ - private const char UpCharacter = '^'; - private const char RightCharacter = '>'; - private const char DownCharacter = 'v'; - private const char LeftCharacter = '<'; - - private static readonly Direction Up = new(0, -1); - private static readonly Direction Down = new(0, 1); - private static readonly Direction Left = new(-1, 0); - private static readonly Direction Right = new(1, 0); - - public static Direction From(char character) - { - return character switch - { - UpCharacter => Up, - RightCharacter => Right, - DownCharacter => Down, - LeftCharacter => Left, - _ => throw new ArgumentException(@$"{nameof(character)} is not a valid: {character}"), - }; - } -} \ No newline at end of file +Console.WriteLine($"The sum of all boxes' GPS coordinates is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file diff --git a/15/WarehouseWoes/PuzzleParser.cs b/15/WarehouseWoes/PuzzleParser.cs new file mode 100644 index 0000000..9e11ffb --- /dev/null +++ b/15/WarehouseWoes/PuzzleParser.cs @@ -0,0 +1,22 @@ +namespace WarehouseWoes; + +static class PuzzleParser +{ + public static (Map Map, List Directions) Parse(string input) + { + var inputParts = input.Split($"{Environment.NewLine}{Environment.NewLine}"); + + var map = WarehouseMap.From(inputParts[0].Split(Environment.NewLine)); + + var directions = inputParts[1] + .Where(character => + { + var str = character.ToString(); + return str != "\r" && str != "\n" && str != "\r\n"; + }) + .Select(Direction.From) + .ToList(); + + return (map, directions); + } +} \ No newline at end of file diff --git a/15/WarehouseWoes/WarehouseMap.cs b/15/WarehouseWoes/WarehouseMap.cs new file mode 100644 index 0000000..85814fe --- /dev/null +++ b/15/WarehouseWoes/WarehouseMap.cs @@ -0,0 +1,137 @@ +namespace WarehouseWoes; + +class WarehouseMap : Map +{ + private WarehouseMap(Dictionary positions) : base(positions) {} + + public static WarehouseMap From(string[] input) => new(ConvertToDictionary(input)); + + public override Map Scale() + { + var scaledPositions = new Dictionary(); + var scaleFactor = 2; + + foreach (var (position, value) in Positions) + { + var scaledX = position.X * scaleFactor; + var scaledY = position.Y; + + switch (value) + { + case Wall: + scaledPositions[new(scaledX, scaledY)] = Wall; + scaledPositions[new(scaledX + 1, scaledY)] = Wall; + break; + case Box: + scaledPositions[new(scaledX, scaledY)] = WideWarehouseMap.LeftBoxSide; + scaledPositions[new(scaledX + 1, scaledY)] = WideWarehouseMap.RightBoxSide; + break; + case Empty: + scaledPositions[new(scaledX, scaledY)] = Empty; + scaledPositions[new(scaledX + 1, scaledY)] = Empty; + break; + case Robot: + scaledPositions[new(scaledX, scaledY)] = Robot; + scaledPositions[new(scaledX + 1, scaledY)] = Empty; + break; + default: + throw new ApplicationException($"Unknown value present {value}"); + } + } + + return WideWarehouseMap.From(scaledPositions); + } + + public override Map MoveRobot(List directions) + { + var positions = Positions.ToDictionary(); + var robotPosition = RobotPosition; + + foreach (var direction in directions) + { + var nextPosition = robotPosition.GetNextPosition(direction); + var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue); + + if (hasNextPosition is false) + { + continue; + } + + switch (nextPositionValue) + { + case Wall: + continue; + case Empty: + positions[robotPosition] = Empty; + positions[nextPosition] = Robot; + robotPosition = nextPosition; + continue; + case Box when CanPushBox(nextPosition, direction, positions): + positions = PushBox(nextPosition, direction, positions); + positions[robotPosition] = Empty; + positions[nextPosition] = Robot; + robotPosition = nextPosition; + break; + } + } + + return new WarehouseMap(positions); + } + + public override int CalculateTotalBoxGpsCoordinates() + { + var boxes = Positions.Where(kvp => kvp.Value is Box).ToList(); + return boxes.Select(kvp => kvp.Key).Sum(p => p.GpsCoordinate); + } + + private static bool CanPushBox(Position boxPosition, Direction direction, Dictionary positions) + { + while (true) + { + var nextPosition = boxPosition.GetNextPosition(direction); + var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue); + + if (hasNextPosition is false) + { + return false; + } + + switch (nextPositionValue) + { + case Box: + boxPosition = nextPosition; + continue; + case Empty: + return true; + default: + return false; + } + } + } + + private static Dictionary PushBox(Position boxPosition, Direction direction, Dictionary currentPositions) + { + var positions = currentPositions.ToDictionary(); + + while (true) + { + var nextPosition = boxPosition.GetNextPosition(direction); + var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue); + + if (hasNextPosition is false) + { + return positions; + } + + if (nextPositionValue is Box) + { + boxPosition = nextPosition; + continue; + } + + positions[nextPosition] = Box; + + return positions; + } + } +} \ No newline at end of file diff --git a/15/WarehouseWoes/WideWarehouseMap.cs b/15/WarehouseWoes/WideWarehouseMap.cs new file mode 100644 index 0000000..d2f29ae --- /dev/null +++ b/15/WarehouseWoes/WideWarehouseMap.cs @@ -0,0 +1,129 @@ +namespace WarehouseWoes; + +class WideWarehouseMap : Map +{ + private WideWarehouseMap(Dictionary positions) : base(positions) {} + + public static WideWarehouseMap From(Dictionary positions) => new(positions); + public static WideWarehouseMap From(string[] input) => new(ConvertToDictionary(input)); + + public override Map MoveRobot(List directions) + { + var positions = Positions.ToDictionary(); + var robotPosition = RobotPosition; + + foreach (var direction in directions) + { + var nextPosition = robotPosition.GetNextPosition(direction); + var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue); + + if (hasNextPosition is false) + { + continue; + } + + switch (nextPositionValue) + { + case Wall: + continue; + case Empty: + positions[robotPosition] = Empty; + positions[nextPosition] = Robot; + robotPosition = nextPosition; + continue; + case LeftBoxSide: + case RightBoxSide: + var newPositions = PushBox(nextPosition, direction, positions); + + if (newPositions == positions) + { + break; + } + + positions = newPositions; + positions[robotPosition] = Empty; + positions[nextPosition] = Robot; + robotPosition = nextPosition; + break; + } + } + + return new WideWarehouseMap(positions); + } + + public override int CalculateTotalBoxGpsCoordinates() + { + var boxes = Positions.Where(kvp => kvp.Value is LeftBoxSide).ToList(); + return boxes.Select(kvp => kvp.Key).Sum(p => p.GpsCoordinate); + } + + private static Dictionary PushBox(Position boxPosition, Direction direction, Dictionary currentPositions) + { + var positions = currentPositions.ToDictionary(); + var boxPositions = new Queue(); + var visitedPositions = new HashSet(); + boxPositions.Enqueue(boxPosition); + + while (boxPositions.Count is not 0) + { + var currentPosition = boxPositions.Dequeue(); + + if (visitedPositions.Contains(currentPosition)) + { + continue; + } + + visitedPositions.Add(currentPosition); + + var nextPosition = currentPosition.GetNextPosition(direction); + var hasCurrentPosition = positions.TryGetValue(currentPosition, out var currentPositionValue); + var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue); + + if (hasCurrentPosition is false || hasNextPosition is false) + { + return currentPositions; + } + + if (nextPositionValue is Wall) + { + return currentPositions; + } + + switch (currentPositionValue) + { + case RightBoxSide: + { + var leftBoxSidePosition = currentPosition.GetNextPosition(Direction.Left); + boxPositions.Enqueue(leftBoxSidePosition); + break; + } + case LeftBoxSide: + { + var rightBoxSidePosition = currentPosition.GetNextPosition(Direction.Right); + boxPositions.Enqueue(rightBoxSidePosition); + break; + } + } + + if (nextPositionValue is Empty) + { + continue; + } + + boxPositions.Enqueue(nextPosition); + } + + foreach (var position in visitedPositions.Reverse()) + { + var nextPosition = position.GetNextPosition(direction); + + var currentValue = positions[position]; + var nextValue = positions[nextPosition]; + + positions[nextPosition] = currentValue; + positions[position] = nextValue; + } + + return positions; + } +} \ No newline at end of file diff --git a/README.md b/README.md index ef25377..12338ca 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,7 @@ dotnet run -- part2 You can also build the projects and run the executables directly. ```bash -dotnet build -./bin/Debug/net9.0/ +dotnet build ./bin/Debug/net9.0/ ``` ## Challenges @@ -58,3 +57,4 @@ dotnet build | 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/GardenGroups/) | CORNERS == SIDES | | 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! |