#include <iostream>
#include <iomanip>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <complex>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cmath>
#include <cassert>
#include <queue>
#include <set>
#include <map>
#include <valarray>
#include <bitset>
#include <stack>
using namespace std;

#define REP(i,n) for(int i=0;i<(int)n;++i)
#define FOR(i,c) for(__typeof((c).begin())i=(c).begin();i!=(c).end();++i)
#define ALL(c) (c).begin(), (c).end()
#define chmax(a,b) (a<(b)?(a=b,1):0)
#define chmin(a,b) (a>(b)?(a=b,1):0)
#define valid(y,x,h,w) (0<=y&&y<h&&0<=x&&x<w)
#define double long double
const int INF = 1<<29;
const double EPS = 1e-8;
const double PI = acos(-1);
typedef pair<int,int> pii;
typedef long long ll;

typedef complex<double> P;
P pIN() { double x,y; cin >> x >> y; return P(x,y); }
namespace std {
  bool operator < (const P& a, const P& b) {
    if (abs(a-b)<EPS) return 0;
    return real(a) != real(b) ? real(a) < real(b) : imag(a) < imag(b);
  }
}
double cross(const P& a, const P& b) {
  return imag(conj(a)*b);
}
double dot(const P& a, const P& b) {
  return real(conj(a)*b);
}
struct L : public vector<P> {
  L(const P &a, const P &b) {
    push_back(a); push_back(b);
  }
  L() { resize(2); }
  P v() {
    return (*this)[1]-(*this)[0];
  }
};
ostream &operator<<(ostream &os, const L &a) {
  os << a[0] << " -> " << a[1];
  return os;
}
typedef vector<P> G;
#define curr(g, i) g[i]
#define next(g, i) g[(i+1)%g.size()]

struct C {
  P p; double r;
  C(const P &p, double r) : p(p), r(r) { }
};
int ccw(P a, P b, P c) {
  b -= a; c -= a;
  if (cross(b, c) > EPS)   return +1;       // counter clockwise
  if (cross(b, c) < -EPS)   return -1;       // clockwise
  if (dot(b, c) < -EPS)     return +2;       // c--a--b on line
  if (EPS < norm(b) && norm(b) < norm(c)-EPS) return -2; // a--b--c on line
  return 0;
}

bool intersectLL(const L &l, const L &m) {
  return abs(cross(l[1]-l[0], m[1]-m[0])) > EPS || // non-parallel
         abs(cross(l[1]-l[0], m[0]-l[0])) < EPS;   // same line
}
bool intersectLS(const L &l, const L &s) {
  return cross(l[1]-l[0], s[0]-l[0])*       // s[0] is left of l
         cross(l[1]-l[0], s[1]-l[0]) < EPS; // s[1] is right of l
}
bool intersectLP(const L &l, const P &p) {
  return abs(cross(l[1]-p, l[0]-p)) < EPS;
}
bool intersectSS(const L &s, const L &t) {
  return ccw(s[0],s[1],t[0])*ccw(s[0],s[1],t[1]) <= 0 &&
         ccw(t[0],t[1],s[0])*ccw(t[0],t[1],s[1]) <= 0;
}
bool intersectSS2(const L &s, const L &t) { // 接しているやつは交差と考えない
  REP(i, 2) {
    if (ccw(s[0], s[1], t[i]) == 0) {
      int c = ccw(s[0],s[1],t[!i]);
      if (s[0] == t[i]) {
        if (c!=-2&&c) return 0;
      } else if (s[1] == t[i]) {
        if (c!=2&&c) return 0;
      } else if (abs(c)==1) return 0;
    }
  }
  return ccw(s[0],s[1],t[0])*ccw(s[0],s[1],t[1]) <= 0 &&
         ccw(t[0],t[1],s[0])*ccw(t[0],t[1],s[1]) <= 0;
}
bool intersectSP(const L &s, const P &p) {
  return abs(s[0]-p)+abs(s[1]-p)-abs(s[1]-s[0]) < EPS; // triangle inequality
}
P crosspoint(const L &l, const L &m) {
  double A = cross(l[1] - l[0], m[1] - m[0]);
  double B = cross(l[1] - l[0], l[1] - m[0]);
  if (abs(A) < EPS && abs(B) < EPS) return m[0];// same line
  if (abs(A) < EPS) assert(false); // !!!PRECONDITION NOT SATISFIED!!!
  return m[0] + B / A * (m[1] - m[0]);
}

typedef int Weight;
struct Edge {
  int src, dst;
  Weight weight;
  Edge(int src, int dst, Weight weight) :
    src(src), dst(dst), weight(weight) { }
};

bool operator < (const Edge &e, const Edge &f) {
  return e.weight != f.weight ? e.weight > f.weight : // !!INVERSE!!
    e.src != f.src ? e.src < f.src : e.dst < f.dst;
}

typedef vector<Edge> Edges;
typedef vector<Edges> Graph;

