344 lines
9.2 KiB
C#
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);
|
|
}
|
|
} |