#include <iostream>
#include <vector>
#include <algorithm>
#include <complex>
using namespace std;

// xy平面上の点(ベクトル)を表現するには、complex型を利用するとよい
typedef complex<double> P;

// 辺の表現 (座標を2つ pair でもつ)
typedef pair<P, P> L;

// 円の表現 (座標 P と 半径 d で表現する)
typedef pair<P, double> C;

// 成分を取り出すのを簡単にする
#define X real()
#define Y imag()

// 誤差(epsilon)の定義
#define EPS (1e-10)

// 2つの要素が等しいかどうか
#define EQ(a,b) (abs((a) - (b)) < EPS)

// 2つのベクトルが等しいかどうか
#define EQV(a,b) ( EQ((a).X, (b).X) && EQ((a).Y, (b).Y) )

// m は n より大きい(以上)かどうか
#define LE(n, m) ((n) < (m) + EPS)
#define LEQ(n, m) ((n) <= (m) + EPS)

// m は n より小さい(以下)かどうか
#define GE(n, m) ((n) + EPS > (m))
#define GEQ(n, m) ((n) + EPS >= (m))

// 2つのベクトルの内積を求める
double dot(P a, P b) {
    return (a.X * b.X + a.Y * b.Y);
}

// 2つのベクトルの外積を求める
double cross(P a, P b) {
    return (a.X * b.Y - a.Y * b.X);
}

// Union-Find 木 (Verified: AtCoder Typical Contest 001 B)
struct UnionFind {
private:
    const int n;
    int __size;
    vector<int> uf;
public:
    // 初期化 UnionFind uni(n) のように宣言すれば良い
    UnionFind(int _n) : n(_n), __size(_n), uf(_n, -1) {}
    // find (木の根を求める)
    int find(int x) {return (uf[x] < 0) ? x : uf[x] = find(uf[x]);}
    // x と y が同じ集合に属するかどうか
    bool same(int x, int y) {return find(x) == find(y);}
    // x が属する集合の要素数
    int size(int x) {return -uf[find(x)];}
    // 集合はいくつあるか
    int size()      {return __size;}
    // x と y の属する集合を併合
    void unite(int x, int y) {
        x = find(x); y = find(y);
        if(x == y) return;
        __size--;
        if(uf[y] < uf[x]) swap(x, y);
        uf[x] += uf[y]; uf[y] = x;
    }
};

// 凸多角形の内部判定 O(log n)
// 点が領域内部なら1、境界上なら2、外部なら0を返す
int inCcwConvex(P p, const vector<P> &ps) {
    int n = ps.size();
    P g = (ps[0] + ps[n / 3] + ps[n*2 / 3]) / 3.0;
    if(g == p) return 1;
    P gp = p - g;

    int l = 0, r = n;
    while(l + 1 < r) {
        int mid = (l + r) / 2;
        P gl = ps[l] - g;
        P gm = ps[mid] - g;
        if(cross(gl,gm) > 0) {
            if(cross(gl,gp) >= 0 && cross(gm,gp) <= 0) r = mid;
            else l = mid;
        }
        else {
            if(cross(gl,gp) <= 0 && cross(gm,gp) >= 0) l = mid;
            else r = mid;
        }
    }
    r %= n;
    double cr = cross(ps[l] - p, ps[r] - p);
    return EQ(cr, 0) ? 2 : cr < 0 ? 0 : 1;
}

// ccw (c が直線(線分) ab に対してどのような位置関係か？)
// Verified: AOJ CGL_1_C: Counter-Clockwise
// +1 ... a → b で半時計方向に折れて b → c (COUNTER_CLOCKWISE)
// -1 ... a → b で時計方向に折れて b → c (CLOCKWISE)
// +2 ... c, a, b がこの順で同一直線状にある場合 (ONLINE_BACK)
// -2 ... a, b, c がこの順で同一直線状にある場合 ( or a == b ) (ONLINE_FRONT)
//  0 ... c が線分 ab 上にある場合 (点 a, b 上を含む) (ON_SEGMENT)
int ccw(P a, P b, P c) {
    b -= a; c -= a;
    if( cross(b,c) > EPS ) return +1;
    if( cross(b,c) < -EPS ) return -1;
    if( dot(b,c) < 0 ) return +2;
    if( norm(b) < norm(c) ) return -2;
    return 0;
}

// 線分 a1, a2 と線分 b1, b2 の交差判定
// Verified: CGL_2_B: Intersection
bool isec_ss(P a1, P a2, P b1, P b2) {
    return ( ccw(a1,a2,b1) * ccw(a1,a2,b2) <= 0 ) &&
           ( ccw(b1,b2,a1) * ccw(b1,b2,a2) <= 0 );
}

bool intersect(vector<P> A, vector<P> B) {
    bool res = false;
    for(size_t k=0; k<A.size(); k++) {
        if(inCcwConvex(A[k], B) > 0) {
            res = true;
        }
    }

    for(size_t i=0; i<A.size(); i++) {
        for(size_t j=0; j<B.size(); j++) {
            L la(A[i], A[(i+1)%A.size()]), lb(B[j], B[(j+1)%B.size()]);
            if(isec_ss(la.first, la.second, lb.first, lb.second)) res = true;
        }
    }
    return res;
}

int solve_testcase() {
    int N; cin >> N;
    if(N == 0) return 1;

    for(int i=0; i<N; i++) {
        int M; cin >> M;
        UnionFind uf(M);
        vector< vector<P> > polys;
        for(int j=0; j<M; j++) {
            vector<P> p;
            for(int k=0; k<4; k++) {
                double x, y; cin >> x >> y;
                p.emplace_back(x, y);
            }
            reverse(p.begin(), p.end());
            polys.emplace_back(p);
        }
        for(int x=0; x<M; x++) {
            for(int y=0; y<x; y++) {
                if(intersect(polys[x], polys[y]) or intersect(polys[y], polys[x])) {
                    uf.unite(x, y);
                }
            }
        }
        cout << uf.size() << endl;
    }
    return 0;
}

int main() {
    while(!solve_testcase());
    return 0;
}



Measured memory_bytes: 3204