From f7275f351fe5c0d9a356fd2e0c80644297760f4d Mon Sep 17 00:00:00 2001 From: Adam Rodger Date: Sat, 14 Dec 2024 10:34:53 +0000 Subject: [PATCH] day(14): Restroom Redoubt :christmas_tree: --- src/AdventOfCode/Day14.cs | 147 +++++++++++++++++++++++++ src/AdventOfCode/inputs/day14.txt | Bin 0 -> 8359 bytes tests/AdventOfCode.Tests/Day14Tests.cs | 75 +++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 src/AdventOfCode/Day14.cs create mode 100644 src/AdventOfCode/inputs/day14.txt create mode 100644 tests/AdventOfCode.Tests/Day14Tests.cs diff --git a/src/AdventOfCode/Day14.cs b/src/AdventOfCode/Day14.cs new file mode 100644 index 0000000..04dba9f --- /dev/null +++ b/src/AdventOfCode/Day14.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using AdventOfCode.Utilities; + +namespace AdventOfCode +{ + /// + /// Solver for Day 14 + /// + public class Day14 + { + public int Part1(string[] input, int width, int height) + { + ICollection bots = Parse(input); + + for (int i = 0; i < 100; i++) + { + MoveBots(bots, width, height); + } + + int q1 = 0; + int q2 = 0; + int q3 = 0; + int q4 = 0; + int midX = width / 2; + int midY = height / 2; + + foreach (Robot bot in bots) + { + if (bot.Position.X < midX) + { + if (bot.Position.Y < midY) + { + q1++; + } + else if (bot.Position.Y > midY) + { + q2++; + } + } + else if (bot.Position.X > midX) + { + if (bot.Position.Y < midY) + { + q3++; + } + else if (bot.Position.Y > midY) + { + q4++; + } + } + } + + return q1 * q2 * q3 * q4; + } + + public int Part2(string[] input, int width, int height) + { + ICollection bots = Parse(input); + + int i = 0; + + while (true) + { + i++; + + MoveBots(bots, width, height); + + var positions = bots.Select(b => b.Position).ToHashSet(); + + // there's a 32x34 'frame' around the image with the top corner at 40,47 which I found empirically the first time :D + if (positions.Count(p => p.X == 40) == 34 && positions.Count(p => p.Y == 47) == 32) + { + Print(i, positions, width, height); + return i; + } + } + } + + /// + /// Parse the input + /// + /// Input + /// Parsed robots + private static ICollection Parse(string[] input) => input.Select(line => line.Numbers()) + .Select(n => new Robot + { + Position = (n[0], n[1]), + Vector = (n[2], n[3]) + }) + .ToArray(); + + /// + /// Move all the bots - this mutates the given collection in-place + /// + /// Bots + /// Grid width + /// Grid height + private static void MoveBots(ICollection bots, int width, int height) + { + foreach (Robot bot in bots) + { + bot.Position = ((bot.Position.X + bot.Vector.X + width) % width, + (bot.Position.Y + bot.Vector.Y + height) % height); + } + } + + /// + /// Print the grid and indicate where all the bots are + /// + /// Number of moves to get to this point + /// Bot positions + /// Grid width + /// Grid height + private static void Print(int i, ISet bots, int width, int height) + { + if (!Debugger.IsAttached) + { + return; + } + + StringBuilder s = new StringBuilder(width * height + height * Environment.NewLine.Length); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + s.Append(bots.Contains((x, y)) ? '#' : '.'); + } + + s.AppendLine(); + } + + Debug.WriteLine($"Step {i}"); + Debug.WriteLine(s.ToString()); + } + + private class Robot + { + public Point2D Position { get; set; } + public Point2D Vector { get; init; } + } + } +} diff --git a/src/AdventOfCode/inputs/day14.txt b/src/AdventOfCode/inputs/day14.txt new file mode 100644 index 0000000000000000000000000000000000000000..904e2e31c53384bcd8010aa7ceb2d5842a210e64 GIT binary patch literal 8359 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 literal 0 HcmV?d00001 diff --git a/tests/AdventOfCode.Tests/Day14Tests.cs b/tests/AdventOfCode.Tests/Day14Tests.cs new file mode 100644 index 0000000..8a88a49 --- /dev/null +++ b/tests/AdventOfCode.Tests/Day14Tests.cs @@ -0,0 +1,75 @@ +using System.IO; +using Xunit; +using Xunit.Abstractions; + +namespace AdventOfCode.Tests +{ + public class Day14Tests + { + private readonly ITestOutputHelper output; + private readonly Day14 solver; + + public Day14Tests(ITestOutputHelper output) + { + this.output = output; + this.solver = new Day14(); + } + + private static string[] GetRealInput() + { + string[] input = File.ReadAllLines("inputs/day14.txt"); + return input; + } + + private static string[] GetSampleInput() + { + return new string[] + { + "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", + }; + } + + [Fact] + public void Part1_SampleInput_ProducesCorrectResponse() + { + var expected = 12; + + var result = solver.Part1(GetSampleInput(), 11, 7); + + Assert.Equal(expected, result); + } + + [Fact] + public void Part1_RealInput_ProducesCorrectResponse() + { + var expected = 211692000; + + var result = solver.Part1(GetRealInput(), 101, 103); + output.WriteLine($"Day 14 - Part 1 - {result}"); + + Assert.Equal(expected, result); + } + + [Fact] + public void Part2_RealInput_ProducesCorrectResponse() + { + var expected = 6587; + + var result = solver.Part2(GetRealInput(), 101, 103); + output.WriteLine($"Day 14 - Part 2 - {result}"); + + Assert.Equal(expected, result); + } + } +}