From 367cd56a42f8b904c52b79be5742086c2f663a58 Mon Sep 17 00:00:00 2001 From: AliveDevil Date: Sun, 22 Dec 2024 02:20:02 +0100 Subject: [PATCH] Add Day 6 --- .vscode/launch.json | 11 +++ .vscode/tasks.json | 8 ++ AdventOfCode.sln | 7 ++ src/2024/06/Guard.cs | 68 ++++++++++++++++ src/2024/06/IUpdate.cs | 4 + src/2024/06/Map.cs | 145 +++++++++++++++++++++++++++++++++ src/2024/06/Program.cs | 83 +++++++++++++++++++ src/2024/06/Vector2I.cs | 8 ++ src/2024/06/aoc-2024-06.csproj | 11 +++ 9 files changed, 345 insertions(+) create mode 100644 src/2024/06/Guard.cs create mode 100644 src/2024/06/IUpdate.cs create mode 100644 src/2024/06/Map.cs create mode 100644 src/2024/06/Program.cs create mode 100644 src/2024/06/Vector2I.cs create mode 100644 src/2024/06/aoc-2024-06.csproj diff --git a/.vscode/launch.json b/.vscode/launch.json index b1ea163..0cc5409 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -58,6 +58,17 @@ "cwd": "${workspaceFolder}", "stopAtEntry": false, "console": "internalConsole" + }, + { + "name": "aoc-2024-06 Debug", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "dotnet: build aoc-2024-06", + "program": "${workspaceFolder}/artifacts/bin/aoc-2024-06/debug/aoc-2024-06.dll", + "args": ["part-two", "data/2024/06/dev.txt"], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b3fc837..efc390a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -42,6 +42,14 @@ "group": "build", "problemMatcher": [], "label": "dotnet: build aoc-2024-05" + }, + { + "type": "dotnet", + "task": "build aoc-2024-06.csproj", + "file": "src/2024/06/aoc-2024-06.csproj", + "group": "build", + "problemMatcher": [], + "label": "dotnet: build aoc-2024-06" } ] } \ No newline at end of file diff --git a/AdventOfCode.sln b/AdventOfCode.sln index 674fbdd..a86866b 100644 --- a/AdventOfCode.sln +++ b/AdventOfCode.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoc-2024-04", "src\2024\04\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoc-2024-05", "src\2024\05\aoc-2024-05.csproj", "{D108A6AB-B974-4BD9-B97A-CB1D1A2F3920}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoc-2024-06", "src\2024\06\aoc-2024-06.csproj", "{EEC6EF36-EE16-4DB4-9AE8-CF0234751458}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,6 +46,10 @@ Global {D108A6AB-B974-4BD9-B97A-CB1D1A2F3920}.Debug|Any CPU.Build.0 = Debug|Any CPU {D108A6AB-B974-4BD9-B97A-CB1D1A2F3920}.Release|Any CPU.ActiveCfg = Release|Any CPU {D108A6AB-B974-4BD9-B97A-CB1D1A2F3920}.Release|Any CPU.Build.0 = Release|Any CPU + {EEC6EF36-EE16-4DB4-9AE8-CF0234751458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEC6EF36-EE16-4DB4-9AE8-CF0234751458}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEC6EF36-EE16-4DB4-9AE8-CF0234751458}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEC6EF36-EE16-4DB4-9AE8-CF0234751458}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {FAF70800-E2C3-4AD7-B433-86C1F20380F7} = {37CA8017-085A-4F6A-BADB-535F929C10B9} @@ -51,5 +57,6 @@ Global {D6DAA79C-6B82-46E7-90CD-73D0A3196B8A} = {37CA8017-085A-4F6A-BADB-535F929C10B9} {49021FCE-1CA9-430F-99B2-8E5C14A48394} = {37CA8017-085A-4F6A-BADB-535F929C10B9} {D108A6AB-B974-4BD9-B97A-CB1D1A2F3920} = {37CA8017-085A-4F6A-BADB-535F929C10B9} + {EEC6EF36-EE16-4DB4-9AE8-CF0234751458} = {37CA8017-085A-4F6A-BADB-535F929C10B9} EndGlobalSection EndGlobal diff --git a/src/2024/06/Guard.cs b/src/2024/06/Guard.cs new file mode 100644 index 0000000..bd79652 --- /dev/null +++ b/src/2024/06/Guard.cs @@ -0,0 +1,68 @@ +class Guard : IUpdate +{ + private readonly Map _map; + private readonly HashSet<(Vector2I, char)> _path = []; + + public char Direction { get; set; } + + public Vector2I Position { get; set; } + + public Guard(Map map) + { + _map = map; + map.RegisterOnUpdate(this); + } + + public bool? Step() + { + if (!_path.Add((Position, Direction))) + { + return null; + } + var test = NextPosition(); + if (!_map.IsValid(test)) + { + return false; + } + else if (_map.IsOccupied(test)) + { + Direction = Turn(); + } + else + { + Position = test; + } + + return true; + } + + public void Update() + { + var tmpPosition = Position; + if (Step() is not true) + { + _map.UnregisterOnUpdate(this); + } + else + { + _map.Move(tmpPosition, Position); + } + } + + private Vector2I NextPosition() + { + return Position + Program.ToVector(Direction); + } + + private char Turn() + { + return Direction switch + { + '^' => '>', + '>' => 'v', + 'v' => '<', + '<' => '^', + _ => throw new InvalidOperationException() + }; + } +} diff --git a/src/2024/06/IUpdate.cs b/src/2024/06/IUpdate.cs new file mode 100644 index 0000000..20d43bc --- /dev/null +++ b/src/2024/06/IUpdate.cs @@ -0,0 +1,4 @@ +interface IUpdate +{ + void Update(); +} diff --git a/src/2024/06/Map.cs b/src/2024/06/Map.cs new file mode 100644 index 0000000..5509329 --- /dev/null +++ b/src/2024/06/Map.cs @@ -0,0 +1,145 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.IO.MemoryMappedFiles; + +class Map +{ + delegate void UpdateHandler(); + + private readonly ArrayList _entities = []; + private (char, object?)[]? _map; + private UpdateHandler? _updater; + + public int Width { get; private set; } + + public int Height { get; private set; } + + public void RegisterOnUpdate(IUpdate instance) + { + _updater += instance.Update; + } + + public void UnregisterOnUpdate(IUpdate instance) + { + _updater -= instance.Update; + } + + public T? FindEntity() + { + return _entities.OfType().FirstOrDefault(); + } + + public bool Update() + { + if (_updater is not { } updatee) + { + return false; + } + + updatee(); + return true; + } + + public void Load(string file) + { + using var fileView = MemoryMappedFile.CreateFromFile(file, FileMode.Open); + using var accessor = fileView.CreateViewAccessor(); + (Width, Height, var stride) = Parse(accessor); + _map = new (char, object?)[Width * Height]; + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + object? entity = null; + var tile = (char)accessor.ReadByte(y * stride + x); + if (tile is '^' or '>' or '<' or 'v') + { + _entities.Add(entity = new Guard(this) + { + Position = new(x, y), + Direction = tile + }); + tile = '.'; + } + + SetTile(x, y, tile, entity); + } + } + + static (int Width, int Height, int Stride) Parse(MemoryMappedViewAccessor map) + { + bool hasNewLine = false; + int? width = null, stride = null; + for (int i = 0; i < map.Capacity; i++) + { + if ((char)map.ReadByte(i) is '\r' or '\n') + { + if (!hasNewLine) + { + width = i; + } + + hasNewLine = true; + } + else if (hasNewLine) + { + stride = i; + break; + } + } + + return (width!.Value, (int)((map.Capacity + width.Value) / stride!.Value), stride.Value); + } + } + + public bool IsValid(Vector2I position) => IsValid(position.X, position.Y); + + public bool IsValid(int x, int y) => x >= 0 && x < Width && y >= 0 && y < Height; + + public bool IsOccupied(Vector2I position) => IsOccupied(position.X, position.Y); + + public bool IsOccupied(int x, int y) + { + EnsureMap(); + if (!IsValid(x, y)) + { + throw new Exception("(x, y)"); + } + + return _map[(y * Width) + x] is { Item2: not null } or { Item1: '#' }; + } + + public void SetTile(Vector2I position, char? tile, object? entity) => SetTile(position.X, position.Y, tile, entity); + + public void Move(Vector2I from, Vector2I to) + { + EnsureMap(); + if (!IsValid(from)) + { + throw new ArgumentException(null, nameof(from)); + } + + if (!IsValid(to)) + { + throw new ArgumentException(null, nameof(to)); + } + + ref (char, object?) fromTile = ref _map[from.Y * Width + from.X]; + _map[to.Y * Width + to.X].Item2 = fromTile.Item2; + fromTile.Item2 = null; + } + + public void SetTile(int x, int y, char? tile, object? entity) + { + EnsureMap(); + ref (char, object?) value = ref _map[(y * Width) + x]; + value = value with + { + Item1 = tile ?? value.Item1, + Item2 = entity ?? value.Item2 + }; + } + + [MemberNotNull(nameof(_map))] + private void EnsureMap() => ArgumentNullException.ThrowIfNull(_map); +} diff --git a/src/2024/06/Program.cs b/src/2024/06/Program.cs new file mode 100644 index 0000000..9db5751 --- /dev/null +++ b/src/2024/06/Program.cs @@ -0,0 +1,83 @@ +using System.ComponentModel.DataAnnotations; + +using ConsoleAppFramework; + +partial class Program +{ + private static void Main(string[] args) + { + var builder = ConsoleApp.Create(); + builder.Add("part-one", static ([Argument, FileExists] string file) => + { + Console.WriteLine($"Result: {ReadFile(file, false)}"); + }); + builder.Add("part-two", static ([Argument, FileExists] string file) => + { + Console.WriteLine($"Result: {ReadFile(file, true)}"); + }); + builder.Run(args); + } + + internal static Vector2I ToVector(char direction) + { + return direction switch + { + '^' => new(0, -1), + '>' => new(1, 0), + '<' => new(-1, 0), + 'v' => new(0, 1), + _ => throw new InvalidOperationException() + }; + } + + static int ReadFile(string file, bool findLoops) + { + Map map = new(); + map.Load(file); + Guard guard = map.FindEntity()!; + var guardStartDirection = guard.Direction; + var guardStartPosition = guard.Position; + HashSet uniquePositions = [guardStartPosition]; + while (map.Update()) + { + uniquePositions.Add(guard.Position); + } + + if (!findLoops) + { + return uniquePositions.Count; + } + + HashSet uniqueObstacles = []; + foreach (var position in uniquePositions) + { + if (position == guardStartPosition) + { + continue; + } + + Guard validator = new(map) + { + Position = guardStartPosition, + Direction = guardStartDirection + }; + map.SetTile(position, '#', null); + bool? result; + while ((result = validator.Step()) is true) + { } + map.SetTile(position, '.', null); + + if (result is null) + { + uniqueObstacles.Add(position); + } + } + + return uniqueObstacles.Count; + } +} + +class FileExistsAttribute : ValidationAttribute +{ + public override bool IsValid(object? value) => value is string path && File.Exists(path); +} diff --git a/src/2024/06/Vector2I.cs b/src/2024/06/Vector2I.cs new file mode 100644 index 0000000..adb10df --- /dev/null +++ b/src/2024/06/Vector2I.cs @@ -0,0 +1,8 @@ +using System.Numerics; + +record struct Vector2I(int X, int Y) : IAdditionOperators, ISubtractionOperators +{ + public static Vector2I operator +(Vector2I left, Vector2I right) => new(left.X + right.X, left.Y + right.Y); + + public static Vector2I operator -(Vector2I left, Vector2I right) => new(left.X - right.X, left.Y - right.Y); +} diff --git a/src/2024/06/aoc-2024-06.csproj b/src/2024/06/aoc-2024-06.csproj new file mode 100644 index 0000000..99d3fe8 --- /dev/null +++ b/src/2024/06/aoc-2024-06.csproj @@ -0,0 +1,11 @@ + + + + Exe + net8.0 + aoc_2024_06 + enable + enable + + +