This commit is contained in:
eleith 2022-12-27 22:27:51 -08:00
parent d7571f643e
commit b32c9d18d6
4 changed files with 2456 additions and 0 deletions

44
18/README.md Normal file
View File

@ -0,0 +1,44 @@
--- Day 18: Boiling Boulders ---
You and the elephants finally reach fresh air. You've emerged near the base of a large volcano that seems to be actively erupting! Fortunately, the lava seems to be flowing away from you and toward the ocean.
Bits of lava are still being ejected toward you, so you're sheltering in the cavern exit a little longer. Outside the cave, you can see the lava landing in a pond and hear it loudly hissing as it solidifies.
Depending on the specific compounds in the lava and speed at which it cools, it might be forming obsidian! The cooling rate should be based on the surface area of the lava droplets, so you take a quick scan of a droplet as it flies past you (your puzzle input).
Because of how quickly the lava is moving, the scan isn't very good; its resolution is quite low and, as a result, it approximates the shape of the lava droplet with 1x1x1 cubes on a 3D grid, each given as its x,y,z position.
To approximate the surface area, count the number of sides of each cube that are not immediately connected to another cube. So, if your scan were only two adjacent cubes like 1,1,1 and 2,1,1, each cube would have a single side covered and five sides exposed, a total surface area of 10 sides.
Here's a larger example:
2,2,2
1,2,2
3,2,2
2,1,2
2,3,2
2,2,1
2,2,3
2,2,4
2,2,6
1,2,5
3,2,5
2,1,5
2,3,5
In the above example, after counting up all the sides that aren't connected to another cube, the total surface area is 64.
What is the surface area of your scanned lava droplet?
Your puzzle answer was 3346.
--- Part Two ---
Something seems off about your calculation. The cooling rate depends on exterior surface area, but your calculation also included the surface area of air pockets trapped in the lava droplet.
Instead, consider only cube sides that could be reached by the water and steam as the lava droplet tumbles into the pond. The steam will expand to reach as much as possible, completely displacing any air on the outside of the lava droplet but never expanding diagonally.
In the larger example above, exactly one cube of air is trapped within the lava droplet (at 2,2,5), so the exterior surface area of the lava droplet is 58.
What is the exterior surface area of your scanned lava droplet?
Your puzzle answer was 1980.

2170
18/input.txt Normal file

File diff suppressed because it is too large Load Diff

13
18/sample-input.txt Normal file
View File

@ -0,0 +1,13 @@
2,2,2
1,2,2
3,2,2
2,1,2
2,3,2
2,2,1
2,2,3
2,2,4
2,2,6
1,2,5
3,2,5
2,1,5
2,3,5

229
18/src/index.ts Normal file
View File

