$grid = grid($input);
[$w, $h] = [$grid->width(), $grid->height()];
// Parse all portals
[$portals, $outer, $inner] = set($grid->searchAll("."))->reduce(function ($carry, $xy) use ($grid, $w, $h) {
$n = $grid->getNeighbors($xy)->keep(range("A", "Z"));
if ($n->empty()) return $carry;
$first = $n->values()[0];
$nn = $grid->getNeighbors(explode(";", $n->keys()[0]));
$second = $nn->keep(range("A", "Z"))->values()[0];
$dir = $nn->values()->search($second);
$portal = $dir == 1 || $dir == 2 ? $first . $second : $second . $first;
$carry[0][$portal][] = $xy;
$carry[($xy[1] == 2 || $xy[1] == $h - 3 || $xy[0] == 2 || $xy[0] == $w - 3) ? 1 : 2][] = implode(";", $xy);
return $carry;
}, [[], [], []]);
$shortcuts = set([]);
foreach ($portals as $xy) {
if (count($xy) > 1) {
$shortcuts[implode(";", $xy[0])] = implode(";", $xy[1]);
$shortcuts[implode(";", $xy[1])] = implode(";", $xy[0]);
}
}
$outer = array_diff($outer, [implode(";", $portals["AA"][0]), implode(";", $portals["ZZ"][0])]);
// ==================================================
// > PART 1 : Could be optimized by creating simpler graph of portal to portal
// ==================================================
[$path, $solution_1] = (new Graph(function ($graph) use ($grid, $shortcuts) {
$pos = explode(";", $graph->current);
return $grid->getNeighbors($pos)->keep(".")->map(fn () => 1)
->mapAssoc(fn ($k, $v) => isset($shortcuts[$k]) ? [$shortcuts[$k] => 2] : [$k => 1]);
}))
->explore(implode(";", $portals["AA"][0]), implode(";", $portals["ZZ"][0]));
// ==================================================
// > PART 2 : same
// ==================================================
[$path, $solution_2] = (new Graph(function ($graph) use ($grid, $shortcuts, $inner, $outer) {
[$pos, $depth] = explode("|", $graph->current);
$pos = explode(";", $pos); $depth = (int) $depth;
return $grid->getNeighbors($pos)->keep(".")->map(fn () => 1)
->mapAssoc(function ($k, $v) use ($depth, $inner, $outer, $shortcuts) {
[$state, $distance] = isset($shortcuts[$k]) ? [$shortcuts[$k], 2] : [$k, 1];
$new_depth = ($depth + (in_array($k, $inner) ? 1 : (in_array($k, $outer) ? -1 : 0)));
if ($new_depth < 0) return [];
return [$state . "|" . $new_depth => $distance];
});
}))
->explore(implode(";", $portals["AA"][0])."|0", implode(";", $portals["ZZ"][0])."|0");