namespace AoC24; using AoC24.Common; using AoCLevelInputProvider; using Parsing; using Parsing.Data; using Parsing.Schema; public class LoopDetectionException : Exception { public LoopDetectionException(string message) : base(message) {} } public class GuardRunner { public IDataIndex currentPosition; public IDataIndex initialCurrentPosition; public int TraversedTiles; public IDictionary>> traversalMap = new Dictionary>>(); public Direction currentDirection; public Direction initialDirection; public List> obstacles; private DefaultTwoDimensionalManipulator manipulator; private DefaultDataSetIndexer indexer; private List> dataSet; public GuardRunner(IDataIndex startingPosition, string guardChar, List> obstacles, List> dataSet) { switch(guardChar) { case "^": this.currentDirection = Direction.Up; break; case ">": this.currentDirection = Direction.Right; break; case "v": this.currentDirection = Direction.Down; break; case "<": this.currentDirection = Direction.Left; break; default: throw new ArgumentException("Invalid directional parameter given"); } this.initialDirection = currentDirection; this.currentPosition = startingPosition; this.initialCurrentPosition = startingPosition; this.obstacles = obstacles; this.manipulator = new DefaultTwoDimensionalManipulator(dataSet); this.indexer = new DefaultDataSetIndexer(); this.dataSet = dataSet; } private void VisitTile(int x, int y, Direction d) { if(!this.traversalMap.ContainsKey(x)) { this.traversalMap[x] = new Dictionary>(); } if(!this.traversalMap[x].ContainsKey(y)) { this.traversalMap[x][y] = new List(); } this.traversalMap[x][y].Add(d); var dirStringList = new List(); foreach(var direction in this.traversalMap[x][y]) { dirStringList.Add(direction.ToString()); } //Console.WriteLine($"Check tile ({x},{y}): " + string.Join(",", dirStringList)); } private Direction TurnRight(Direction d) { switch(d) { case Direction.Up: return Direction.Right; case Direction.Right: return Direction.Down; case Direction.Down: return Direction.Left; case Direction.Left: return Direction.Up; default: throw new InvalidOperationException("This default case should never be reached!"); } } private bool AreEqual(DirectionalSearchResult di1, DirectionalSearchResult di2) { return di1.Direction == di2.Direction && di1.DataIndex.GetIndices()[0] == di2.DataIndex.GetIndices()[0] && di1.DataIndex.GetIndices()[1] == di2.DataIndex.GetIndices()[1]; } public void Run() { TraversedTiles = 1; currentPosition = initialCurrentPosition; traversalMap = new Dictionary>>(); currentDirection = initialDirection; while(true) { var nextPosition = currentPosition; while(nextPosition == currentPosition) { var nextPositionCandidate = manipulator.Move(currentPosition, currentDirection); if(!manipulator.IsValidIndex(nextPositionCandidate)) { // we just went off the map return; } // if we added an extra obstacle it will be at the beginning of the list if(this.obstacles[0].DataIndex.GetIndices()[0] == nextPositionCandidate.GetIndices()[0] && this.obstacles[0].DataIndex.GetIndices()[1] == nextPositionCandidate.GetIndices()[1]) { currentDirection = TurnRight(currentDirection); } else if(indexer.Get(dataSet, nextPositionCandidate) == "#") { currentDirection = TurnRight(currentDirection); } else { nextPosition = nextPositionCandidate; } } // we just moved, take into account the tile we just moved on currentPosition = nextPosition; // if we have been here before we have entered a loop if(this.traversalMap.ContainsKey(currentPosition.GetIndices()[0]) && this.traversalMap[currentPosition.GetIndices()[0]].ContainsKey(currentPosition.GetIndices()[1]) && this.traversalMap[currentPosition.GetIndices()[0]][currentPosition.GetIndices()[1]].Contains(currentDirection)) { throw new LoopDetectionException(""); } VisitTile(currentPosition.GetIndices()[0], currentPosition.GetIndices()[1], currentDirection); } } } public class Level6Solver : FullTextLevelSolverBase { public override int LevelNumber { get { return 6; } } protected override InputSchemaBuilder DefineInputSchema(InputSchemaBuilder schemaBuilder) { return schemaBuilder .Repeat() .Expect(InputType.Char) .EndRepetition(); } public override string SolveFirstStar() { var data = this.GetData() .AsListRows(); var manipulator = DefaultTwoDimensionalManipulator.Create(data); var obstacles = manipulator.FindInSet("#"); var possibleGuardCharacters = new List() { ">", "<", "v", "^" }; var obstacleList = manipulator.FindInSet("#"); SearchResult guard = null; foreach(var gchar in possibleGuardCharacters) { guard = manipulator.FindInSet(gchar).SingleOrDefault(); if (guard != null) { break; } } var dataAccessor = new DefaultDataSetIndexer(); var guardChar = dataAccessor.Get(data, guard.DataIndex); var guardRunner = new GuardRunner(guard.DataIndex, guardChar, obstacleList, data); guardRunner.Run(); var count = 1; foreach(var xCoord in guardRunner.traversalMap.Keys) { count += guardRunner.traversalMap[xCoord].Count; } return count.ToString(); } public override string SolveSecondStar() { var data = this.GetData() .AsListRows(); var manipulator = DefaultTwoDimensionalManipulator.Create(data); var possibleGuardCharacters = new List() { ">", "<", "v", "^" }; var obstacleList = manipulator.FindInSet("#"); SearchResult guard = null; foreach(var gchar in possibleGuardCharacters) { guard = manipulator.FindInSet(gchar).SingleOrDefault(); if (guard != null) { break; } } var dataAccessor = new DefaultDataSetIndexer(); var guardChar = dataAccessor.Get(data, guard.DataIndex); // first determine regular taken path var guardRunner = new GuardRunner(guard.DataIndex, guardChar, obstacleList, data); guardRunner.Run(); // now for each visited tile, place an obstacle there and see if we run into a loop int loopCount = 0; foreach(var xCoord in guardRunner.traversalMap.Keys) { foreach(var yCoord in guardRunner.traversalMap[xCoord].Keys) { //Console.WriteLine($"Trying obstacle at ({xCoord},{yCoord})"); var newObstacleList = new List>(); newObstacleList.Add(new SearchResult(new DefaultPositionalDataIndex(xCoord, yCoord))); newObstacleList.AddRange(obstacleList); var testRunner = new GuardRunner(guard.DataIndex, guardChar, newObstacleList, data); try { testRunner.Run(); } catch(LoopDetectionException) { loopCount += 1; } } } return loopCount.ToString(); } }