Answer ⏱
[VISUALISATION]
Part 1 :
Part 2 :
const TO = ["N" => 1, "E" => 2, "S" => 4, "W" => 8];
const FROM = ["S" => 1, "W" => 2, "N" => 4, "E" => 8];
function get_grid($input) {
$grid = [];
$queue = [[[0, 0], substr($input, 1, -1)]];
while (count($queue)) {
[$pos, $path] = array_shift($queue);
if (empty($path)) continue;
// Get direct path until firt intersection is met
$direct = strpos($path, "(") !== false ? substr($path, 0, strpos($path, "(")) : $path;
// Follow path, save open doors each way using their bit values
for ($c = 0; $c < strlen($direct); $c++) {
$grid[$pos[1]][$pos[0]] = ($grid[$pos[1]][$pos[0]] ?? 0) | TO[$direct[$c]];
match ($direct[$c]) {
"N" => $pos[1]--,
"E" => $pos[0]++,
"S" => $pos[1]++,
"W" => $pos[0]--,
};
$grid[$pos[1]][$pos[0]] = ($grid[$pos[1]][$pos[0]] ?? 0) | FROM[$direct[$c]];
}
// Split intersection and queue each sub-path from the current position...
$rest = substr($path, strlen($direct) + 1);
if (!$rest) continue;
// ... find intersection group
$o = 1; $c = 0; $i = 0;
while ($o > $c) match ($rest[$i++]) {
"(" => $o++,
")" => $c++,
default => null
};
$group = substr($rest, 0, $i - 1);
// ... and queue each first-level parts
$item = ""; $o = 0; $c = 0;
for ($i = 0; $i < strlen($group); $i++) {
if ($group[$i] == "|" && $o == $c) {
$queue[] = [$pos, $item];
$item = "";
continue;
}
if ($group[$i] == "(") $o++;
if ($group[$i] == ")") $c++;
$item .= $group[$i];
}
$queue[] = [$pos, $item];
// Queue the rest
$rest = substr($rest, $i + 1);
$queue[] = [$pos, $rest];
}
return $grid;
}
// ==================================================
// > PART 1
// ==================================================
$grid = get_grid($input);
// Explore all doors
$graph = new Graph(function ($graph) use ($grid) {
[$x, $y] = explode(";", $graph->current);
$doors = array_filter(array_keys(TO), fn ($door) => $grid[$y][$x] & TO[$door]);
$rooms = (array) set([
"N" => [$x, $y - 1],
"E" => [$x + 1, $y],
"S" => [$x, $y + 1],
"W" => [$x - 1, $y],
])->keepKeys($doors)->map(fn ($xy) => implode(";", $xy));
return array_fill_keys($rooms, 1);
});
// When everything was explored, return furthest room
$graph->onCompletion(function ($graph) {
return max($graph->values);
});
$solution_1 = $graph->explore("0;0");
// ==================================================
// > PART 2
// ==================================================
$solution_2 = set($graph->values)->filter(fn ($v) => $v >= 1000)->count();