Advent of code 2016/17
Ajax Direct

Answer

Part 1 :
Part 2 :
$graph = new Graph(function ($graph) {
    [$x, $y, $path] = explode(";", $graph->current);

    $doors = array_combine(
        ["U", "D", "L", "R"],
        array_map(fn ($chr) => $chr >= "b", str_split(substr(md5($graph->passcode . $path), 0, 4)))
    );

    $ways = [
        "U" => $y     ? [$x, $y - 1] : false,
        "D" => $y < 3 ? [$x, $y + 1] : false,
        "L" => $x     ? [$x - 1, $y] : false,
        "R" => $x < 3 ? [$x + 1, $y] : false,
    ];

    $states = [];
    foreach ($ways as $dir => $way) {
        if ($way && $doors[$dir]) {
            $states[implode(";", [...$way, $path . $dir])] = 1;
        }
    }

    return $states;
});

$graph->defineEnd(function ($graph) {
    [$x, $y, $path] = explode(";", $graph->current);
    return $x == 3 && $y == 3;
});

$graph->onEnd(fn ($graph) => set(explode(";", $graph->current))->last());

// ==================================================
// > PART 1
// ==================================================
$graph->passcode = $input;
$solution_1 = $graph->explore("0;0;");

// ==================================================
// > PART 2
// ==================================================
$graph->setTarget("highest");

// If reached the vault : stop there and register path
$graph->addPruning(function ($states, $graph) {
    [$x, $y, $path] = explode(";", $graph->current);
    if ($x == 3 && $y == 3) {
        $graph->ends[$graph->current] = $graph->values[$graph->current];
        return [];
    };
    return $states;
});

// When every possible state was explored, return the end state with the maximum value
$graph->onCompletion(function ($graph) {
    return set($graph->ends)->max();
});

$solution_2 = $graph->explore("0;0;");