From 349d64468f5be9077bd710bd2485b384da2e25e5 Mon Sep 17 00:00:00 2001 From: Amatsugu Date: Sat, 7 Dec 2024 02:37:00 -0500 Subject: [PATCH] Day 6 part 1 --- .../Problems/AOC2024/Day6/GuardGallivant.cs | 348 ++++++++++++++++++ AdventOfCode/Utils/Extensions.cs | 14 + AdventOfCode/Utils/Models/Common.cs | 20 + 3 files changed, 382 insertions(+) create mode 100644 AdventOfCode/Problems/AOC2024/Day6/GuardGallivant.cs create mode 100644 AdventOfCode/Utils/Extensions.cs create mode 100644 AdventOfCode/Utils/Models/Common.cs diff --git a/AdventOfCode/Problems/AOC2024/Day6/GuardGallivant.cs b/AdventOfCode/Problems/AOC2024/Day6/GuardGallivant.cs new file mode 100644 index 0000000..d70628b --- /dev/null +++ b/AdventOfCode/Problems/AOC2024/Day6/GuardGallivant.cs @@ -0,0 +1,348 @@ +using AdventOfCode.Utils.Models; + +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Turn = (AdventOfCode.Utils.Models.Vec2 pos, int dir, int step); + +namespace AdventOfCode.Problems.AOC2024.Day6; + +[ProblemInfo(2024, 6, "Guard Gallivant")] +internal class GuardGallivant : Problem +{ + private char[][] _data = []; + private int _height; + private int _width; + public static readonly Vec2[] DIRS = [ + new (0, -1), + new (1, 0), + new (0, 1), + new (-1, 0), + ]; + + public override void CalculatePart1() + { + var map = new GuardMap(_data, GetStartPos()); + Part1 = map.GetPath().DistinctBy(p => p.pos).Count(); + } + + private FrozenSet<(Vec2 pos, int dir)> GetVisited(out List turns) + { + var visited = new HashSet<(Vec2 pos, int dir)>(); + turns = []; + var pos = GetStartPos(); + var dir = 0; + var step = 0; + while (IsInBounds(pos)) + { + var curDir = DIRS[dir]; + visited.Add((pos, dir)); + if (CanMove(pos, curDir, _data)) + pos += curDir; + else + { + turns.Add((pos, dir, step)); + dir = (dir + 1) % DIRS.Length; + } + step++; + } + //PrintBoard(visited.Select(v => v.pos).ToFrozenSet(), pos, _data); + return visited.ToFrozenSet(); + } + + private void PrintBoard(FrozenSet> visited, Vec2 pos, char[][] board) + { + Console.WriteLine("======================"); + for (int y = 0; y < board.Length; y++) + { + var row = board[y]; + for (int x = 0; x < row.Length; x++) + { + var p = new Vec2(x, y); + if (row[x] == '#') + { + Console.Write(row[x]); + continue; + } + if (row[x] == '^' && p != pos) + { + if(visited.Contains(p)) + Console.Write('X'); + else + Console.Write('.'); + } + else + { + if (visited.Contains(new(x, y))) + Console.Write('X'); + else if(p == pos) + Console.Write('*'); + else + Console.Write(row[x]); + } + } + Console.WriteLine(); + } + } + + private bool CanMove(Vec2 pos, Vec2 dir, char[][] board) + { + var p = pos + dir; + if(IsInBounds(p)) + return board[p.Y][p.X] == '.' || board[p.Y][p.X] == '^'; + return true; + } + + private Vec2 GetStartPos() + { + for (int y = 0; y < _data.Length; y++) + { + var row = _data[y]; + for (int x = 0; x < row.Length; x++) + { + if (row[x] == '^') + return new (x, y); + } + } + throw new Exception("Start Position not found"); + } + + private bool IsInBounds(Vec2 pos) + { + if (pos.X < 0 || pos.Y < 0) + return false; + if (pos.X >= _width || pos.Y >= _height) + return false; + return true; + } + + public override void CalculatePart2() + { + var map = new GuardMap(_data, GetStartPos()); + var path = map.GetPath(); + foreach (var (pos, node) in path) + { + var turn = (node.Direction + 1) % 4; + if(map.GetNextObstacle(pos, turn, out var next)){ + var obstacle = new GuardNode(pos, turn); + var nextNode = map.Nodes.FirstOrDefault(p => p.Pos == next - DIRS[turn] && p.Direction == (turn + 1) % 4); + if(nextNode != null) + { + var tmp = node.Next; + node.Next = obstacle; + obstacle.Next = nextNode; + if(obstacle.IsLoop()) + Part2++; + node.Next = tmp; + } + } + } + } + + + + public override void LoadInput() + { + _data = ReadInputLines("input.txt").Select(r => r.ToCharArray()).ToArray(); + _height = _data.Length; + _width = _data[0].Length; + } +} + +file class GuardMap +{ + public GuardNode Start { get; set; } + public List Nodes { get; set; } + + private List> _obstacles = []; + + private int _height; + private int _width; + + public GuardMap(char[][] map, Vec2 start) + { + _height = map.Length; + _width = map[0].Length; + Start = new GuardNode(start, 0); + Nodes = [Start]; + FindObstacles(map); + Solve(); + } + + public List<(Vec2 pos, GuardNode node)> GetPath() + { + var path = new List<(Vec2, GuardNode)>(); + + var curNode = Start; + while (true) + { + + if (curNode.Next == null) + { + var end = curNode.Direction switch + { + 0 => new Vec2(curNode.Pos.X, -1), + 1 => new Vec2(_width, curNode.Pos.Y), + 2 => new Vec2(curNode.Pos.X, _height), + 3 => new Vec2(-1, curNode.Pos.Y), + _ => throw new InvalidOperationException() + }; + path.AddRange(GetPointsBetween(curNode.Pos, end, curNode.Direction).Select(p => (p, curNode))); + break; + } + path.AddRange(GetPointsBetween(curNode.Pos, curNode.Next.Pos, curNode.Direction).Select(p => (p, curNode))); + curNode = curNode.Next; + } + + return path; + } + + private List> GetPointsBetween(Vec2 start, Vec2 end, int dir) + { + var result = new List>(); + switch (dir) + { + case 0: + for (int i = start.Y; i > end.Y; i--) + result.Add(new Vec2(start.X, i)); + break; + case 1: + for (int i = start.X; i < end.X; i++) + result.Add(new Vec2(i, start.Y)); + break; + case 2: + for (int i = start.Y; i < end.Y; i++) + result.Add(new Vec2(start.X, i)); + break; + case 3: + for (int i = start.X; i > end.X; i--) + result.Add(new Vec2(i, start.Y)); + break; + } + + return result; + } + + private void FindObstacles(char[][] map) + { + for (int y = 0; y < map.Length; y++) + { + for (int x = 0; x < map[0].Length; x++) + { + if (map[y][x] == '#') + _obstacles.Add(new(x, y)); + } + } + } + + private void Solve() + { + var curNode = Start; + while (true) + { + if(!GetNextObstacle(curNode.Pos, curNode.Direction, out var next)) + break; + curNode.Next = new GuardNode(next - GuardGallivant.DIRS[curNode.Direction], (curNode.Direction + 1) % 4); + curNode = curNode.Next; + Nodes.Add(curNode); + } + } + + public bool SolveNewPath(GuardNode start, Vec2 extraObsticle) + { + var curNode = start; + var nodes = new List() + { + start + }; + while (true) + { + if (nodes.Count > 8) + return false; + if (!GetNextObstacle(curNode.Pos, curNode.Direction, out var next, extraObsticle)) + return false; + var newNode = new GuardNode(next - GuardGallivant.DIRS[curNode.Direction], (curNode.Direction + 1) % 4); + if (nodes.Any(n => n.Pos == newNode.Pos && n.Direction == newNode.Direction)) + return true; + curNode.Next = newNode; + curNode = curNode.Next; + } + } + + public bool GetNextObstacle(Vec2 start, int dir, out Vec2 pos, Vec2? extraObsticle = null) + { + var obstacles = extraObsticle != null ? _obstacles.Append((Vec2)extraObsticle) : _obstacles; + pos = default; + switch (dir) + { + case 0: + var up = obstacles.Where(o => o.X == start.X && o.Y < start.Y); + if (up.Any()) + { + pos = up.MaxBy(o => o.Y); + return true; + } + break; + case 1: + var right = obstacles.Where(o => o.Y == start.Y && o.X > start.X); + if (right.Any()) + { + pos = right.MinBy(o => o.X); + return true; + } + break; + case 2: + var down = obstacles.Where(o => o.X == start.X && o.Y > start.Y); + if (down.Any()) + { + pos = down.MinBy(o => o.Y); + return true; + } + break; + case 3: + var left = obstacles.Where(o => o.Y == start.Y && o.X < start.X); + if (left.Any()) + { + pos = left.MaxBy(o => o.X); + return true; + } + break; + } + return false; + } +} + +file class GuardNode +{ + public Vec2 Pos { get; } + public int Direction { get; } + public GuardNode? Next { get; set; } + public GuardNode(Vec2 pos, int dir) + { + Pos = pos; + Direction = dir; + } + + public bool IsLoop() + { + return NodeExists(this); + } + + public bool NodeExists(GuardNode target) + { + if (Next == null) + return false; + if (Next == target) + return true; + return Next.NodeExists(target); + } + + public override string ToString() + { + return $"{Pos}: {Direction}"; + } +} \ No newline at end of file diff --git a/AdventOfCode/Utils/Extensions.cs b/AdventOfCode/Utils/Extensions.cs new file mode 100644 index 0000000..3e95de0 --- /dev/null +++ b/AdventOfCode/Utils/Extensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AdventOfCode.Utils; +public static class Extensions +{ + public static string AsJoinedString(this object[] data, string delim = ", ") + { + return string.Join(delim, data); + } +} diff --git a/AdventOfCode/Utils/Models/Common.cs b/AdventOfCode/Utils/Models/Common.cs new file mode 100644 index 0000000..76738a0 --- /dev/null +++ b/AdventOfCode/Utils/Models/Common.cs @@ -0,0 +1,20 @@ +using System.Numerics; + +namespace AdventOfCode.Utils.Models; + + +public record struct Vec2(T X, T Y) where T : INumber +{ + public static Vec2 operator +(Vec2 left, Vec2 right) => new Vec2(left.X + right.X, left.Y + right.Y); + public static Vec2 operator -(Vec2 left, Vec2 right) => new Vec2(left.X - right.X, left.Y - right.Y); + public static Vec2 operator *(Vec2 left, T right) => new Vec2(left.X * right, left.Y * right); + public static Vec2 operator /(Vec2 left, T right) => new Vec2(left.X / right, left.Y / right); +} + +public record struct Vec3(T X, T Y, T Z) where T : INumber +{ + public static Vec3 operator +(Vec3 left, Vec3 right) => new Vec3(left.X + right.X, left.Y + right.Y, left.Z + right.Z); + public static Vec3 operator -(Vec3 left, Vec3 right) => new Vec3(left.X - right.X, left.Y - right.Y, left.Z - right.Z); + public static Vec3 operator *(Vec3 left, T right) => new Vec3(left.X * right, left.Y * right, left.Z * right); + public static Vec3 operator /(Vec3 left, T right) => new Vec3(left.X / right, left.Y / right, left.Z / right); +} \ No newline at end of file