@ -0,0 +1,229 @@
import { readFileSync } from 'fs'
const CubeFace = {
front: 'front',
back: 'back',
top: 'top',
bottom: 'bottom',
left: 'left',
right: 'right',
} as const
const CubeFaceState = {
open: 'open',
closed: 'closed',
} as const
type CubeFaceState = keyof typeof CubeFaceState
type CubeFace = keyof typeof CubeFace
type CubeSpace = {
x: number
y: number
z: number
}
type CubeFaces = {
-readonly [key in keyof typeof CubeFace]: CubeFaceState
}
type Cube = CubeSpace & {
face: CubeFaces
}
type CubeMap = {
cubes: Cube[]
coordinates: {
[coordinates: string]: CubeSpace
}
bounds: {
x: [number, number]
y: [number, number]
z: [number, number]
}
}
const matchCube = /(\d+),(\d+),(\d+)/
function parseCubes(lines: string[]) {
return lines.map((line) => {
const match = line.match(matchCube)
if (match) {
const [x, y, z] = match.slice(1).map(Number)
return {
x,
y,
z,
face: {
top: CubeFaceState.open,
bottom: CubeFaceState.open,
left: CubeFaceState.open,
right: CubeFaceState.open,
front: CubeFaceState.open,
back: CubeFaceState.open,
},
}
} else {
throw new Error(`Invalid cube: ${line}`)
}
})
}
function spaceToKey(cube: CubeSpace) {
return `${cube.x},${cube.y},${cube.z}`
}
function countOpenFacesx(cube: Cube): number {
return Object.values(cube.face).reduce(
(open, face) => (face === CubeFaceState.open ? open + 1 : open),
0
)
}
function markAdjacentFaces(cubes: Cube[]) {
cubes.forEach((cube) => {
cubes.forEach((other) => {
if (cube.x === other.x && cube.y === other.y && cube.z - other.z === 1) {
cube.face.back = CubeFaceState.closed
other.face.front = CubeFaceState.closed
} else if (cube.x === other.x && cube.z === other.z && cube.y - other.y === 1) {
cube.face.bottom = CubeFaceState.closed
other.face.top = CubeFaceState.closed
} else if (cube.y === other.y && cube.z === other.z && cube.x - other.x === 1) {
cube.face.left = CubeFaceState.closed
other.face.right = CubeFaceState.closed
}
})
})
}
function getFaceSpace(cube: Cube, face: CubeFace) {
if (face === CubeFace.front) {
return { x: cube.x, y: cube.y, z: cube.z + 1 }
}
if (face === CubeFace.back) {
return { x: cube.x, y: cube.y, z: cube.z - 1 }
}
if (face === CubeFace.top) {
return { x: cube.x, y: cube.y + 1, z: cube.z }
}
if (face === CubeFace.bottom) {
return { x: cube.x, y: cube.y - 1, z: cube.z }
}
if (face === CubeFace.left) {
return { x: cube.x - 1, y: cube.y, z: cube.z }
}
if (face === CubeFace.right) {
return { x: cube.x + 1, y: cube.y, z: cube.z }
}
throw new Error(`Invalid face: ${face}`)
}
function isFaceEnclosed(cube: Cube, cubeFace: CubeFace, cubeMap: CubeMap) {
const faceSpace = getFaceSpace(cube, cubeFace)
const queue: CubeSpace[] = [faceSpace]
const visited: CubeMap['coordinates'] = { [`${spaceToKey(faceSpace)}`]: faceSpace }
let enclosed = true
while (queue.length > 0) {
const { x, y, z } = queue.shift() as CubeSpace
if (cubeMap.coordinates[`${x},${y},${z}`]) {
continue
}
if (x < cubeMap.bounds.x[0] || x > cubeMap.bounds.x[1]) {
enclosed = false
break
}
if (y < cubeMap.bounds.y[0] || y > cubeMap.bounds.y[1]) {
enclosed = false
break
}
if (z < cubeMap.bounds.z[0] || z > cubeMap.bounds.z[1]) {
enclosed = false
break
}
const neighbors = [
{ x: x - 1, y, z },
{ x: x + 1, y, z },
{ x, y: y - 1, z },
{ x, y: y + 1, z },
{ x, y, z: z - 1 },
{ x, y, z: z + 1 },
]
neighbors.forEach((neighbor) => {
if (!visited[`${spaceToKey(neighbor)}`]) {
queue.push(neighbor)
visited[`${spaceToKey(neighbor)}`] = neighbor
}
})
}
return enclosed
}
function makeCubeMap(cubes: Cube[]): CubeMap {
const Xs = cubes.map((cube) => cube.x)
const Ys = cubes.map((cube) => cube.y)
const Zs = cubes.map((cube) => cube.z)
return {
cubes,
coordinates: cubes.reduce((map, cube) => {
map[spaceToKey(cube)] = cube
return map
}, {} as CubeMap['coordinates']),
bounds: {
x: [Math.min(...Xs), Math.max(...Xs)],
y: [Math.min(...Ys), Math.max(...Ys)],
z: [Math.min(...Zs), Math.max(...Zs)],
},
}
}
function markEnclosedFaces(cubes: Cube[]) {
const cubeMap = makeCubeMap(cubes)
cubes.forEach((cube) => {
const faces = Object.keys(cube.face) as CubeFace[]
faces.forEach((face) => {
if (cube.face[face] === 'open' && isFaceEnclosed(cube, face, cubeMap)) {
cube.face[face] = CubeFaceState.closed
}
})
})
}
function solvePart1(lines: string[]): number {
const cubes = parseCubes(lines)
markAdjacentFaces(cubes)
return cubes.reduce((sum, cube) => sum + countOpenFacesx(cube), 0)
}
function solvePart2(lines: string[]): number {
const cubes = parseCubes(lines)
markAdjacentFaces(cubes)
markEnclosedFaces(cubes)
return cubes.reduce((sum, cube) => sum + countOpenFacesx(cube), 0)
}
function run() {
const input = process.argv.slice(2)[0]
const file = readFileSync(input, 'utf8')
const lines = file.split('\n').slice(0, -1)
const solution1 = solvePart1(lines)
console.log(`part1: ${solution1}`)
const solution2 = solvePart2(lines)
console.log(`part2: ${solution2}`)
}
run()