function parse($input) {
foreach ($input->lines as $i=>$l) {
[$x1, $y1, $z1, $x2, $y2, $z2] = $l->numbers();
foreach (range($x1, $x2) as $x) foreach (range($y1, $y2) as $y) foreach (range($z1, $z2) as $z) {
$bricks[$i][] = [$x, $y, $z];
$grid[$x][$y][$z] = $i;
}
}
$order = array_keys($bricks);
usort($order, fn($a, $b) => $bricks[$a][0][2] <=> $bricks[$b][0][2]);
return [$bricks, $grid, $order];
}
function get_bricks_under($id, $bricks, $grid) {
$under = [];
foreach ($bricks[$id] as $cube) {
[$x, $y, $z] = $cube;
if ($z == 1) return ["floor"];
if ($z != $bricks[$id][0][2]) break;
if (isset($grid[$x][$y][$z-1])) $under[] = $grid[$x][$y][$z-1];
}
return array_unique($under);
}
function make_brick_fall($id, &$bricks, &$grid) {
$cubes = &$bricks[$id];
foreach ($cubes as &$cube) {
[$x, $y, $z] = $cube;
unset($grid[$x][$y][$z]);
$cube[2] = $z - 1;
$grid[$x][$y][$z - 1] = $id;
}
}
// ==================================================
// > PART 1
// ==================================================
[$bricks, $grid, $order] = parse($input);
foreach ($order as $id) {
while (!($under[$id] = get_bricks_under($id, $bricks, $grid))) make_brick_fall($id, $bricks, $grid);
foreach ($under[$id] as $u) $over[$u][] = $id;
if (count($under[$id]) == 1) $only_under = array_merge($only_under??[], $under[$id]);
}
$only_under = array_unique(array_diff($only_under, ["floor"]));
$solution_1 = count($bricks) - count($only_under);
// ==================================================
// > PART 2
// ==================================================
$solution_2 = 0;
foreach ($only_under as $id) {
$queue = [$id];
$felt = [];
$u = $under;
while ($queue) {
$id = array_shift($queue);
if (isset($felt[$id])) continue;
$felt[$id] = true;
foreach (($over[$id]??[]) as $i) {
$u[$i] = array_diff($u[$i], [$id]);
if (empty($u[$i])) $queue[] = $i;
}
}
$solution_2 += count($felt) - 1;
}