335 lines
10 KiB
C#
335 lines
10 KiB
C#
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<List<GridSquare>> Grid { get; set; } = new List<List<GridSquare>>();
|
|
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<GridSquare> newRow = new List<GridSquare>();
|
|
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<Level>(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<GridSquare> list = new List<GridSquare>();
|
|
|
|
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;
|
|
}
|
|
}
|