diff --git a/08/HauntedWasteland.Tests/GlobalUsings.cs b/08/HauntedWasteland.Tests/GlobalUsings.cs index 2d5b473..d6f0167 100644 --- a/08/HauntedWasteland.Tests/GlobalUsings.cs +++ b/08/HauntedWasteland.Tests/GlobalUsings.cs @@ -1,3 +1,3 @@ global using FluentAssertions; -global using Xunit; +global using Xunit; \ No newline at end of file diff --git a/09/MirageMaintenance.Tests/GlobalUsings.cs b/09/MirageMaintenance.Tests/GlobalUsings.cs index 2d5b473..d6f0167 100644 --- a/09/MirageMaintenance.Tests/GlobalUsings.cs +++ b/09/MirageMaintenance.Tests/GlobalUsings.cs @@ -1,3 +1,3 @@ global using FluentAssertions; -global using Xunit; +global using Xunit; \ No newline at end of file diff --git a/10/PipeMaze.Tests/GlobalUsings.cs b/10/PipeMaze.Tests/GlobalUsings.cs new file mode 100644 index 0000000..d6f0167 --- /dev/null +++ b/10/PipeMaze.Tests/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using FluentAssertions; + +global using Xunit; \ No newline at end of file diff --git a/10/PipeMaze.Tests/MazeTests.cs b/10/PipeMaze.Tests/MazeTests.cs new file mode 100644 index 0000000..87caba6 --- /dev/null +++ b/10/PipeMaze.Tests/MazeTests.cs @@ -0,0 +1,442 @@ +namespace PipeMaze.Tests; + +public class MazeTests +{ + + [Theory] + [MemberData(nameof(TestData.ParseTestData), MemberType = typeof(TestData))] + public void Parse_WhenGivenInput_ItShouldReturnExpectedMaze(string[] input, Maze expectedMaze, Pipe? expectedStartingPipe) + { + var maze = Maze.Parse(input); + + maze + .Should() + .BeEquivalentTo(expectedMaze); + + maze.StartingPipe + .Should() + .BeEquivalentTo(expectedStartingPipe); + } + + [Theory] + [MemberData(nameof(TestData.IdentifyPipeTypeTestData), MemberType = typeof(TestData))] + public void IdentifyPipeType_WhenGivenInput_ItShouldReturnExpectedPipeType(Maze maze, PipeType expectedPipeType) + { + maze + .IdentifyStartPipeType() + .Should() + .Be(expectedPipeType); + } + + [Theory] + [MemberData(nameof(TestData.GetLoopTestData), MemberType = typeof(TestData))] + public void GetLoop_WhenGivenInput_ItShouldReturnExpectedLoop(Maze maze, List expectedLoop) + { + maze + .GetLoop() + .Should() + .BeEquivalentTo(expectedLoop); + } + + [Theory] + [MemberData(nameof(TestData.FurthestStepFromStartTestData), MemberType = typeof(TestData))] + public void FurthestStepFromStart_WhenGivenInput_ItShouldReturnExpectedFurthestStepFromStart(Maze maze, int expectedFurthestStepFromStart) + { + maze.GetFarthestStepFromStart() + .Should() + .Be(expectedFurthestStepFromStart); + } + + [Theory] + [MemberData(nameof(TestData.AreaTestData), MemberType = typeof(TestData))] + public void Area_WhenGivenInput_ItShouldReturnExpectedArea(string[] input, int expectedArea) + { + Maze + .Parse(input) + .GetAreaOfLoop() + .Should() + .Be(expectedArea); + } + + public static class TestData + { + private static readonly string[] Input = File.ReadAllLines("INPUT.txt"); + + private static readonly string[] TestMazeInput = + { + ".....", + ".S-7.", + ".|.|.", + ".L-J.", + ".....", + }; + + private static readonly Maze TestMaze = + new( + 5, + 5, + [ + new('S', PipeType.Unknown, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + ] + ); + + public static IEnumerable AreaTestData => + new List + { + new object[] + { + new string[] + { + "...........", + ".S-------7.", + ".|F-----7|.", + ".||.....||.", + ".||.....||.", + ".|L-7.F-J|.", + ".|..|.|..|.", + ".L--J.L--J.", + "...........", + }, + 4, + }, + new object[] + { + new string[] + { + ".F----7F7F7F7F-7....", + ".|F--7||||||||FJ....", + ".||.FJ||||||||L7....", + "FJL7L7LJLJ||LJ.L-7..", + "L--J.L7...LJS7F-7L7.", + "....F-J..F7FJ|L7L7L7", + "....L7.F7||L7|.L7L7|", + ".....|FJLJ|FJ|F7|.LJ", + "....FJL-7.||.||||...", + "....L---J.LJ.LJLJ...", + }, + 8 + }, + new object[] + { + new string[] + { + "FF7FSF7F7F7F7F7F---7", + "L|LJ||||||||||||F--J", + "FL-7LJLJ||||||LJL-77", + "F--JF--7||LJLJ7F7FJ-", + "L---JF-JLJ.||-FJLJJ7", + "|F|F-JF---7F7-L7L|7|", + "|FFJF7L7F-JF7|JL---7", + "7-L-JL7||F7|L7F-7F7|", + "L.L7LFJ|||||FJL7||LJ", + "L7JLJL-JLJLJL--JLJ.L", + }, + 10 + }, + new object[] + { + Input, + 287, + } + }; + + public static IEnumerable FurthestStepFromStartTestData => + new List + { + new object[] + { + TestMaze, + 4 + }, + new object[] + { + new Maze( + 5, + 5, + [ + new('7', PipeType.DownAndLeft, 0, 0), + new('-', PipeType.UpAndRight, 0, 1), + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + new('-', PipeType.LeftAndRight, 0, 4), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + new('7', PipeType.DownAndLeft, 1, 4), + + new('S', PipeType.Unknown, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 2), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + new('L', PipeType.UpAndRight, 4, 3), + new('J', PipeType.UpAndLeft, 4, 4), + ] + ), + 8, + }, + new object[] + { + Maze.Parse(Input), + 6870, + } + }; + + public static IEnumerable GetLoopTestData => + new List + { + new object[] + { + TestMaze, + new List + { + new('S', PipeType.DownAndRight, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + } + }, + new object[] + { + new Maze( + 5, + 5, + [ + new('-', PipeType.LeftAndRight, 0, 0), + new('L', PipeType.UpAndRight, 0, 1), + new('|', PipeType.UpAndDown, 0, 2), + new('F', PipeType.DownAndRight, 0, 3), + new('7', PipeType.DownAndLeft, 0, 4), + + new('7', PipeType.DownAndLeft, 1, 0), + new('S', PipeType.Unknown, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 1, 4), + + new('L', PipeType.UpAndRight, 2, 0), + new('|', PipeType.UpAndDown, 2, 1), + new('7', PipeType.DownAndLeft, 2, 2), + new('|', PipeType.UpAndDown, 2, 3), + new('|', PipeType.UpAndDown, 2, 4), + + new('-', PipeType.LeftAndRight, 3, 0), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + new('|', PipeType.UpAndDown, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('|', PipeType.UpAndDown, 4, 1), + new('-', PipeType.LeftAndRight, 4, 2), + new('J', PipeType.UpAndLeft, 4, 3), + new('F', PipeType.DownAndRight, 4, 4), + ] + ), + new List + { + new('S', PipeType.DownAndRight, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + } + }, + new object[] + { + new Maze( + 5, + 5, + [ + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + + new('S', PipeType.Unknown, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + ] + ), + new List + { + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + + new('S', PipeType.DownAndRight, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + } + }, + new object[] + { + new Maze( + 5, + 5, + [ + new('7', PipeType.DownAndLeft, 0, 0), + new('-', PipeType.UpAndRight, 0, 1), + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + new('-', PipeType.LeftAndRight, 0, 4), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + new('7', PipeType.DownAndLeft, 1, 4), + + new('S', PipeType.Unknown, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 2), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + new('L', PipeType.UpAndRight, 4, 3), + new('J', PipeType.UpAndLeft, 4, 4), + ] + ), + new List + { + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + + new('S', PipeType.DownAndRight, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + } + } + }; + + public static IEnumerable IdentifyPipeTypeTestData => + new List + { + new object[] + { + TestMaze, + PipeType.DownAndRight, + } + }; + + public static IEnumerable ParseTestData => + new List + { + new object?[] + { + new string[] + { + ".....", + ".F-7.", + ".|.|.", + ".L-J.", + ".....", + }, + new Maze( + 5, + 5, + [ + new('F', PipeType.DownAndRight, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + ] + ), + null + }, + new object?[] + { + TestMazeInput, + new Maze( + 5, + 5, + [ + new('S', PipeType.Unknown, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + ] + ), + new Pipe('S', PipeType.Unknown, 1, 1) + } + }; + } +} \ No newline at end of file diff --git a/10/PipeMaze.Tests/PipeMaze.Tests.csproj b/10/PipeMaze.Tests/PipeMaze.Tests.csproj new file mode 100644 index 0000000..29d03a6 --- /dev/null +++ b/10/PipeMaze.Tests/PipeMaze.Tests.csproj @@ -0,0 +1,36 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + PreserveNewest + + + + diff --git a/10/PipeMaze/PipeMaze.csproj b/10/PipeMaze/PipeMaze.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/10/PipeMaze/PipeMaze.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/10/PipeMaze/Program.cs b/10/PipeMaze/Program.cs new file mode 100644 index 0000000..b85b6ff --- /dev/null +++ b/10/PipeMaze/Program.cs @@ -0,0 +1,571 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace PipeMaze; + +public class Program +{ + public static async Task Main(string[] args) + { + if (args.Length is 0) + { + Console.WriteLine("Please provide a path to the input file."); + return -1; + } + + if (File.Exists(args[0]) is false) + { + Console.WriteLine("The provided file does not exist."); + return -2; + } + + var isPart2 = args.Length > 1 && args[1] == "part2"; + var input = await File.ReadAllLinesAsync(args[0]); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var maze = Maze.Parse(input); + var result = isPart2 ? maze.GetAreaOfLoop() : maze.GetFarthestStepFromStart(); + + stopwatch.Stop(); + + if (isPart2) + { + Console.WriteLine($"The area of the loop is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); + } + else + { + Console.WriteLine($"The farthest step from the start is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); + } + + return result; + } +} + +/// +/// Represents a maze of pipes. +/// +/// The width of the maze. +/// The height of the maze. +/// The pipes in the maze. +/// An instance of . +public class Maze( + int width, + int height, + List pipes +) +{ + private static readonly Dictionary _pipes = new() + { + { '|', PipeType.UpAndDown }, + { '-', PipeType.LeftAndRight }, + { 'L', PipeType.UpAndRight }, + { 'J', PipeType.UpAndLeft }, + { '7', PipeType.DownAndLeft }, + { 'F', PipeType.DownAndRight }, + { 'S', PipeType.Unknown }, + }; + + private int Width { get; init; } = width; + private int Height { get; init; } = height; + + /// + /// Gets the pipes in the maze. + /// + public List Pipes { get; init; } = pipes; + + /// + /// Gets the starting pipe in the maze. + /// + public Pipe? StartingPipe => Pipes.FirstOrDefault(p => p.Type is PipeType.Unknown); + + /// + /// Gets the farthest step from the start. + /// + /// The farthest step from the start. + public int GetFarthestStepFromStart() => GetLoop().Count / 2; + + /// + /// Gets the area of the loop. + /// + /// The area of the loop. + public int GetAreaOfLoop() + { + var count = 0; + var loop = GetLoop(); + var areInsideLoop = false; + var verticalBoundaries = new List() + { + PipeType.UpAndDown, + PipeType.UpAndRight, + PipeType.UpAndLeft, + }; + + // Loop through each row + for (var row = 0; row < Height; row++) + { + // Loop through each column in the row + for (var column = 0; column < Width; column++) + { + // Check if a pipe in loop exists at the current coordinate + var pipe = loop.FirstOrDefault(p => p.Coordinate.Column == column && p.Coordinate.Row == row); + + // If a pipe does not exist at the current coordinate + // and we are inside the loop, increment the count + if (pipe is null && areInsideLoop is true) + { + count++; + } + + // If a pipe exists at the current coordinate and the pipe is + // representative of a vertical boundary, toggle the areInsideLoop flag + if ( + pipe is not null && + verticalBoundaries.Contains(pipe.Type) is true + ) + { + areInsideLoop = !areInsideLoop; + } + } + } + + return count; + } + + /// + /// Gets the loop in the maze. + /// + /// The loop in the maze. + public List GetLoop() + { + if (StartingPipe is null) + { + return []; + } + + var loop = new List(); + var startingPipeType = IdentifyStartPipeType(); + var currentPipe = new Pipe( + StartingPipe.Symbol, + startingPipeType, + StartingPipe.Coordinate.Row, + StartingPipe.Coordinate.Column + ); + + do + { + loop.Add(currentPipe); + + var connectingPipes = GetConnectingPipes(currentPipe); + currentPipe = connectingPipes + .FirstOrDefault( + p => + p is not null && + loop.Any(pipe => pipe.Coordinate.Equals(p.Coordinate)) is false + ); + + } while (currentPipe?.Equals(StartingPipe) is false); + + return loop; + } + + /// + /// Identifies the type of the starting pipe. + /// + /// The type of the starting pipe. + public PipeType IdentifyStartPipeType() + { + if (StartingPipe is null) + { + return PipeType.Unknown; + } + + var connectingPipes = GetConnectingPipes(StartingPipe); + var pipeAbove = connectingPipes.FirstOrDefault(p => p?.Coordinate.Row < StartingPipe.Coordinate.Row); + var pipeBelow = connectingPipes.FirstOrDefault(p => p?.Coordinate.Row > StartingPipe.Coordinate.Row); + var pipeToTheLeft = connectingPipes.FirstOrDefault(p => p?.Coordinate.Column < StartingPipe.Coordinate.Column); + var pipeToTheRight = connectingPipes.FirstOrDefault(p => p?.Coordinate.Column > StartingPipe.Coordinate.Column); + + var isPipeAboveConnected = pipeAbove is not null && + pipeAbove.Type is PipeType.UpAndDown or PipeType.DownAndRight or PipeType.DownAndLeft; + + var isPipeBelowConnected = pipeBelow is not null && + pipeBelow.Type is PipeType.UpAndDown or PipeType.UpAndRight or PipeType.UpAndLeft; + + var isPipeToTheLeftConnected = pipeToTheLeft is not null && + pipeToTheLeft.Type is PipeType.LeftAndRight or PipeType.DownAndRight or PipeType.UpAndRight; + + var isPipeToTheRightConnected = pipeToTheRight is not null && + pipeToTheRight.Type is PipeType.LeftAndRight or PipeType.DownAndLeft or PipeType.UpAndLeft; + + if (isPipeAboveConnected && isPipeBelowConnected && isPipeToTheLeftConnected && isPipeToTheRightConnected) + { + return PipeType.Unknown; + } + + if (isPipeAboveConnected && isPipeBelowConnected) + { + return PipeType.UpAndDown; + } + + if (isPipeToTheLeftConnected && isPipeToTheRightConnected) + { + return PipeType.LeftAndRight; + } + + if (isPipeAboveConnected && isPipeToTheRightConnected) + { + return PipeType.UpAndRight; + } + + if (isPipeAboveConnected && isPipeToTheLeftConnected) + { + return PipeType.UpAndLeft; + } + + if (isPipeBelowConnected && isPipeToTheRightConnected) + { + return PipeType.DownAndRight; + } + + if (isPipeBelowConnected && isPipeToTheLeftConnected) + { + return PipeType.DownAndLeft; + } + + return PipeType.Unknown; + } + + /// + /// Gets the connecting pipes of the provided pipe. + /// + /// The pipe to get the connecting pipes of. + /// The connecting pipes of the provided pipe. + public List GetConnectingPipes(Pipe pipe) + { + var connectingPipes = new List(); + var isNotOnFirstRow = pipe.Coordinate.Row > 0; + var isNotOnFirstColumn = pipe.Coordinate.Column > 0; + var isNotOnLastRow = pipe.Coordinate.Row < Height - 1; + var isNotOnLastColumn = pipe.Coordinate.Column < Width - 1; + var nextRow = pipe.Coordinate.Row + 1; + var nextColumn = pipe.Coordinate.Column + 1; + var previousRow = pipe.Coordinate.Row - 1; + var previousColumn = pipe.Coordinate.Column - 1; + + switch (pipe.Type) + { + case PipeType.UpAndDown: + { + if (isNotOnFirstRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == pipe.Coordinate.Column && + p.Coordinate.Row == previousRow + ) + ); + } + + if (isNotOnLastRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == pipe.Coordinate.Column && + p.Coordinate.Row == nextRow + ) + ); + } + + break; + } + case PipeType.LeftAndRight: + { + if (isNotOnFirstColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == previousColumn && + p.Coordinate.Row == pipe.Coordinate.Row + ) + ); + } + + if (isNotOnLastColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == nextColumn && + p.Coordinate.Row == pipe.Coordinate.Row + ) + ); + } + + break; + } + case PipeType.UpAndRight: + { + if (isNotOnFirstRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == pipe.Coordinate.Column && + p.Coordinate.Row == previousRow + ) + ); + } + + if (isNotOnLastColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == nextColumn && + p.Coordinate.Row == pipe.Coordinate.Row + ) + ); + } + + break; + } + case PipeType.UpAndLeft: + { + if (isNotOnFirstRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == pipe.Coordinate.Column && + p.Coordinate.Row == previousRow + ) + ); + } + + if (isNotOnFirstColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == previousColumn && + p.Coordinate.Row == pipe.Coordinate.Row + ) + ); + } + + break; + } + case PipeType.DownAndRight: + { + if (isNotOnLastRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == pipe.Coordinate.Column && + p.Coordinate.Row == nextRow + ) + ); + } + + if (isNotOnLastColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == nextColumn && + p.Coordinate.Row == pipe.Coordinate.Row + ) + ); + } + + break; + } + case PipeType.DownAndLeft: + { + if (isNotOnLastRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == pipe.Coordinate.Column && + p.Coordinate.Row == nextRow + ) + ); + } + + if (isNotOnFirstColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == previousColumn && + p.Coordinate.Row == pipe.Coordinate.Row + ) + ); + } + + break; + } + case PipeType.Unknown: + { + if (isNotOnFirstRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == pipe.Coordinate.Column && + p.Coordinate.Row == previousRow + ) + ); + } + + if (isNotOnLastColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == nextColumn && + p.Coordinate.Row == pipe.Coordinate.Row + ) + ); + } + + if (isNotOnLastRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == pipe.Coordinate.Column && + p.Coordinate.Row == nextRow + ) + ); + } + + if (isNotOnFirstColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault( + p => + p.Coordinate.Column == previousColumn && + p.Coordinate.Row == pipe.Coordinate.Row + ) + ); + } + + break; + } + default: + break; + } + + return connectingPipes; + } + + /// + /// Parses the provided input into a maze. + /// + /// The input to parse. + /// An instance of . + public static Maze Parse(string[] input) + { + var width = input[0].Length; + var height = input.Length; + var pipes = new List(); + + for (var row = 0; row < height; row++) + { + for (var column = 0; column < width; column++) + { + var symbol = input[row][column]; + + if (_pipes.TryGetValue(symbol, out PipeType value)) + { + pipes.Add(new(symbol, value, row, column)); + } + } + } + + return new Maze(width, height, pipes); + } +} + +/// +/// Represents a pipe in a maze. +/// +/// The symbol of the pipe. +/// The type of the pipe. +/// The row of the pipe. +/// The column of the pipe. +/// An instance of . +public class Pipe( + char symbol, + PipeType type, + int row, + int column +) +{ + /// + /// Gets the symbol of the pipe. + /// + public char Symbol { get; init; } = symbol; + + /// + /// Gets the type of the pipe. + /// + public PipeType Type { get; init; } = type; + + /// + /// Gets the coordinate of the pipe. + /// + public PipeCoordinate Coordinate { get; init; } = new(column, row); +} + +/// +/// Represents a coordinate of a pipe in a maze. +/// +/// The column of the pipe. +/// The row of the pipe. +/// An instance of . +public class PipeCoordinate( + int column, + int row +) : IEquatable +{ + /// + /// Gets the column of the pipe. + /// + public int Column { get; init; } = column; + + /// + /// Gets the row of the pipe. + /// + public int Row { get; init; } = row; + + /// + /// Determines whether the provided object is equal to the current object. + /// + public bool Equals(PipeCoordinate? other) => + other is not null && + Column == other.Column && + Row == other.Row; +} + +/// +/// Represents the type of a pipe in a maze. +/// +public enum PipeType +{ + UpAndDown, // Vertical Pipe + LeftAndRight, // Horizontal Pipe + UpAndRight, // North-East Pipe + UpAndLeft, // North-West Pipe + DownAndRight, // South-East Pipe + DownAndLeft, // South-West Pipe + Unknown, // Starting Pipe +} \ No newline at end of file diff --git a/AdventOfCode2023.sln b/AdventOfCode2023.sln index f963b6c..6ea177f 100644 --- a/AdventOfCode2023.sln +++ b/AdventOfCode2023.sln @@ -57,6 +57,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MirageMaintenance", "09\Mir EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MirageMaintenance.Tests", "09\MirageMaintenance.Tests\MirageMaintenance.Tests.csproj", "{5C39C115-ACBF-458A-9409-FBD23637A789}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10", "10", "{9703A36A-A019-4ADC-93A7-9DF4EFBCBFBC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PipeMaze", "10\PipeMaze\PipeMaze.csproj", "{B46ADCFF-35FF-496D-A035-FE2B9FFC185A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PipeMaze.Tests", "10\PipeMaze.Tests\PipeMaze.Tests.csproj", "{B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -138,6 +144,14 @@ Global {5C39C115-ACBF-458A-9409-FBD23637A789}.Debug|Any CPU.Build.0 = Debug|Any CPU {5C39C115-ACBF-458A-9409-FBD23637A789}.Release|Any CPU.ActiveCfg = Release|Any CPU {5C39C115-ACBF-458A-9409-FBD23637A789}.Release|Any CPU.Build.0 = Release|Any CPU + {B46ADCFF-35FF-496D-A035-FE2B9FFC185A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B46ADCFF-35FF-496D-A035-FE2B9FFC185A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B46ADCFF-35FF-496D-A035-FE2B9FFC185A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B46ADCFF-35FF-496D-A035-FE2B9FFC185A}.Release|Any CPU.Build.0 = Release|Any CPU + {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {3F8EAC09-4BC7-43AA-B72B-48DDD2710F6D} = {8C29858C-623A-461A-BF0B-254E151CD9C2} @@ -158,5 +172,7 @@ Global {FA8DDDD1-4DD9-4927-A9FB-C4FACDEAF901} = {F21CCF39-1F8A-40DA-A0E5-F1426B09BAA2} {6C233DE5-D1D0-46C0-A250-AF0272B495CE} = {79203C8D-F382-4C95-90EA-FECEE65602DA} {5C39C115-ACBF-458A-9409-FBD23637A789} = {79203C8D-F382-4C95-90EA-FECEE65602DA} + {B46ADCFF-35FF-496D-A035-FE2B9FFC185A} = {9703A36A-A019-4ADC-93A7-9DF4EFBCBFBC} + {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E} = {9703A36A-A019-4ADC-93A7-9DF4EFBCBFBC} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index df65687..7ceeeee 100644 --- a/README.md +++ b/README.md @@ -42,18 +42,18 @@ dotnet build ## Challenges -| Day | Problem | Solution | Status | Notes | -| --- | -------------------------- | :---------------------------------: | :----: | ------------------------------------------------------------------------------------------------------------------------------------ | -| 01 | [Problem](./01/PROBLEM.md) | [Solution](./01/Trebuchet/) | ✅ | The trickiest part here was accounting for overlapping digit words. | -| 02 | [Problem](./02/PROBLEM.md) | [Solution](./02/CubeConundrum/) | ✅ | The key to me here was to parse the input into a useful model. | -| 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/GearRatios/) | ✅ | The edge case that got me here was lines ending with a part number. | -| 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/Scratchcards/) | ✅ | Part 2 gets out of hand quickly with just 200 cards. | -| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/IYGASAF/) | ✅ | I brute forced part 2 using parallelism. I know shame. | -| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/WaitForIt/) | ✅ | Thank goodness part 2 was not like 5's part 2. 😅 | -| 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/CamelCards/) | ✅ | What took me longest here was I missed a case when jokers are wild and there are three groups of cards. | -| 08 | [Problem](./08/PROBLEM.md) | [Solution](./08/HauntedWasteland/) | ✅ | Got to implement a least common multiple algorithm for part 2. | -| 09 | [Problem](./09/PROBLEM.md) | [Solution](./09/MirageMaintenance/) | ✅ | Part 1 took me unnecessarily long. The bug was summing a line to check for all zeros is bad idea when negative numbers are involved. | -| 10 | [Problem](./10/PROBLEM.md) | [Solution](./10/) | ⌛ | +| Day | Problem | Solution | Status | Notes | +| --- | -------------------------- | :---------------------------------: | :----: | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| 01 | [Problem](./01/PROBLEM.md) | [Solution](./01/Trebuchet/) | ✅ | The trickiest part here was accounting for overlapping digit words. | +| 02 | [Problem](./02/PROBLEM.md) | [Solution](./02/CubeConundrum/) | ✅ | The key to me here was to parse the input into a useful model. | +| 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/GearRatios/) | ✅ | The edge case that got me here was lines ending with a part number. | +| 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/Scratchcards/) | ✅ | Part 2 gets out of hand quickly with just 200 cards. | +| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/IYGASAF/) | ✅ | I brute forced part 2 using parallelism. I know shame. | +| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/WaitForIt/) | ✅ | Thank goodness part 2 was not like 5's part 2. 😅 | +| 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/CamelCards/) | ✅ | What took me longest here was I missed a case when jokers are wild and there are three groups of cards. | +| 08 | [Problem](./08/PROBLEM.md) | [Solution](./08/HauntedWasteland/) | ✅ | Got to implement a least common multiple algorithm for part 2. | +| 09 | [Problem](./09/PROBLEM.md) | [Solution](./09/MirageMaintenance/) | ✅ | Part 1 took me unnecessarily long. The bug was summing a line to check for all zeros is bad idea when negative numbers are involved. | +| 10 | [Problem](./10/PROBLEM.md) | [Solution](./10/PipeMaze/) | ✅ | So...my solutions are rather slow, but they work. Part 2 appears to have a mathematical solution, but scanning is what I came up with on my own. | | 11 | [Problem](./11/PROBLEM.md) | [Solution](./11/) | ⌛ | | 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/) | ⌛ | | 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/) | ⌛ |