2024-12-14 00:29:40 -06:00
|
|
|
using System.Diagnostics;
|
2024-12-15 13:50:34 -06:00
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.RegularExpressions;
|
2024-12-14 00:29:40 -06:00
|
|
|
|
|
|
|
|
if (args.Length is 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Please provide a path to the input file.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (File.Exists(args[0]) is false)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("The provided file does not exist.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var isPart2 = args.Length is 2 && args[1] is "part2";
|
|
|
|
|
var input = await File.ReadAllLinesAsync(args[0]);
|
|
|
|
|
|
2024-12-15 17:06:40 -06:00
|
|
|
// TODO: This still doesn't feel like the right API tho...even though it does solve
|
2024-12-14 00:29:40 -06:00
|
|
|
var stopwatch = new Stopwatch();
|
|
|
|
|
stopwatch.Start();
|
|
|
|
|
|
2024-12-15 17:06:40 -06:00
|
|
|
var width = 101;
|
|
|
|
|
var height = 103;
|
|
|
|
|
var times = isPart2
|
|
|
|
|
? width * height
|
|
|
|
|
: 100;
|
|
|
|
|
|
|
|
|
|
var simulation = Simulation.From(width, height, input);
|
|
|
|
|
var simulationResults = simulation.Run(times);
|
|
|
|
|
var result = isPart2
|
|
|
|
|
? simulationResults.MinBy(kvp => kvp.Value).Key
|
|
|
|
|
: simulation.SafetyFactor;
|
|
|
|
|
|
|
|
|
|
var msg = isPart2
|
|
|
|
|
? $"The Christmas tree appears after {result} seconds"
|
|
|
|
|
: $"The safety factor will be {result}";
|
2024-12-14 00:29:40 -06:00
|
|
|
|
|
|
|
|
stopwatch.Stop();
|
2024-12-15 17:06:40 -06:00
|
|
|
Console.WriteLine($"{msg}. ({stopwatch.ElapsedMilliseconds}ms)");
|
|
|
|
|
|
|
|
|
|
if (isPart2)
|
|
|
|
|
{
|
|
|
|
|
simulation.Run(result);
|
|
|
|
|
simulation.PrintToConsole();
|
|
|
|
|
}
|
2024-12-15 13:50:34 -06:00
|
|
|
|
|
|
|
|
class Simulation
|
|
|
|
|
{
|
|
|
|
|
private readonly List<Robot> _robots;
|
|
|
|
|
private readonly int _width;
|
|
|
|
|
private readonly int _height;
|
|
|
|
|
private readonly List<Quadrant> _quadrants;
|
2024-12-15 14:36:40 -06:00
|
|
|
private List<int> RobotsPerQuadrant =>
|
|
|
|
|
_quadrants.Select(quadrant => _robots.Count(r => r.IsIn(quadrant))).ToList();
|
2024-12-15 17:06:40 -06:00
|
|
|
public int SafetyFactor => RobotsPerQuadrant.Aggregate(1, (current, count) => current * count);
|
2024-12-15 13:50:34 -06:00
|
|
|
|
|
|
|
|
private Simulation(int width, int height, List<Robot> 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());
|
2024-12-15 17:06:40 -06:00
|
|
|
|
|
|
|
|
public Dictionary<int, int> Run(int times)
|
2024-12-15 13:50:34 -06:00
|
|
|
{
|
2024-12-15 17:06:40 -06:00
|
|
|
var safetyFactorMap = new Dictionary<int, int>();
|
|
|
|
|
|
2024-12-15 13:50:34 -06:00
|
|
|
for (int i = 0; i < times; i++)
|
|
|
|
|
{
|
|
|
|
|
foreach (var robot in _robots)
|
|
|
|
|
{
|
|
|
|
|
robot.Move(_width, _height);
|
|
|
|
|
}
|
2024-12-15 17:06:40 -06:00
|
|
|
|
|
|
|
|
safetyFactorMap.Add(i + 1, SafetyFactor);
|
2024-12-15 13:50:34 -06:00
|
|
|
}
|
2024-12-15 17:06:40 -06:00
|
|
|
|
|
|
|
|
return safetyFactorMap;
|
2024-12-15 13:50:34 -06:00
|
|
|
}
|
|
|
|
|
|
2024-12-15 13:53:44 -06:00
|
|
|
public override string ToString()
|
2024-12-15 13:50:34 -06:00
|
|
|
{
|
|
|
|
|
var lines = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
for (var currentColumn = 0; currentColumn < _width; currentColumn++)
|
|
|
|
|
{
|
|
|
|
|
var row = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
for (int currentRow = 0; currentRow < _height; currentRow++)
|
|
|
|
|
{
|
2024-12-15 14:36:40 -06:00
|
|
|
row.Append(_robots.Any(r => r.IsIn(currentColumn, currentRow)) ? 'R' : '.');
|
2024-12-15 13:50:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines.AppendLine(row.ToString());
|
|
|
|
|
}
|
2024-12-15 13:53:44 -06:00
|
|
|
|
|
|
|
|
return lines.ToString();
|
2024-12-15 13:50:34 -06:00
|
|
|
}
|
2024-12-15 14:36:40 -06:00
|
|
|
|
|
|
|
|
public void PrintToConsole() => Console.WriteLine(ToString());
|
|
|
|
|
|
|
|
|
|
public Task PrintToFileAsync(string filename) =>
|
|
|
|
|
File.WriteAllTextAsync(Path.Combine(AppContext.BaseDirectory, filename), ToString());
|
2024-12-15 13:50:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
partial record Robot
|
|
|
|
|
{
|
2024-12-15 14:36:40 -06:00
|
|
|
public int PositionX { get; private set; }
|
|
|
|
|
public int PositionY { get; private set; }
|
2024-12-15 13:50:34 -06:00
|
|
|
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)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-19 00:27:38 -06:00
|
|
|
public (int X, int Y) Move(int maxX, int maxY)
|
2024-12-15 13:50:34 -06:00
|
|
|
{
|
2024-12-19 00:27:38 -06:00
|
|
|
PositionX = (PositionX + VelocityX + maxX) % maxX;
|
|
|
|
|
PositionY = (PositionY + VelocityY + maxY) % maxY;
|
2024-12-15 13:50:34 -06:00
|
|
|
|
|
|
|
|
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);
|