Advent of code 2023/13
Ajax Direct

Answer

Part 1 :
Part 2 :
function find_mirror($pattern, $diff_allowed, $exclude, $mult) {
    for ($i = 1; $i < count($pattern); $i++) {
        $diff_found = 0;

        for ($j = 1; $j <= min($i, count($pattern) - $i); $j++) {
            if (!$diff_allowed && $pattern[$i - $j] != $pattern[$i + $j - 1]) continue 2;

            if ($diff_allowed) {
                $diff_found += strlen(
                    str_replace("0", "", decbin(bindec($pattern[$i - $j]) ^ bindec($pattern[$i + $j - 1])))
                );
                if ($diff_found > $diff_allowed) continue 2;
            }
        }

        if ($i * $mult != $exclude) return $i * $mult;
    }
    return null;
}

function solve($pattern, $diff_allowed, $exclude) {
    // Try horizontal
    $h = explode("\n", $pattern);
    if (($m = find_mirror($h, $diff_allowed, $exclude, 100)) != null) return $m;
    // Try vertical
    $v = array_map("join", array_map(null, ...$pattern->lines->map("str_split")));
    return find_mirror($v, $diff_allowed, $exclude, 1);
}

// ==================================================
// > SOLUTION
// ==================================================
[$solution_1, $solution_2] = $input->split("\n\n")->reduce(function ($carry, $p) {
    $p = $p->replace([".", "#"], ["0", "1"]);
    $p1 = solve($p, 0, false);
    $p2 = solve($p, 1, $p1);
    return [$carry[0] + $p1, $carry[1] + $p2];
}, [0, 0]);