From abf19be8cdc16573914fde9874d563405d7438c2 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 7 Dec 2024 01:28:15 -0600 Subject: [PATCH] feat: solved day 6 part 1 and part 2 --- 06/GuardGallivant.Tests/MapTests.cs | 64 +++++++++ 06/GuardGallivant/Direction.cs | 9 ++ 06/GuardGallivant/GuardPosition.cs | 3 + 06/GuardGallivant/Map.cs | 208 ++++++++++++++++++++++++++++ 06/GuardGallivant/Move.cs | 9 ++ 06/GuardGallivant/Program.cs | 17 ++- README.md | 1 + 7 files changed, 306 insertions(+), 5 deletions(-) create mode 100644 06/GuardGallivant.Tests/MapTests.cs create mode 100644 06/GuardGallivant/Direction.cs create mode 100644 06/GuardGallivant/GuardPosition.cs create mode 100644 06/GuardGallivant/Map.cs create mode 100644 06/GuardGallivant/Move.cs diff --git a/06/GuardGallivant.Tests/MapTests.cs b/06/GuardGallivant.Tests/MapTests.cs new file mode 100644 index 0000000..6bb68c2 --- /dev/null +++ b/06/GuardGallivant.Tests/MapTests.cs @@ -0,0 +1,64 @@ +namespace GuardGallivant.Tests; + +public class MapTests +{ + private readonly string[] _exampleMap = [ + "....#.....", + ".........#", + "..........", + "..#.......", + ".......#..", + "..........", + ".#..^.....", + "........#.", + "#.........", + "......#...", + ]; + + private async Task GetPuzzleInput() + { + return await File.ReadAllLinesAsync(Path.Combine(AppContext.BaseDirectory, "INPUT.txt")); + } + + [Test] + public async Task PredictDistinctPositionsCount_WhenCalledWithExample_ItShouldReturnExpectedCount() + { + var map = new Map(_exampleMap); + + var result = map.PredictDistinctPositionsCount(); + + await Assert.That(result).IsEqualTo(41); + } + + [Test] + public async Task PredictDistinctPositionsCount_WhenCalledWithPuzzleInput_ItShouldReturnExpectedCount() + { + var input = await GetPuzzleInput(); + var map = new Map(input); + + var result = map.PredictDistinctPositionsCount(); + + await Assert.That(result).IsEqualTo(4789); + } + + [Test] + public async Task IdentifyNumberOfPlacementsForNewObstruction_WhenCalledWithExample_ItShouldReturnExpectedCount() + { + var map = new Map(_exampleMap); + + var result = map.IdentifyNumberOfPlacementsForNewObstruction(); + + await Assert.That(result).IsEqualTo(6); + } + + [Test] + public async Task IdentifyNumberOfPlacementsForNewObstruction_WhenCalledWithPuzzleInput_ItShouldReturnExpectedCount() + { + var input = await GetPuzzleInput(); + var map = new Map(input); + + var result = map.IdentifyNumberOfPlacementsForNewObstruction(); + + await Assert.That(result).IsEqualTo(1304); + } +} \ No newline at end of file diff --git a/06/GuardGallivant/Direction.cs b/06/GuardGallivant/Direction.cs new file mode 100644 index 0000000..248d30e --- /dev/null +++ b/06/GuardGallivant/Direction.cs @@ -0,0 +1,9 @@ +namespace GuardGallivant; + +record Direction(char Value) +{ + public static readonly Direction Up = new('^'); + public static readonly Direction Down = new('v'); + public static readonly Direction Left = new('<'); + public static readonly Direction Right = new('>'); +} \ No newline at end of file diff --git a/06/GuardGallivant/GuardPosition.cs b/06/GuardGallivant/GuardPosition.cs new file mode 100644 index 0000000..8a44e26 --- /dev/null +++ b/06/GuardGallivant/GuardPosition.cs @@ -0,0 +1,3 @@ +namespace GuardGallivant; + +record GuardPosition(int RowIndex, int ColumnIndex, Direction Direction); \ No newline at end of file diff --git a/06/GuardGallivant/Map.cs b/06/GuardGallivant/Map.cs new file mode 100644 index 0000000..a55c8f4 --- /dev/null +++ b/06/GuardGallivant/Map.cs @@ -0,0 +1,208 @@ +namespace GuardGallivant; + +class Map +{ + private static readonly Dictionary MovementDictionary = new() + { + { Direction.Up, Move.Up }, + { Direction.Down, Move.Down }, + { Direction.Left, Move.Left }, + { Direction.Right, Move.Right }, + }; + + private static readonly Direction[] Directions = MovementDictionary.Keys.ToArray(); + + private readonly string[] _input; + + public Map(string[] input) + { + _input = input; + } + + // TODO: This can be optimized I think. + // Right now I'm basically doing a flood fill...I think + // I'm finding all the places on the map where I can + // place the obstruction, placing, and then seeing if + // a loop is created when guard patrols map. + public int IdentifyNumberOfPlacementsForNewObstruction() + { + var count = 0; + + WalkMap((rowIndex, columnIndex) => + { + var currentCharacter = _input[rowIndex][columnIndex]; + + if (IsGuard(currentCharacter) || IsBlocked(currentCharacter)) + { + return; + } + + var mapWithNewObstruction = _input.ToArray(); + var row = mapWithNewObstruction[rowIndex].ToCharArray(); + row[columnIndex] = '#'; + mapWithNewObstruction[rowIndex] = new string(row); + + var hasLoop = new Map(mapWithNewObstruction).DetectLoop(); + + if (hasLoop) + { + count++; + } + }); + + return count; + } + + private bool DetectLoop() + { + var originalGuardPosition = GetGuardPosition(); + var currentGuardPosition = originalGuardPosition; + + var blockedPositions = new List(); + + while (true) + { + var nextMove = MovementDictionary[currentGuardPosition.Direction]; + + var nextGuardPosition = currentGuardPosition with + { + RowIndex = currentGuardPosition.RowIndex + nextMove.YOffset, + ColumnIndex = currentGuardPosition.ColumnIndex + nextMove.XOffset, + }; + + if (IsOffGrid(nextGuardPosition)) + { + return false; + } + + if (IsBlocked(nextGuardPosition)) + { + if (blockedPositions.Contains(nextGuardPosition)) + { + return true; + } + + blockedPositions.Add(nextGuardPosition); + + var newDirection = TurnRight(currentGuardPosition); + currentGuardPosition = currentGuardPosition with { Direction = newDirection }; + + continue; + } + + currentGuardPosition = nextGuardPosition; + } + } + + public int PredictDistinctPositionsCount() + { + var currentGuardPosition = GetGuardPosition(); + var uniquePositions = new HashSet<(int x, int y)>() + { + (currentGuardPosition.ColumnIndex, currentGuardPosition.RowIndex), + }; + + while (true) + { + var nextMove = MovementDictionary[currentGuardPosition.Direction]; + + var nextGuardPosition = currentGuardPosition with + { + RowIndex = currentGuardPosition.RowIndex + nextMove.YOffset, + ColumnIndex = currentGuardPosition.ColumnIndex + nextMove.XOffset, + }; + + if (IsOffGrid(nextGuardPosition)) + { + break; + } + + if (IsBlocked(nextGuardPosition)) + { + var newDirection = TurnRight(currentGuardPosition); + currentGuardPosition = currentGuardPosition with { Direction = newDirection }; + continue; + } + + currentGuardPosition = nextGuardPosition; + uniquePositions.Add((currentGuardPosition.ColumnIndex, currentGuardPosition.RowIndex)); + } + + return uniquePositions.Count; + } + + private GuardPosition GetGuardPosition() + { + var guardPosition = new GuardPosition(-1, -1, new Direction('X')); + + WalkMap((rowIndex, columnIndex) => + { + var possibleGuard = new Direction(_input[rowIndex][columnIndex]); + + if (Directions.Contains(possibleGuard) is false) + { + return; + } + + guardPosition = new GuardPosition(rowIndex, columnIndex, possibleGuard); + }); + + return guardPosition; + } + + private void WalkMap(Action callback) + { + for (var rowIndex = 0; rowIndex < _input.Length; rowIndex++) + { + var row = _input[rowIndex]; + + for (var columnIndex = 0; columnIndex < row.Length; columnIndex++) + { + callback(rowIndex, columnIndex); + } + } + } + + private bool IsOffGrid(GuardPosition position) + { + return position.RowIndex < 0 || + position.RowIndex > _input.Length - 1 || + position.ColumnIndex < 0 || + position.ColumnIndex > _input[0].Length - 1; + } + + private bool IsBlocked(GuardPosition position) + { + return _input[position.RowIndex][position.ColumnIndex] is '#'; + } + + private bool IsBlocked(char character) + { + return character is '#'; + } + + private bool IsGuard(char currentCharacter) + { + return Directions.Contains(new Direction(currentCharacter)); + } + + private static Direction TurnRight(GuardPosition guardPosition) + { + if (guardPosition.Direction == Direction.Up) + { + return Direction.Right; + } + + if (guardPosition.Direction == Direction.Right) + { + return Direction.Down; + } + + if (guardPosition.Direction == Direction.Down) + { + return Direction.Left; + } + + return Direction.Up; + } +} \ No newline at end of file diff --git a/06/GuardGallivant/Move.cs b/06/GuardGallivant/Move.cs new file mode 100644 index 0000000..3823cbb --- /dev/null +++ b/06/GuardGallivant/Move.cs @@ -0,0 +1,9 @@ +namespace GuardGallivant; + +record Move(int XOffset, int YOffset) +{ + public static readonly Move Up = new(0, -1); + public static readonly Move Down = new(0, 1); + public static readonly Move Left = new(-1, 0); + public static readonly Move Right = new(1, 0); +} \ No newline at end of file diff --git a/06/GuardGallivant/Program.cs b/06/GuardGallivant/Program.cs index 0aa47f4..97951f2 100644 --- a/06/GuardGallivant/Program.cs +++ b/06/GuardGallivant/Program.cs @@ -1,5 +1,7 @@ using System.Diagnostics; +using GuardGallivant; + if (args.Length is 0) { Console.WriteLine("Please provide a path to the input file."); @@ -13,14 +15,19 @@ if (File.Exists(args[0]) is false) } var isPart2 = args.Length is 2 && args[1] is "part2"; -var input = await File.ReadAllTextAsync(args[0]); +var input = await File.ReadAllLinesAsync(args[0]); var stopwatch = new Stopwatch(); stopwatch.Start(); -// TODO: Implement solution +var map = new Map(input); +var result = isPart2 + ? map.IdentifyNumberOfPlacementsForNewObstruction() + : map.PredictDistinctPositionsCount(); + +var msg = isPart2 + ? $"There are {result} positions for the new obstruction" + : $"The guard will visit {result} positions"; stopwatch.Stop(); - -// TODO: Print result -Console.WriteLine($". ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file +Console.WriteLine($"{msg}. ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file diff --git a/README.md b/README.md index dcb1721..bf58008 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,4 @@ dotnet build | 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/MullItOver/) | Thank goodness this wasn't a repeat of day 2. | | 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/CeresSearch/) | This was fun! Definitely was able to see the growth in my abilities here. | | 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/PrintQueue/) | A graph to the rescue! | +| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/GuardGallivant/) | I kind of brute forced part two... |