#ifndef ___Geometry_Library
#define ___Geometry_Library

// ------ Includes ------ //
#include <cmath>
#include <vector>
#include <complex>
#include <utility>
#include <algorithm>

// ------ Defines ------ //
typedef long double GType;
typedef std::complex<GType> Point;
typedef std::pair<Point, Point> Segment;

// ------ Constants ------ //
const GType EPS = 1.0e-10L;

// ------ Functions Level 1 ------ //
GType dot(const Point& p1, const Point& p2) {
	return (std::conj(p1) * p2).real();
}
GType cross(const Point& p1, const Point& p2) {
	return (std::conj(p1) * p2).imag();
}
int ccw(const Point& p1, const Point& p2, const Point& p3) {
	Point v1 = p2 - p1, v2 = p3 - p1;
	if (cross(v1, v2) > EPS) return +1;
	if (cross(v1, v2) < -EPS) return -1;
	if (dot(v1, v2) < -EPS) return +2;
	if (std::norm(v1) < std::norm(v2)) return -2;
	return 0;
}

// ------ Functions Level 2 ------ //
bool intersect(const Segment& s1, const Point& p1, bool segflag) {
	if (!segflag) return std::abs(cross(s1.second - p1, s1.first - p1)) < EPS;
	return (std::abs(s1.first - p1) + std::abs(s1.second - p1) - std::abs(s1.second - s1.first)) < EPS;
}
bool intersect(const Segment& s1, const Segment& s2, bool segflag) {
	if (!segflag) return cross(s1.second - s1.first, s2.first - s1.first) * cross(s1.second - s1.first, s2.second - s1.first) < EPS;
	return (ccw(s1.first, s1.second, s2.first) * ccw(s1.first, s1.second, s2.second) <= 0 && ccw(s2.first, s2.second, s1.first) * ccw(s2.first, s2.second, s1.second) <= 0);
}

// ------ Functions Level 3 ------ //
int contain(std::vector<Point> v, Point p) {
	bool in = false;
	for (int i = 0; i < v.size(); i++) {
		Point a = v[i] - p, b = v[(i + 1 != v.size() ? i + 1 : 0)] - p;
		if (a.imag() > b.imag()) swap(a, b);
		if (a.imag() <= EPS && EPS < b.imag() && cross(a, b) < -EPS) in = !in;
		if (cross(a, b) == 0 && dot(a, b) <= 0) return 1;
	}
	return in ? 2 : 0;
}

#endif

#include <vector>
#include <numeric>
#include <algorithm>

class unionfind {
private:
	int size_;
	std::vector<int> parent;
	std::vector<int> rank;
public:
	unionfind() : size_(0), parent(std::vector<int>()), rank(std::vector<int>()) {};
	unionfind(int size__) : size_(size__), parent(std::vector<int>(size_)), rank(std::vector<int>(size_, 0)) {
		std::iota(parent.begin(), parent.end(), 0);
	}
	int size() { return size_; }
	int root(int x) {
		if (parent[x] == x) return x;
		return parent[x] = root(parent[x]);
	}
	bool same(int x, int y) {
		return root(x) == root(y);
	}
	void unite(int x, int y) {
		x = root(x); y = root(y);
		if (x == y) return;
		if (rank[x] < rank[y]) parent[x] = y;
		else if (rank[x] > rank[y]) parent[y] = x;
		else parent[y] = x, rank[x]++;
	}
};

#include <set>
#include <iostream>
using namespace std;
int Q, N, x, y; vector<Point> p[109];
int main() {
	while (cin >> Q, Q) {
		while (Q--) {
			cin >> N;
			for (int i = 0; i < N; i++) {
				p[i] = vector<Point>(4);
				for (int j = 0; j < 4; j++) {
					cin >> x >> y;
					p[i][j] = Point(x, y);
				}
			}
			unionfind uf(N);
			for (int i = 0; i < N; i++) {
				for (int j = 0; j < N; j++) {
					bool flag = false;
					for (int k = 0; k < 4; k++) {
						if (contain(p[i], p[j][k])) flag = true;
					}
					for (int k = 0; k < 4; k++) {
						for (int l = 0; l < 4; l++) {
							if (intersect(Segment(p[i][k], p[i][(k + 1) % 4]), Segment(p[j][l], p[j][(l + 1) % 4]), true)) flag = true;
						}
					}
					if (flag) uf.unite(i, j);
				}
			}
			set<int> ret;
			for (int i = 0; i < N; i++) ret.insert(uf.root(i));
			cout << ret.size() << endl;
		}
	}
	return 0;
}

Measured memory_bytes: 3044