264 lines
7.0 KiB
C#
264 lines
7.0 KiB
C#
// See https://aka.ms/new-console-template for more information
|
|
using System.Reflection;
|
|
|
|
Part01();
|
|
Part02();
|
|
|
|
void Part01()
|
|
{
|
|
Simulation exampleSim = new Simulation(2);
|
|
Simulation puzzleSim = new Simulation(2);
|
|
|
|
exampleSim.LoadDataFile("example-input.txt");
|
|
exampleSim.SimulateAll(0, 5, 0, 5);
|
|
Console.WriteLine("Part01, Example Visited: " + exampleSim.TailVisitedCount);
|
|
|
|
puzzleSim.LoadDataFile("puzzle-input.txt");
|
|
puzzleSim.SimulateAll(-50, 50, -50, 50);
|
|
Console.WriteLine("Part01, Puzzle Visited: " + puzzleSim.TailVisitedCount);
|
|
}
|
|
|
|
void Part02()
|
|
{
|
|
Simulation exampleSim = new Simulation(10);
|
|
Simulation example2Sim = new Simulation(10);
|
|
Simulation puzzleSim = new Simulation(10);
|
|
|
|
exampleSim.LoadDataFile("example-input.txt");
|
|
exampleSim.SimulateAll(0, 6, 0, 5, false);
|
|
Console.WriteLine("Part02, Example Visited: " + exampleSim.TailVisitedCount);
|
|
|
|
example2Sim.LoadDataFile("example-input2.txt");
|
|
example2Sim.SimulateAll(-15,15,-10,10, false);
|
|
Console.WriteLine("Part02, Example2 Visited: " + example2Sim.TailVisitedCount);
|
|
|
|
puzzleSim.LoadDataFile("puzzle-input.txt");
|
|
puzzleSim.SimulateAll(-50, 50, -50, 50);
|
|
Console.WriteLine("Part02, Puzzle Visited: " + puzzleSim.TailVisitedCount);
|
|
}
|
|
|
|
class Position
|
|
{
|
|
public int x;
|
|
public int y;
|
|
|
|
public Position() : this(0,0) { }
|
|
public Position(int x, int y)
|
|
{
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
|
|
public Position(Position other) : this(other.x, other.y) { }
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
if (!(obj is Position)) return false;
|
|
Position p = (Position)obj;
|
|
|
|
return p.x == x & p.y == y;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return x ^ y;
|
|
}
|
|
|
|
public double distanceFrom(Position p)
|
|
{
|
|
return Math.Sqrt(Math.Pow(p.x - x, 2) + Math.Pow(p.y - y, 2));
|
|
}
|
|
|
|
public void distanceFrom(Position p, out int x, out int y)
|
|
{
|
|
x = p.x - this.x;
|
|
y = p.y - this.y;
|
|
}
|
|
}
|
|
|
|
class RopeBridge
|
|
{
|
|
public Position Head {
|
|
get
|
|
{
|
|
return rope[0];
|
|
}
|
|
}
|
|
|
|
public Position Tail
|
|
{
|
|
get
|
|
{
|
|
return rope[rope.Count - 1];
|
|
}
|
|
set
|
|
{
|
|
rope[rope.Count - 1] = value;
|
|
}
|
|
}
|
|
|
|
List<Position> rope = new List<Position>();
|
|
|
|
public RopeBridge(int bridgeLength)
|
|
{
|
|
for (int i = 0; i < bridgeLength; i++)
|
|
{
|
|
rope.Add(new Position());
|
|
}
|
|
}
|
|
|
|
private void UpdateKnot(Position parent, int index)
|
|
{
|
|
double distance = rope[index - 1].distanceFrom(rope[index]);
|
|
|
|
if (distance >= 2)
|
|
{
|
|
int x, y;
|
|
rope[index].distanceFrom(parent, out x, out y);
|
|
|
|
if (x > 0) rope[index].x++;
|
|
if (y > 0) rope[index].y++;
|
|
if (x < 0) rope[index].x--;
|
|
if (y < 0) rope[index].y--;
|
|
|
|
// Only update if it actually moved
|
|
if (index + 1 < rope.Count)
|
|
{
|
|
UpdateKnot(rope[index], index + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void MoveHead(char direction)
|
|
{
|
|
Position oldHead = new Position(Head);
|
|
switch (direction)
|
|
{
|
|
case 'U':
|
|
Head.y++;
|
|
break;
|
|
case 'D':
|
|
Head.y--;
|
|
break;
|
|
case 'L':
|
|
Head.x--;
|
|
break;
|
|
case 'R':
|
|
Head.x++;
|
|
break;
|
|
default:
|
|
throw new InvalidDataException();
|
|
}
|
|
|
|
UpdateKnot(Head, 1);
|
|
}
|
|
|
|
public void PrintBridge(int x_min, int x_max, int y_min, int y_max)
|
|
{
|
|
for (int j = y_max; j >= y_min; j--)
|
|
{
|
|
for (int i = x_min; i < x_max; i++)
|
|
{
|
|
var index = rope.IndexOf(new Position(i, j));
|
|
|
|
if (index != -1)
|
|
{
|
|
var query = rope.Where(pos => pos.x == i && pos.y == j);
|
|
|
|
if (query.Count() > 1)
|
|
{
|
|
for (index = 0; index < rope.Count; index++)
|
|
{
|
|
if (rope[index].x == i && rope[index].y == j)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (index == 0)
|
|
{
|
|
Console.Write('H');
|
|
}
|
|
else if (index + 1 == rope.Count)
|
|
{
|
|
Console.Write('T');
|
|
}
|
|
else
|
|
{
|
|
Console.Write(index);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Write('.');
|
|
}
|
|
}
|
|
Console.WriteLine();
|
|
}
|
|
|
|
Console.WriteLine();
|
|
}
|
|
|
|
}
|
|
|
|
class Simulation
|
|
{
|
|
RopeBridge bridge;
|
|
Dictionary<Position, bool> visited = new Dictionary<Position, bool>();
|
|
List<Tuple<char, int>> simulationSteps = new List<Tuple<char, int>>();
|
|
|
|
public Simulation(int bridgeLength)
|
|
{
|
|
bridge = new RopeBridge(bridgeLength);
|
|
}
|
|
|
|
public void LoadDataFile(string filename)
|
|
{
|
|
using (StreamReader reader = System.IO.File.OpenText(filename))
|
|
{
|
|
while (!reader.EndOfStream)
|
|
{
|
|
string? line = reader.ReadLine();
|
|
if (line == null) throw new InvalidDataException();
|
|
|
|
var split = line.Split(' ');
|
|
if (!Char.IsAsciiLetter(split[0][0])) throw new InvalidDataException();
|
|
if (!Char.IsAsciiDigit(split[1][0])) throw new InvalidDataException();
|
|
|
|
simulationSteps.Add(new Tuple<char, int>(split[0][0], Int32.Parse(split[1])));
|
|
}
|
|
}
|
|
}
|
|
|
|
public int TailVisitedCount
|
|
{
|
|
get { return visited.Count; }
|
|
}
|
|
|
|
public void SimulateAll(int x_min, int x_max, int y_min, int y_max, bool print = false)
|
|
{
|
|
if (print) bridge.PrintBridge(x_min, x_max, y_min, y_max);
|
|
|
|
foreach (var pair in simulationSteps)
|
|
{
|
|
if (print) Console.WriteLine(String.Format(" === {0} {1} ===", pair.Item1, pair.Item2));
|
|
|
|
Simulate(pair.Item1, pair.Item2, x_min, x_max, y_min, y_max, print);
|
|
}
|
|
}
|
|
|
|
public void Simulate(char direction, int count, int x_min, int x_max, int y_min, int y_max, bool print = false)
|
|
{
|
|
// Visit initial state always
|
|
visited.TryAdd(new Position(bridge.Tail), true);
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
bridge.MoveHead(direction);
|
|
visited.TryAdd(new Position(bridge.Tail), true);
|
|
|
|
if (print) bridge.PrintBridge(x_min, x_max, y_min, y_max);
|
|
}
|
|
}
|
|
} |