using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Serialization; var exParsed = new Day06Parsed("example-input.txt"); Console.WriteLine("Example Part01: {0}", exParsed.GetPart01()); Console.WriteLine("Example Part02: {0}", exParsed.GetPart02()); var pzParsed = new Day06Parsed("puzzle-input.txt"); Console.WriteLine("Puzzle Part01: {0}", pzParsed.GetPart01()); Console.WriteLine("Puzzle Part02: {0}", pzParsed.GetPart02()); class Day06Parsed : AdventCommon.ParsedInput { public enum Direction { UP, DOWN, LEFT, RIGHT } public enum BLOCK_STATE { UNTOUCHED, OBSTACLE, VISITED } public class GridSquare { public GridSquare(BLOCK_STATE state) { State = state; } public BLOCK_STATE State { get; set; } = BLOCK_STATE.UNTOUCHED; public bool hasVisited() { return State == BLOCK_STATE.VISITED; } public bool VisitedFacingUp { get; set; } = false; public bool VisitedFacingDown { get; set; } = false; public bool VisitedFacingLeft { get; set; } = false; public bool VisitedFacingRight { get; set; } = false; public GridSquare Clone() { return (GridSquare)MemberwiseClone(); } } public class Guard { public Guard() { } public Guard(Guard other) { CurrentDirection = other.CurrentDirection; X = other.X; Y = other.Y; IsGone = other.IsGone; } public Direction CurrentDirection { get; set; } public int X { get; set; } public int Y { get; set; } public bool IsGone { get; set; } = false; public Guard Clone() { return (Guard)MemberwiseClone(); } } public class Level { public List> Grid { get; set; } = new List>(); public Guard SecurityGuard { get; set; } = new Guard(); public int CountVisited() => Grid.SelectMany(x => x).Count(x => x.State == BLOCK_STATE.VISITED); public bool GuardIsAround() { return SecurityGuard.X >= 0 && SecurityGuard.Y >= 0; } public enum SIMULATION_RESULT { CONTINUE, DONE, LOOP } public SIMULATION_RESULT SimStep() { if (SecurityGuard.IsGone) return SIMULATION_RESULT.DONE; try { int newY = SecurityGuard.Y, newX = SecurityGuard.X; switch (SecurityGuard.CurrentDirection) { case Direction.UP: newY = SecurityGuard.Y-1; break; case Direction.DOWN: newY = SecurityGuard.Y+1; break; case Direction.LEFT: newX = SecurityGuard.X-1; break; case Direction.RIGHT: newX = SecurityGuard.X+1; break; } if (Grid[newY][newX].State == BLOCK_STATE.OBSTACLE) { switch (SecurityGuard.CurrentDirection) { case Direction.UP: SecurityGuard.CurrentDirection = Direction.RIGHT; break; case Direction.DOWN: SecurityGuard.CurrentDirection = Direction.LEFT; break; case Direction.LEFT: SecurityGuard.CurrentDirection = Direction.UP; break; case Direction.RIGHT: SecurityGuard.CurrentDirection = Direction.DOWN; break; } } else { if (Grid[newY][newX].State == BLOCK_STATE.VISITED) { // We've already visited this block, check if we're looping bool hasLooped = false; switch (SecurityGuard.CurrentDirection) { case Direction.UP: if (Grid[newY][newX].VisitedFacingUp) hasLooped = true; break; case Direction.DOWN: if (Grid[newY][newX].VisitedFacingDown) hasLooped = true; break; case Direction.LEFT: if (Grid[newY][newX].VisitedFacingLeft) hasLooped = true; break; case Direction.RIGHT: if (Grid[newY][newX].VisitedFacingRight) hasLooped = true; break; default: throw new Exception("Unknown direction"); } if (hasLooped) return SIMULATION_RESULT.LOOP; } SecurityGuard.X = newX; SecurityGuard.Y = newY; Grid[newY][newX].State = BLOCK_STATE.VISITED; switch (SecurityGuard.CurrentDirection) { case Direction.UP: Grid[newY][newX].VisitedFacingUp = true; break; case Direction.DOWN: Grid[newY][newX].VisitedFacingDown = true; break; case Direction.LEFT: Grid[newY][newX].VisitedFacingLeft = true; break; case Direction.RIGHT: Grid[newY][newX].VisitedFacingRight = true; break; } } } catch (Exception) { // Guard left the area SecurityGuard.IsGone = true; return SIMULATION_RESULT.DONE; } return SIMULATION_RESULT.CONTINUE; } public SIMULATION_RESULT SimulateAll() { SIMULATION_RESULT result; for (result = SIMULATION_RESULT.CONTINUE; result == SIMULATION_RESULT.CONTINUE; result = SimStep()); return result; } public Level Clone() { bool useJson = false; if (!useJson) { Level other = new Level(); // Copy the grid foreach (var row in this.Grid) { List newRow = new List(); foreach (var s in row) { newRow.Add(s.Clone()); } other.Grid.Add(newRow); } // Copy the guard other.SecurityGuard = SecurityGuard.Clone(); return other; } else { var jsonString = JsonSerializer.Serialize(this); var copy = JsonSerializer.Deserialize(jsonString); if (copy == null) throw new Exception("Failed to clone"); return copy; } } public GridSquare GetGridSquare(int x, int y) => Grid[y][x]; } Level CurrentLevel { get; set; } = new Level(); public Day06Parsed(string fileName) : base(fileName) { } public override int GetPart01() { var level = CurrentLevel.Clone(); level.SimulateAll(); return level.CountVisited(); } public override int GetPart02() { // First simulate everything as a base var simulatedOriginalLevel = CurrentLevel.Clone(); simulatedOriginalLevel.SimulateAll(); int loops = 0; // Iterate across the level and find loops List<(int,int)> visited = new List<(int,int)>(); for (int y = 0; y < simulatedOriginalLevel.Grid.Count; y++) { for (int x = 0; x < simulatedOriginalLevel.Grid[y].Count; x++) { if (simulatedOriginalLevel.Grid[y][x].State == BLOCK_STATE.VISITED) { visited.Add((x, y)); } } } // Run simulations in parallel because we can Parallel.ForEach(visited, coords => { int x = coords.Item1; int y = coords.Item2; //var loopTest = coords.Item3; var loopTest = CurrentLevel.Clone(); loopTest.GetGridSquare(x, y).State = BLOCK_STATE.OBSTACLE; var result = loopTest.SimulateAll(); if (result == Level.SIMULATION_RESULT.LOOP) loops++; }); return loops; } public override bool ParseLine(string line, object? context = null) { List list = new List(); int index = 0; foreach (char c in line) { switch (c) { case '.': list.Add(new GridSquare(BLOCK_STATE.UNTOUCHED)); break; case '#': list.Add(new GridSquare(BLOCK_STATE.OBSTACLE)); break; case '^': var gridSquare = new GridSquare(BLOCK_STATE.VISITED); int guardY = CurrentLevel.Grid.Count; int guardX = index; CurrentLevel.SecurityGuard.CurrentDirection = Direction.UP; CurrentLevel.SecurityGuard.X = guardX; CurrentLevel.SecurityGuard.Y = guardY; gridSquare.VisitedFacingUp = true; list.Add(gridSquare); break; default: throw new Exception("Unknown character"); } index++; } CurrentLevel.Grid.Add(list); return true; } }