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 CaveData = new Dictionary(); 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; } }