<?php
	$input = <<<'EOF'
1....X.......
2..X..X...X....
3X.X...X..X.....
4X....XXXXXX.....
5X..XXX...........
6.....X..........
7.........X....X
8..X......X....X....
9..X......X....X....X...
A....X.....
B.X..X..
C.....
DXXX
EXXX
FXXX
EOF;
    
    $pattern = '/
    ^                   
    (?:(?|
      (?(5)(?![\s\S]*+\5))
      (?!(?!)()())      
      (?=
        (?:
          .          
          (?=        
            .*+\n    
            ( \3? . )
            .*+\n    
            ( \4? . )
          )
        )*?         
        X .*+\n     
        \3          
        X .*+\n     
        \4          
        X           
      )
      ()
    |
      (?(5)(?=[\s\S]*+\5)|(?!))
      (?:
        .
        (?=
          .*+\n
          ( \1? .)
          .*+\n
          ( \2? .)
        )
      )+?
      (?=
        (?<=X) .*+\n       
        (\1)               
        (?<=X) .*+\n       
        (\2)               
        (?<=X)             
      )
      (?=
        ([\s\S])    
        [\s\S]*
        (. (?(6)\6))
      )
    ){2})+
    /xm';
    
    echo "Input:\n", $input;

    $str = preg_replace_callback($pattern, 
        function($match) {
            return '!' . $match[0];
        }
        , $input);
    
    $str = preg_replace('/^(?!!)/m', '-', $str);
    
    echo "\n\nLines containing at least one match are marked with '!':\n", $str;
    
    preg_match_all($pattern, $input, $matches);
    echo "\n\nThis is group 6 for each match. Each subarray represents a
    matching line. The amount of characters indicates how many matches there are
    in that particular line. You could find out which line this corresponds to
    by using PREG_CAPTURE_OFFSET and inspecting group 0 (the entire match).";
    print_r($matches[6]);
?>