From bfff85956ed4c7c074967744d7510453dbdecf9d Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sun, 15 Dec 2024 13:50:34 -0600 Subject: [PATCH] feat: solved day 14 part 1 --- 14/RestroomRedoubt.Tests/RobotTests.cs | 73 +++++++++++ 14/RestroomRedoubt.Tests/SimulationTests.cs | 42 ++++++ 14/RestroomRedoubt/Program.cs | 138 +++++++++++++++++++- 3 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 14/RestroomRedoubt.Tests/RobotTests.cs create mode 100644 14/RestroomRedoubt.Tests/SimulationTests.cs diff --git a/14/RestroomRedoubt.Tests/RobotTests.cs b/14/RestroomRedoubt.Tests/RobotTests.cs new file mode 100644 index 0000000..d93a842 --- /dev/null +++ b/14/RestroomRedoubt.Tests/RobotTests.cs @@ -0,0 +1,73 @@ +namespace RestroomRedoubt.Tests; + +public class RobotTests +{ + [Test] + [MethodDataSource(nameof(FromTestCases))] + public async Task From_WhenCalledWithInput_ItShouldReturnExpectedRobot(FromTestCase testCase) + { + var expected = Robot.From( + testCase.ExpectedPosition.X, + testCase.ExpectedPosition.Y, + testCase.ExpectedVelocity.X, + testCase.ExpectedVelocity.Y + ); + + var result = Robot.From(testCase.Input); + + await Assert.That(result).IsEqualTo(expected); + } + + [Test] + [MethodDataSource(nameof(MoveTestCases))] + public async Task Move_WhenCalled_ItShouldMoveRobotToExpectedPosition(MoveTestCase testCase) + { + var robot = Robot.From( + testCase.CurrentPosition.X, + testCase.CurrentPosition.Y, + testCase.Velocity.X, + testCase.Velocity.Y + ); + + var result = robot.Move(testCase.Area.Width, testCase.Area.Height); + + await Assert.That(result).IsEqualTo(testCase.ExpectedNextPosition); + } + + public static IEnumerable> MoveTestCases() + { + var testVelocity = (2, -3); + var testArea = (11, 7); + + yield return () => new((2, 4), testVelocity, testArea, (4, 1)); + yield return () => new((4, 1), testVelocity, testArea, (6, 5)); + yield return () => new((6, 5), testVelocity, testArea, (8, 2)); + yield return () => new((8, 2), testVelocity, testArea, (10, 6)); + yield return () => new((10, 6), testVelocity, testArea, (1, 3)); + } + + public record MoveTestCase( + (int X, int Y) CurrentPosition, + (int X, int Y) Velocity, + (int Width, int Height) Area, + (int X, int Y) ExpectedNextPosition + ); + + public static IEnumerable> FromTestCases() + { + yield return () => new("p=0,4 v=3,-3", (0, 4), (3, -3)); + yield return () => new("p=6,3 v=-1,-3", (6, 3), (-1, -3)); + yield return () => new("p=10,3 v=-1,2", (10, 3), (-1, 2)); + yield return () => new("p=2,0 v=2,-1", (2, 0), (2, -1)); + yield return () => new("p=0,0 v=1,3", (0, 0), (1, 3)); + yield return () => new("p=3,0 v=-2,-2", (3, 0), (-2, -2)); + yield return () => new("p=7,6 v=-1,-3", (7, 6), (-1, -3)); + yield return () => new("p=3,0 v=-1,-2", (3, 0), (-1, -2)); + yield return () => new("p=9,3 v=2,3", (9, 3), (2, 3)); + yield return () => new("p=7,3 v=-1,2", (7, 3), (-1, 2)); + yield return () => new("p=2,4 v=2,-3", (2, 4), (2, -3)); + yield return () => new("p=9,5 v=-3,-3", (9, 5), (-3, -3)); + } + + public record FromTestCase(string Input, (int X, int Y) ExpectedPosition, (int X, int Y) ExpectedVelocity); +} \ No newline at end of file diff --git a/14/RestroomRedoubt.Tests/SimulationTests.cs b/14/RestroomRedoubt.Tests/SimulationTests.cs new file mode 100644 index 0000000..3e63b90 --- /dev/null +++ b/14/RestroomRedoubt.Tests/SimulationTests.cs @@ -0,0 +1,42 @@ +namespace RestroomRedoubt.Tests; + +public class SimulationTests +{ + private readonly string[] _exampleInput = [ + "p=0,4 v=3,-3", + "p=6,3 v=-1,-3", + "p=10,3 v=-1,2", + "p=2,0 v=2,-1", + "p=0,0 v=1,3", + "p=3,0 v=-2,-2", + "p=7,6 v=-1,-3", + "p=3,0 v=-1,-2", + "p=9,3 v=2,3", + "p=7,3 v=-1,2", + "p=2,4 v=2,-3", + "p=9,5 v=-3,-3", + ]; + + private async Task GetPuzzleInput() + { + return await File.ReadAllLinesAsync(Path.Combine(AppContext.BaseDirectory, "INPUT.txt")); + } + + [Test] + public async Task Run_WhenCalledWithPuzzleInput_ItShouldReturnExpectedValue() + { + var input = await GetPuzzleInput(); + + var result = Simulation.From(101, 103, input).Run(100); + + await Assert.That(result).IsEqualTo(224554908); + } + + [Test] + public async Task Run_WhenCalledWithExampleInput_ItShouldReturnExpectedValue() + { + var result = Simulation.From(11, 7, _exampleInput).Run(100); + + await Assert.That(result).IsEqualTo(12); + } +} \ No newline at end of file diff --git a/14/RestroomRedoubt/Program.cs b/14/RestroomRedoubt/Program.cs index 5f94bf8..994ec39 100644 --- a/14/RestroomRedoubt/Program.cs +++ b/14/RestroomRedoubt/Program.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; if (args.Length is 0) { @@ -18,7 +20,139 @@ var input = await File.ReadAllLinesAsync(args[0]); var stopwatch = new Stopwatch(); stopwatch.Start(); -// TODO: Implement solution +var result = Simulation.From(101, 103, input).Run(100); stopwatch.Stop(); -Console.WriteLine($". ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file +Console.WriteLine($"The safety factor will be {result}. ({stopwatch.ElapsedMilliseconds}ms)"); + +class Simulation +{ + private readonly List _robots; + private readonly int _width; + private readonly int _height; + private readonly List _quadrants; + + private Simulation(int width, int height, List robots) + { + _width = width; + _robots = robots; + _height = height; + + var middleColumn = _width / 2; + var middleRow = _height / 2; + + var topLeftQuadrant = new Quadrant(0, middleColumn - 1, 0, middleRow - 1); + var topRightQuadrant = new Quadrant(middleColumn + 1, _width - 1, 0, middleRow - 1); + var bottomLeftQuadrant = new Quadrant(0, middleColumn - 1, middleRow + 1, _height - 1); + var bottomRightQuadrant = new Quadrant(middleColumn + 1, _width - 1, middleRow + 1, _height - 1); + _quadrants = [topLeftQuadrant, topRightQuadrant, bottomLeftQuadrant, bottomRightQuadrant]; + } + + public static Simulation From(int width, int height, string[] input) => new(width, height, input.Select(Robot.From).ToList()); + + public int Run(int times) + { + for (int i = 0; i < times; i++) + { + foreach (var robot in _robots) + { + robot.Move(_width, _height); + } + + Debug(i); + } + + return _quadrants + .Select(quadrant => _robots.Count(r => r.IsIn(quadrant))) + .Aggregate(1, (current, count) => current * count); + } + + private void Debug(int time) + { + var lines = new StringBuilder(); + + for (var currentColumn = 0; currentColumn < _width; currentColumn++) + { + var row = new StringBuilder(); + + for (int currentRow = 0; currentRow < _height; currentRow++) + { + if (_robots.Any(r => r.IsIn(currentColumn, currentRow))) + { + row.Append('R'); + } + else + { + row.Append('.'); + } + } + + lines.AppendLine(row.ToString()); + } + + File.WriteAllText(Path.Combine(AppContext.BaseDirectory, $"OUTPUT_{time}.txt"), lines.ToString()); + } +} + +partial record Robot +{ + private int PositionX { get; set; } + private int PositionY { get; set; } + private int VelocityX { get; } + private int VelocityY { get; } + + private Robot(int positionX, int positionY, int velocityX, int velocityY) + { + PositionX = positionX; + PositionY = positionY; + VelocityX = velocityX; + VelocityY = velocityY; + } + + public static Robot From( + int positionX, + int positionY, + int velocityX, + int velocityY + ) => new(positionX, positionY, velocityX, velocityY); + + public static Robot From(string input) + { + var matches = RobotRegex().Match(input); + + if (matches.Groups.Count is not 5) + { + throw new ArgumentException("The given robot input is missing values"); + } + + return new( + int.Parse(matches.Groups[1].Value), + int.Parse(matches.Groups[2].Value), + int.Parse(matches.Groups[3].Value), + int.Parse(matches.Groups[4].Value) + ); + } + + public (int X, int Y) Move(int MaxX, int MaxY) + { + PositionX = (PositionX + VelocityX + MaxX) % MaxX; + PositionY = (PositionY + VelocityY + MaxY) % MaxY; + + return (PositionX, PositionY); + } + + public bool IsIn(Quadrant quadrant) + { + return PositionX >= quadrant.MinX && + PositionX <= quadrant.MaxX && + PositionY >= quadrant.MinY && + PositionY <= quadrant.MaxY; + } + + public bool IsIn(int x, int y) => PositionX == x && PositionY == y; + + [GeneratedRegex(@"p=(-?\d+),(-?\d+) v=(-?\d+),(-?\d+)")] + private static partial Regex RobotRegex(); +} + +record Quadrant(int MinX, int MaxX, int MinY, int MaxY); \ No newline at end of file