264 lines
7.7 KiB
C#
264 lines
7.7 KiB
C#
using System.Drawing;
|
|
|
|
Part01();
|
|
Part02();
|
|
|
|
void Part01()
|
|
{
|
|
var exampleCave = new Cave("example-input.txt");
|
|
exampleCave.PrintCave();
|
|
int result = exampleCave.DropUntilOverflow();
|
|
exampleCave.PrintCave();
|
|
Console.WriteLine("Example Number of Sand: {0}", result);
|
|
|
|
var puzzleCave = new Cave("puzzle-input.txt");
|
|
puzzleCave.PrintCave();
|
|
result = puzzleCave.DropUntilOverflow();
|
|
puzzleCave.PrintCave();
|
|
Console.WriteLine("Puzzle Number of Sand: {0}", result);
|
|
}
|
|
|
|
void Part02()
|
|
{
|
|
var exampleCave = new Cave("example-input.txt");
|
|
exampleCave.PrintCave();
|
|
int result = exampleCave.DropUntilSourceIsPlugged();
|
|
exampleCave.PrintCave();
|
|
Console.WriteLine("Example Number of Sand: {0}", result);
|
|
|
|
var puzzleCave = new Cave("puzzle-input.txt");
|
|
puzzleCave.PrintCave();
|
|
result = puzzleCave.DropUntilSourceIsPlugged();
|
|
puzzleCave.PrintCave();
|
|
Console.WriteLine("Puzzle Number of Sand: {0}", result);
|
|
}
|
|
|
|
class Cave
|
|
{
|
|
public enum BlockType
|
|
{
|
|
None,
|
|
Sand,
|
|
Rock,
|
|
SandSource
|
|
}
|
|
public Dictionary<Point, BlockType> CaveData = new Dictionary<Point, BlockType>();
|
|
public Point SandSource { get; set; } = new Point(500, 0);
|
|
|
|
public Cave(string filename)
|
|
{
|
|
using (StreamReader reader = System.IO.File.OpenText(filename))
|
|
{
|
|
while (!reader.EndOfStream)
|
|
{
|
|
string? line = reader.ReadLine();
|
|
if (line == null) { throw new InvalidDataException(); }
|
|
|
|
var vectors = line.Split("->", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
Point lastPoint = ReadPoint(vectors[0]);
|
|
for (int i = 1; i < vectors.Length; i++)
|
|
{
|
|
Point nextPoint = ReadPoint(vectors[i]);
|
|
CaveData[nextPoint] = BlockType.Rock;
|
|
|
|
int dir;
|
|
if (lastPoint.X != nextPoint.X)
|
|
{
|
|
if (lastPoint.X < nextPoint.X)
|
|
{
|
|
dir = 1;
|
|
}
|
|
else
|
|
{
|
|
dir = -1;
|
|
}
|
|
|
|
while (lastPoint.X != nextPoint.X)
|
|
{
|
|
CaveData[lastPoint] = BlockType.Rock;
|
|
lastPoint.X += dir;
|
|
}
|
|
}
|
|
else if (lastPoint.Y != nextPoint.Y)
|
|
{
|
|
if (lastPoint.Y < nextPoint.Y)
|
|
{
|
|
dir = 1;
|
|
}
|
|
else
|
|
{
|
|
dir = -1;
|
|
}
|
|
|
|
while (lastPoint.Y != nextPoint.Y)
|
|
{
|
|
CaveData[lastPoint] = BlockType.Rock;
|
|
lastPoint.Y += dir;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CaveData.ContainsKey(SandSource))
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
|
|
CaveData[SandSource] = BlockType.SandSource;
|
|
FindBounds();
|
|
TrueBottom = Bounds.Y + Bounds.Height + 2;
|
|
}
|
|
|
|
public void PrintCave()
|
|
{
|
|
PrintCave(new Point(-1, -1));
|
|
}
|
|
|
|
public void PrintCave(Point sand)
|
|
{
|
|
int x_min = Bounds.Left, x_max = x_min + Bounds.Width, y_min = Bounds.Top, y_max = y_min + Bounds.Height;
|
|
|
|
for (int y = y_min; y <= y_max; y++)
|
|
{
|
|
for (int x = x_min; x <= x_max; x++)
|
|
{
|
|
var point = new Point(x, y);
|
|
|
|
if (sand == point)
|
|
{
|
|
Console.Write('o');
|
|
}
|
|
else if (CaveData.ContainsKey(point))
|
|
{
|
|
var item = CaveData[point];
|
|
|
|
if (item == BlockType.Rock)
|
|
{
|
|
Console.Write("#");
|
|
}
|
|
else if (item == BlockType.Sand)
|
|
{
|
|
Console.Write("o");
|
|
}
|
|
else if (item == BlockType.SandSource)
|
|
{
|
|
Console.Write("+");
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Write('.');
|
|
}
|
|
}
|
|
Console.WriteLine();
|
|
}
|
|
}
|
|
|
|
public int DropUntilOverflow()
|
|
{
|
|
int count = 0;
|
|
|
|
while(!DropSand()) { count++; }
|
|
|
|
return count;
|
|
}
|
|
|
|
public int DropUntilSourceIsPlugged()
|
|
{
|
|
int count = 0;
|
|
|
|
while (CaveData[SandSource] != BlockType.Sand)
|
|
{
|
|
DropSand(false);
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
public bool DropSand(bool returnEarly = true, bool printEachStep = false)
|
|
{
|
|
Point sandPosition = new Point(SandSource.X, SandSource.Y);
|
|
|
|
do
|
|
{
|
|
if (printEachStep) PrintCave(sandPosition);
|
|
|
|
Point down = new Point(sandPosition.X, sandPosition.Y + 1);
|
|
Point downLeft = new Point(sandPosition.X - 1, sandPosition.Y + 1);
|
|
Point downRight = new Point(sandPosition.X + 1, sandPosition.Y + 1);
|
|
|
|
if (!CaveData.ContainsKey(down) && down.Y != TrueBottom)
|
|
{
|
|
sandPosition = down;
|
|
}
|
|
else if (!CaveData.ContainsKey(downLeft) && downLeft.Y != TrueBottom)
|
|
{
|
|
sandPosition = downLeft;
|
|
}
|
|
else if (!CaveData.ContainsKey(downRight) && downRight.Y != TrueBottom)
|
|
{
|
|
sandPosition = downRight;
|
|
}
|
|
else
|
|
{
|
|
// Cant go anywhere, so need to stop
|
|
CaveData[sandPosition] = BlockType.Sand;
|
|
return false;
|
|
}
|
|
|
|
// Fell off the world
|
|
if (!Bounds.Contains(sandPosition))
|
|
{
|
|
FindBounds();
|
|
if (returnEarly)
|
|
return true;
|
|
}
|
|
|
|
} while (true);
|
|
}
|
|
|
|
private Rectangle Bounds { get; set; }
|
|
private int TrueBottom { get; set; }
|
|
|
|
private void FindBounds()
|
|
{
|
|
int x_min = int.MaxValue;
|
|
int x_max = int.MinValue;
|
|
int y_min = 0; // sand always starts at 0
|
|
int y_max = int.MinValue;
|
|
|
|
foreach (var item in CaveData)
|
|
{
|
|
if (item.Value == BlockType.None) throw new InvalidDataException();
|
|
|
|
x_min = Int32.Min(item.Key.X, x_min);
|
|
x_max = Int32.Max(item.Key.X, x_max);
|
|
y_min = Int32.Min(item.Key.Y, y_min);
|
|
y_max = Int32.Max(item.Key.Y, y_max);
|
|
}
|
|
|
|
Bounds = new Rectangle(x_min, y_min, x_max-x_min, y_max-y_min);
|
|
}
|
|
|
|
private Point ReadPoint(string s)
|
|
{
|
|
Point p = new Point();
|
|
var split = s.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
p.X = Int32.Parse(split[0]);
|
|
p.Y = Int32.Parse(split[1]);
|
|
|
|
return p;
|
|
}
|
|
} |