// O(n^3)
Graph segmentArrangement(const vector<L> &ss, vector<P> &ps) {
  int n = ss.size();
  REP(i,n) {
    ps.push_back( ss[i][0] );
    ps.push_back( ss[i][1] );
    for (int j = i+1; j < ss.size(); ++j)
      if (intersectSS(ss[i], ss[j]))
        ps.push_back( crosspoint(ss[i], ss[j]) );
  }
  sort(ALL(ps)); ps.erase(unique(ALL(ps)), ps.end());

  Graph g(ps.size());
  REP(i,ss.size()) {
    int pre = -1;
    REP(j,ps.size()) {
      if (intersectSP(ss[i], ps[j])) {
        if (pre != -1) {
          double d = abs(ps[pre]-ps[j]);
          g[pre].push_back(Edge(pre,j,d));
          g[j].push_back(Edge(j,pre,d));
        }
        pre = j;
      }
    }
  }
  return g;
}

// ベクトルａからみたベクトルｂの角度の[0,2pi)
double angle(const P &a, const P &b) {
  double ret = arg(b)-arg(a);
  return (ret>=0) ? ret : ret + 2*PI;
}
// ベクトルaとベクトルbの間の角度
double angle2(const P &a, const P &b) {
  return min(angle(a,b), angle(b,a));
}
double area2(const G& g) {
  double A = 0;
  for (int i = 0; i < g.size(); ++i) {
    A += cross(g[i], next(g, i));
  }
  return A;
}


map<pii,bool> visited;

G calculateHolesSub(const Graph &g, const vector<P> &ps, int a, int b) {
  G res;
  int pre = a;
  int cur = b;
  res.push_back(ps[a]);
  while(1) {
    visited[pii(pre,cur)] = 1;
    res.push_back(ps[cur]);
    P vec = ps[pre]-ps[cur];
    double minang = -1;
    int dst = -1;
    FOR(it, g[cur]) {
      // if (it->dst == pre) continue;
      double tang = angle(vec, ps[it->dst]-ps[cur]);
      if (chmax(minang, tang)) {
        dst = it->dst;
      }
    }
    if (dst == -1) return G();
    pre = cur;
    cur = dst;
    if (dst == a) break;
  }
  visited[pii(pre,cur)] = 1;
  if (area2(res) > EPS) return res;
  return G();
}

inline bool sameSeg(const L &s, const L &t) {
  return abs(s[0]-t[0])<EPS && abs(s[1]-t[1]) < EPS ||
    abs(s[0]-t[1])<EPS && abs(s[1]-t[0]) < EPS;
}
// 点-多角形包含関係
int contains(const G &g, const P &p) {
  bool in = 0;
  REP(i,g.size()) {
    P a = g[i] - p;
    P b = next(g,i) - p;
    if (imag(a) > imag(b)) swap(a,b);
    if (imag(a) <= 0 && 0 < imag(b))
      if (cross(a,b) < 0) in = !in;
    if (abs(cross(a,b))< EPS && dot(a,b) <= EPS) return 0;
  }
  return in?1:-1;
}

bool shouldBeMerged(const G &a, const G &b) {
  REP(i,a.size()) {
    REP(j,b.size()) {
      if (sameSeg(L(a[i],next(a,i)),
                  L(b[j],next(b,j)))) return 1;
    }
    if (contains(b,a[i]) == 1) return 1;
  }
  return 0;
}

vector<G> calculateHoles(const Graph &g, const vector<P> &ps) {
  int m = g.size();
  visited.clear();
  vector<G> ret;
  REP(i,m) {
    FOR(it, g[i]) {
      if (visited.count(pii(it->src,it->dst))) continue;
      G t = calculateHolesSub(g, ps, it->src, it->dst);
      if (t.size())
        ret.push_back(t);
    }
  }
  return ret;
}

struct UnionFind {
  vector<int> data;
  UnionFind(int size) : data(size, -1) { }
  bool unionSet(int x, int y) {
    x = root(x); y = root(y);
    if (x != y) {
      if (data[y] < data[x]) swap(x, y);
      data[x] += data[y]; data[y] = x;
    }
    return x != y;
  }
  bool findSet(int x, int y) {
    return root(x) == root(y);
  }
  int root(int x) {
    return data[x] < 0 ? x : data[x] = root(data[x]);
  }
  int size(int x) {
    return -data[root(x)];
  }
};

int main() {
  int n;
  while(cin>>n) {
    vector<L> ss;
    REP(i,n) {
      P a = pIN();
      P b = pIN();
      ss.push_back(L(a,b));
    }
    vector<P> ps;
    Graph g = segmentArrangement(ss,ps);
    vector<G> gs = calculateHoles(g,ps);

    int m = gs.size();
    UnionFind uf(m);
    int ans = m;
    REP(i,m) {
      REP(j,i) {
        if (shouldBeMerged(gs[i], gs[j])) {
          if (uf.unionSet(i,j)) {
            ans--;
          }
        }
      }
    }
    cout << ans << endl;
    // FOR(it, gs) {
    //   FOR(jt, *it) {
    //     cout << *jt << " ";
    //   }
    //   cout << endl;
    // }
  }
}

Measured memory_bytes: 1512