import { readFileSync } from 'node:fs'; const input = readFileSync('input', 'utf-8'); const cubes = input .split('\n') .filter(line => !!line.trim()) .map(coords => { const [x, y, z] = coords.split(',').map(n => parseInt(n)); return {x, y, z}; }); const surfaceArea = cubes.map(cubeA => { const touchingCubes = cubes.filter(cubeB => { const xDiff = Math.abs(cubeA.x - cubeB.x); const yDiff = Math.abs(cubeA.y - cubeB.y); const zDiff = Math.abs(cubeA.z - cubeB.z); return ( (xDiff === 1 && yDiff === 0 && zDiff === 0) || (yDiff === 1 && zDiff === 0 && xDiff === 0) || (zDiff === 1 && xDiff === 0 && yDiff === 0) ); }); const nonConnectedSides = 6 - touchingCubes.length; return nonConnectedSides; }).reduce((total, sides) => total + sides); console.log(`The surface area of the droplet is: ${surfaceArea}`); function minMax(dimension) { return [ cubes.reduce((min, coords) => Math.min(min, coords[dimension]), Infinity), cubes.reduce((max, coords) => Math.max(max, coords[dimension]), -Infinity), ]; } function isCoordEqual(coordA, coordB) { return coordA.every((v, i) => coordB[i] === v); } function uniqueCoordsFilter(coordA, i, coords) { return coords.findLastIndex(coordB => isCoordEqual(coordA, coordB)) === i; } function getAllConnected(startX, startY, startZ, isConnected) { const connected = []; let toVisit = [[startX, startY, startZ]]; while (true) { connected.push(...toVisit); toVisit = toVisit.flatMap(([x, y, z]) => [ [x + 1, y, z ], [x - 1, y, z ], [x, y + 1, z ], [x, y - 1, z ], [x, y, z + 1], [x, y, z - 1] ]) .filter(coordA => !connected.find(coordB => isCoordEqual(coordA, coordB)) ) .filter(uniqueCoordsFilter) .filter(([x, y, z]) => isConnected(x, y, z)); if (!toVisit.length) { break; } } return connected; } const [minX, maxX] = minMax('x'); const [minY, maxY] = minMax('y'); const [minZ, maxZ] = minMax('z'); // Get all coordinates external to droplet with a border of 2 const connected = getAllConnected(minX - 2, minY - 2, minZ - 2, (x, y, z) => { return ( x >= minX - 2 && x <= maxX + 2 && y >= minY - 2 && y <= maxY + 2 && z >= minZ - 2 && z <= maxZ + 2 && // Not part of the droplet !cubes.find(coord => coord.x === x && coord.y === y && coord.z === z) ); }); // Calculate internal surface area of inverted droplet const externalSurfaceArea = connected.map(([ax, ay, az]) => { // Skip coordinates right on the edge if ( ax < minX - 1 || ax > maxX + 1 || ay < minY - 1 || ay > maxY + 1 || az < minZ - 1 || az > maxZ + 1 ) { return 0; } const touchingCubes = connected.filter(([bx, by, bz]) => { const xDiff = Math.abs(ax - bx); const yDiff = Math.abs(ay - by); const zDiff = Math.abs(az - bz); return ( (xDiff === 1 && yDiff === 0 && zDiff === 0) || (yDiff === 1 && zDiff === 0 && xDiff === 0) || (zDiff === 1 && xDiff === 0 && yDiff === 0) ); }); const nonConnectedSides = 6 - touchingCubes.length; return nonConnectedSides; }).reduce((total, sides) => total + sides); console.log( `The EXTERNAL surface area of the droplet is: ${externalSurfaceArea}` );