Files
AdventOfCode/2022/Day18CSharp/Program.cs
2025-11-30 20:28:10 -05:00

344 lines
9.2 KiB
C#

// 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<Vertex> vertices = new List<Vertex>();
// 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<Cube> Cubes { get; }
public HashSet<Cube> AirCubes { get; private set; } = new HashSet<Cube>();
public ProblemData(string filename)
{
Cubes = new HashSet<Cube>();
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<Cube> 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<Cube> cubes)
{
int area = 0;
foreach (var c in cubes)
{
area += GetExposedSides(c, cubes);
}
return area;
}
public int ExteriorSurfaceArea
{
get
{
HashSet<Cube> exterior = new HashSet<Cube>();
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<Cube> dropletCubes, HashSet<Cube> exterior)
{
List<Cube> toTest = new List<Cube>();
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);
}
}