Language: C++

EPS = 1e-9

class Point
  attr_accessor :x, :y
  def initialize(x = 0, y = 0)
    @x = x
    @y = y
  end

  def +(right)
    Point.new(@x + right.x, @y + right.y)
  end
  def -(right)
    Point.new(@x - right.x, @y - right.y)
  end
  def [](idx)
    idx == 0 ? @x : @y
  end
  def []=(idx, val)
    @x = val if idx == 0
    @y = val if idx == 1
  end
end

class Tetragon
  attr_accessor :points
  def initialize()
    @points = Array.new(4){ Point.new }
  end
  def [](idx)
    @points[idx]
  end
end

def dot(a, b)
  a.x * b.x + a.y * b.y
end

def cross(a, b)
  a.x * b.y - b.x * a.y
end

def is_intersected_ls(a1, a2, b1, b2)
  cross(a2-a1, b1-a1) * cross(a2-a1, b2-a1) < EPS and cross(b2-b1, a1-b1) * cross(b2-b1, a2-b1) < EPS
end

def cont(tet, pnt)
  area = cross(tet[1] - tet[0], tet[2] - tet[0]).abs + cross(tet[1] - tet[3], tet[2] - tet[3]).abs

  4.times{ |i| area -= cross(tet[i] - pnt, tet[(i+1)%4] - pnt).abs }
  
  area.abs < EPS
end

def col(a, b)
  4.times{ |i| return true if cont(a, b[i]) or cont(b, a[i]) }

  4.times{ |i| 4.times{ |j| return true if is_intersected_ls(a[i], a[(i+1)%4], b[j], b[(j+1)%4]) } }

  false
end

class UnionFind
  def initialize(size)
    @data = Array.new(size, -1)
  end
  def unite(a, b)
    a = root(a); b = root(b)
    if(a != b)
      a, b = b, a if @data[b] < @data[a]
      @data[a] += @data[b]; @data[b] = a
    end
    a != b
  end
  def same(a, b)
    root(a) == root(b)
  end
  def root(a)
    @data[a] < 0 ? a : @data[a] = root(@data[a])
  end
  def size(a)
    -@data[root(a)]
  end
  def count
    @data.count{ |val| val < 0 }
  end
end


while n = gets.chomp.to_i and n != 0
  n.times do
    m = gets.chomp.to_i
    tetras = Array.new(m){ Tetragon.new }

    m.times{ |i| gets.split(" ").map(&:to_i).each_with_index{ |j, k| tetras[i][k / 2][k % 2] = j } }

    uft = UnionFind.new(m)

    m.times{ |i| m.times{ |j| uft.unite(i, j) if col(tetras[i], tetras[j]) } }

    puts uft.count
  end
end

Compiler output:
a.cc:6:5: error: stray '@' in program
    6 |     @x = x
      |     ^
a.cc:7:5: error: stray '@' in program
    7 |     @y = y
      |     ^
a.cc:11:15: error: stray '@' in program
   11 |     Point.new(@x + right.x, @y + right.y)
      |               ^
a.cc:11:29: error: stray '@' in program
   11 |     Point.new(@x + right.x, @y + right.y)
      |                             ^
a.cc:14:15: error: stray '@' in program
   14 |     Point.new(@x - right.x, @y - right.y)
      |               ^
a.cc:14:29: error: stray '@' in program
   14 |     Point.new(@x - right.x, @y - right.y)
      |                             ^
a.cc:17:16: error: stray '@' in program
   17 |     idx == 0 ? @x : @y
      |                ^
a.cc:17:21: error: stray '@' in program
   17 |     idx == 0 ? @x : @y
      |                     ^
a.cc:20:5: error: stray '@' in program
   20 |     @x = val if idx == 0
      |     ^
a.cc:21:5: error: stray '@' in program
   21 |     @y = val if idx == 1
      |     ^
a.cc:28:5: error: stray '@' in program
   28 |     @points = Array.new(4){ Point.new }
      |     ^
a.cc:31:5: error: stray '@' in program
   31 |     @points[idx]
      |     ^
a.cc:65:5: error: stray '@' in program
   65 |     @data = Array.new(size, -1)
      |     ^
a.cc:70:22: error: stray '@' in program
   70 |       a, b = b, a if @data[b] < @data[a]
      |                      ^
a.cc:70:33: error: stray '@' in program
   70 |       a, b = b, a if @data[b] < @data[a]
      |                                 ^
a.cc:71:7: error: stray '@' in program
   71 |       @data[a] += @data[b]; @data[b] = a
      |       ^
a.cc:71:19: error: stray '@' in program
   71 |       @data[a] += @data[b]; @data[b] = a
      |                   ^
a.cc:71:29: error: stray '@' in program
   71 |       @data[a] += @data[b]; @data[b] = a
      |                             ^
a.cc:79:5: error: stray '@' in program
   79 |     @data[a] < 0 ? a : @data[a] = root(@data[a])
      |     ^
a.cc:79:24: error: stray '@' in program
   79 |     @data[a] < 0 ? a : @data[a] = root(@data[a])
      |                        ^
a.cc:79:40: error: stray '@' in program
   79 |     @data[a] < 0 ? a : @data[a] = root(@data[a])
      |                                        ^
a.cc:82:6: error: stray '@' in program
   82 |     -@data[root(a)]
      |      ^
a.cc:85:5: error: stray '@' in program
   85 |     @data.count{ |val| val < 0 }
      |     ^
a.cc:1:1: error: 'EPS' does not name a type
    1 | EPS = 1e-9
      | ^~~
a.cc:29:3: error: 'end' does not name a type
   29 |   end
      |   ^~~
a.cc:52:3: error: 'area' does not name a type
   52 |   area.abs < EPS
      |   ^~~~
a.cc:58:3: error: expected unqualified-id before numeric constant
   58 |   4.times{ |i| 4.times{ |j| return true if is_intersected_ls(a[i], a[(i+1)%4], b[j], b[(j+1)%4]) } }
      |   ^~~~~~~
a.cc:60:3: error: expected unqualified-id before 'false'
   60 |   false
      |   ^~~~~
a.cc:68:18: error: 'b' does not name a type
   68 |     a = root(a); b = root(b)
      |                  ^
a.cc:71:30: error: 'data' does not name a type
   71 |       @data[a] += @data[b]; @data[b] = a
      |                              ^~~~
a.cc:86:3: error: 'end' does not name a type
   86 |   end
      |   ^~~
a.cc:95:5: error: 'm' does not name a type
   95 |     m.times{ |i| gets.split(" ").map(&:to_i).each_with_index{ |j, k| tetras[i][k / 2][k % 2] = j } }
      |     ^
a.cc:97:5: error: 'uft' does not name a type
   97 |     uft = UnionFind.new(m)
      |     ^~~
a.cc:101:5: error: 'puts' does not name a type
  101 |     puts uft.count
      |     ^~~~