// See https://aka.ms/new-console-template for more information using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Security.Cryptography.X509Certificates; Part01(); void Part01() { Console.WriteLine("!! === Part 01 === !!"); Console.WriteLine(" === Example Simple ==="); var exampleSimpleData = new ProblemData("example-input-simple.txt"); exampleSimpleData.Print(); Console.WriteLine(" === Example ==="); var exampleData = new ProblemData("example-input.txt"); exampleData.Print(); Console.WriteLine(" === Puzzle ==="); var puzzleData = new ProblemData("puzzle-input.txt"); puzzleData.Print(); } struct Vertex : IComparable { public int X; public int Y; public int Z; public Vertex() : this(int.MinValue, int.MinValue, int.MinValue) { } public Vertex(int x, int y, int z) { this.X = x; this.Y = y; this.Z = z; } public int CompareTo(object? obj) { if (obj == null) throw new InvalidCastException(); var other = (Vertex)obj; if (this.X != other.X) { return this.X.CompareTo(other.X); } else if (this.Y != other.Y) { return this.Y.CompareTo(other.Y); } else if (this.Z != other.Z) { return this.Z.CompareTo(other.Z); } return 0; } public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); } public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null) return false; var other = (Vertex)obj; return this.CompareTo(other) == 0; } } class Cube { public Cube(int x, int y, int z) { init(x, y, z); } public Cube(string data) { var split = data.Split(','); init(int.Parse(split[0]), int.Parse(split[1]), int.Parse(split[2])); } private void init(int x, int y, int z) { X = x; Y = y; Z = z; List vertices = new List(); // Create the verts (we'll store CCW) for (int d = 0; d < 2; d++) { vertices.Add(new Vertex(x, y, z + d)); vertices.Add(new Vertex(x+1, y, z + d)); vertices.Add(new Vertex(x+1, y+1, z + d)); vertices.Add(new Vertex(x, y+1, z + d)); } Vertices = vertices.ToArray(); } public Vertex[] Vertices { get; private set; } = new Vertex[0]; public int X { get; set; } public int Y { get; set; } public int Z { get; set; } public override string ToString() { return String.Format("Cube at {0},{1},{2}", X, Y, Z); } public override bool Equals(object? obj) { if (obj == null) return false; var other = (Cube)obj; return X == other.X && Y == other.Y && Z == other.Z; } public override int GetHashCode() { return X ^ Y ^ Z; } public bool IsAdjacent(Cube c) { var faceAdjacents = new Cube[] { // Directly to the sides new Cube(c.X+1, c.Y, c.Z), new Cube(c.X, c.Y+1, c.Z), new Cube(c.X, c.Y, c.Z+1), new Cube(c.X-1, c.Y, c.Z), new Cube(c.X, c.Y-1, c.Z), new Cube(c.X, c.Y, c.Z-1), }; foreach (var f in faceAdjacents) { if (f.Equals(this)) return true; } return false; } public Cube[] Adjacents { get { var faceAdjacents = new Cube[] { // Directly to the sides new Cube(this.X+1, this.Y, this.Z), new Cube(this.X, this.Y+1, this.Z), new Cube(this.X, this.Y, this.Z+1), new Cube(this.X-1, this.Y, this.Z), new Cube(this.X, this.Y-1, this.Z), new Cube(this.X, this.Y, this.Z-1), }; return faceAdjacents; } } } class ProblemData { public HashSet Cubes { get; } public HashSet AirCubes { get; private set; } = new HashSet(); public ProblemData(string filename) { Cubes = new HashSet(); using (StreamReader reader = System.IO.File.OpenText(filename)) { while (!reader.EndOfStream) { var line = reader.ReadLine(); if (line == null) throw new InvalidDataException(); var newCube = new Cube(line); if (Cubes.Contains(newCube)) throw new InvalidDataException(); Cubes.Add(newCube); } } } public static int GetExposedSides(Cube c, HashSet cubes) { int total = 0; var faceAdjacents = new Cube[] { // Directly to the sides new Cube(c.X+1, c.Y, c.Z), new Cube(c.X, c.Y+1, c.Z), new Cube(c.X, c.Y, c.Z+1), new Cube(c.X-1, c.Y, c.Z), new Cube(c.X, c.Y-1, c.Z), new Cube(c.X, c.Y, c.Z-1), }; var angledAdjacents = new Cube[] { new Cube(c.X+1, c.Y, c.Z-1), // Right and closer to camera new Cube(c.X-1, c.Y, c.Z-1), // Left and closer to camera new Cube(c.X+1, c.Y, c.Z+1), // Right and farther from camera new Cube(c.X-1, c.Y, c.Z+1), // Left and farther from camera new Cube(c.X, c.Y+1, c.Z-1), // Up and closer to the camera new Cube(c.X, c.Y+1, c.Z+1), // Up and farther from camera new Cube(c.X, c.Y-1, c.Z-1), // Down and closer to camera new Cube(c.X, c.Y-1, c.Z+1), // Down and farther from camera }; foreach (var adjacent in faceAdjacents) { if (cubes.Contains(adjacent)) { continue; } else { total++; } } return total; } public int SurfaceArea { get { return GetSurfaceArea(Cubes); } } private int GetSurfaceArea(HashSet cubes) { int area = 0; foreach (var c in cubes) { area += GetExposedSides(c, cubes); } return area; } public int ExteriorSurfaceArea { get { HashSet exterior = new HashSet(); FillExterior(new Cube(Bounds[0].X, Bounds[0].Y, Bounds[0].Z), Cubes, exterior); int area = 0; foreach (var cube in Cubes) { foreach (var adjacent in cube.Adjacents) { if (exterior.Contains(adjacent)) area++; } } return area; } } public void FillExterior(Cube startingCube, HashSet dropletCubes, HashSet exterior) { List toTest = new List(); toTest.Add(startingCube); do { var c = toTest[0]; toTest.RemoveAt(0); if (c.X >= Bounds[0].X - 1 && c.Y >= Bounds[0].Y - 1 && c.Z >= Bounds[0].Z - 1 && c.X <= Bounds[1].X + 1 && c.Y <= Bounds[1].Y + 1 && c.Z <= Bounds[1].Z + 1 && !dropletCubes.Contains(c) && !exterior.Contains(c)) { exterior.Add(c); foreach (var face in c.Adjacents) { // C# doesnt support tail recursion innately //FillExterior(face, dropletCubes, exterior); toTest.Add(face); } } } while (toTest.Count > 0); } private bool HasCubeBelow(Cube c) { return Cubes.Contains(new Cube(c.X, c.Y-1, c.Z)); } public Vertex[] Bounds { get { if (mBounds.Length == 0) { Vertex bottomLeft = new Vertex(int.MaxValue, int.MaxValue, int.MaxValue); Vertex topRight = new Vertex(int.MinValue,int.MinValue,int.MinValue); foreach (var c in Cubes) { bottomLeft.X = int.Min(c.X, bottomLeft.X); bottomLeft.Y = int.Min(c.Y, bottomLeft.Y); bottomLeft.Z = int.Min(c.Z, bottomLeft.Z); topRight.X = int.Max(c.X, topRight.X); topRight.Y = int.Max(c.Y, topRight.Y); topRight.Z = int.Max(c.Z, topRight.Z); } mBounds = new Vertex[] { bottomLeft, topRight }; } return mBounds; } } private Vertex[] mBounds = new Vertex[0]; public void Print() { Console.WriteLine("Cubes {0} with surface area {1} and {2} exterior area", Cubes.Count, SurfaceArea, ExteriorSurfaceArea); } }