Advent of code 2022/14
Ajax Direct

Answer 5296ms

Part 1 : 674 Part 2 : 24958
// Build the map
$grid = grid();
$grid->set([500, 0], "+");

foreach ($input->lines as $line) {
    $corners = $line->numbers()->chunk(2);
    $c[] = $corners;

    for ($i = 0; $i < $corners->count() - 1; $i++) {
        $grid->drawLine((array) $corners[$i], (array) $corners[$i + 1], "#");
    }
}

function get_solution($grid, $with_floor = false)
{
    $floor = $grid->rows()->keys()->max() + ($with_floor ? 2 : 0);
    $rest  = 0;

    // Draw floor
    if ($with_floor) foreach (xy([-1000, $floor])->pathTo(xy([1000, $floor])) as $cell) {
        $grid->set($cell, "#");
    }

    // Start moving sand
    while (true) {

        // Source is covered in sand, stop
        if ($with_floor && $grid->get([500, 0]) === "o") break;

        // Emit a new sand and move it one step at a time
        $sand = xy([500, 0]);
        while (true) {
            $sand->move([0, 1]);

            // Reached the bottom and has no floor, stop there
            if (!$with_floor && $sand[1] > $floor) break 2;

            // Nothing, keep falling
            if (!$grid->get($sand)) continue;

            // Hit something, try to go left
            $sand->move([-1, 0]);
            if (!$grid->get($sand)) continue;

            // Hit something, try to go right
            $sand->move([2, 0]);
            if (!$grid->get($sand)) continue;

            // Nowhere to go, stack on top
            $sand->move([-1, -1]);
            $grid->set($sand, "o");
            $rest++;
            break;
        }
    }

    return [$rest, $grid];
}

// ==================================================
// > SOLUTIONS
// ==================================================
$solution_1 = get_solution(clone $grid)[0];
$solution_2 = get_solution($grid, true)[0];