#include<bits/stdc++.h>

#define REP(i,s,n) for(int i=s;i<n;i++)
#define rep(i,n) REP(i,0,n)
#define EPS (1e-8)
#define equals(a,b) (fabs((a)-(b)) < EPS)
#define COUNTER_CLOCKWISE 1
#define CLOCKWISE -1 
#define ONLINE_BACK 2
#define ONLINE_FRONT -2
#define ON_SEGMENT 0

using namespace std;

class Point{
public:
  double x,y;

  Point(double x = 0,double y = 0): x(x),y(y){}

  Point operator + (Point p){return Point(x+p.x,y+p.y);}
  Point operator - (Point p){return Point(x-p.x,y-p.y);}
  Point operator * (double a){return Point(a*x,a*y);}
  Point operator / (double a){return Point(x/a,y/a);}
  Point operator * (Point a){ return Point(x * a.x - y * a.y, x * a.y + y * a.x); }

  bool operator < (const Point& p) const{ return !equals(x,p.x)?x<p.x:(!equals(y,p.y)&&y<p.y); }

  bool operator == (const Point& p)const{ return fabs(x-p.x) < EPS && fabs(y-p.y) < EPS; }

};

struct Segment{
  Point p1,p2;
  Segment(Point p1 = Point(),Point p2 = Point()):p1(p1),p2(p2){}
  bool operator <  (const Segment& s) const { return ( p2 == s.p2 ) ? p1 < s.p1 : p2 < s.p2; }
  bool operator == (const Segment& s) const { return ( s.p1 == p1 && s.p2 == p2 ) || ( s.p1 == p2 && s.p2 == p1 ); }

};

typedef Point Vector;
typedef Segment Line;
typedef vector<Point> Polygon;

ostream& operator << (ostream& os,const Point& a){ os << "(" << a.x << "," << a.y << ")"; }

ostream& operator << (ostream& os,const Segment& a){ os << "( " << a.p1 << " , " << a.p2 << " )"; }

double dot(Point a,Point b){ return a.x*b.x + a.y*b.y; }

double cross(Point a,Point b){ return a.x*b.y - a.y*b.x; }

double norm(Point a){ return a.x*a.x+a.y*a.y; }

double abs(Point a){ return sqrt(norm(a)); }

//rad は角度をラジアンで持たせること
Point rotate(Point a,double rad){ return Point(cos(rad)*a.x - sin(rad)*a.y,sin(rad)*a.x + cos(rad)*a.y); }

// 度をラジアンに変換
double toRad(double agl){ return agl*M_PI/180.0; }

// a => prev, b => cur, c=> next
// prev から cur へ行って next へ行く際の角度を求める
double getArg(Point a,Point b,Point c){
  double arg1 = atan2(b.y-a.y,b.x-a.x);
  double arg2 = atan2(c.y-b.y,c.x-b.x);
  double arg = fabs( arg1 - arg2 );
  while( arg > M_PI ) arg -= 2.0 * M_PI;
  return fabs(arg);
}

int ccw(Point p0,Point p1,Point p2){
  Point a = p1-p0;
  Point b = p2-p0;
  if(cross(a,b) > EPS)return COUNTER_CLOCKWISE;
  if(cross(a,b) < -EPS)return CLOCKWISE;
  if(dot(a,b) < -EPS)return ONLINE_BACK;
  if(norm(a) < norm(b))return ONLINE_FRONT;
  return ON_SEGMENT;
}

bool intersectSS(Line s, Line t) {
  return ccw(s.p1,s.p2,t.p1)*ccw(s.p1,s.p2,t.p2) <= 0 &&
         ccw(t.p1,t.p2,s.p1)*ccw(t.p1,t.p2,s.p2) <= 0;
}

//cross product of pq and pr
double cross3p(Point p,Point q,Point r) { return (r.x-q.x) * (p.y -q.y) - (r.y - q.y) * (p.x - q.x); }
  
//returns true if point r is on the same line as the line pq
bool collinear(Point p,Point q,Point r) { return fabs(cross3p(p,q,r)) < EPS; }
  
//returns true if point t is on the left side of line pq
bool ccwtest(Point p,Point q,Point r){
  return cross3p(p,q,r) > 0; //can be modified to accept collinear points
}
 
bool onSegment(Point p,Point q,Point r){
  return collinear(p,q,r) && equals(sqrt(pow(p.x-r.x,2)+pow(p.y-r.y,2)) + sqrt(pow(r.x-q.x,2) + pow(r.y-q.y,2) ),sqrt(pow(p.x-q.x,2)+pow(p.y-q.y,2)) ) ;
}

int par[1100];

int find(int x){
  if( x == par[x] ) return x;
  return par[x] = find(par[x]);
}

void unit(int x,int y){
  x = find(x), y = find(y);
  if( x != y ) par[x] = y;
}

vector<vector<int> > connect_counter;
int connect[10][6] = {
  {+2,+2,+2,+2,-1,-1},
  {+0,-1,-1,-1,-1,-1},
  {+1,+1,+2,+2,+2,-1},
  {+1,+1,+1,+3,-1,-1},
  {+1,+1,+2,-1,-1,-1},
  {+1,+1,+2,+2,+2,-1},
  {+1,+2,+2,+2,+3,-1},
  {+1,+1,+2,+3,-1,-1},
  {+2,+2,+2,+3,+3,-1},
  {+2,+2,+2,+2,-1,-1}
};

int WhatIsThis(vector<Segment> &segs){
  if( segs.size() == 1 ) return 1;
  vector<int> tmp;
  Segment cur,next;
  rep(i,segs.size()) {
    int cnt = 0;
    Segment touch;
    rep(j,segs.size()) if( i != j ) if( intersectSS(segs[i],segs[j]) ) {
        ++cnt;
        touch = segs[j];
      }
    if( cnt == 1 ) { cur = segs[i], next = touch; }
    assert( cnt );
    tmp.push_back(cnt);
  }
  sort(tmp.begin(),tmp.end());
  if( tmp == connect_counter[3] ) return 3;
  if( tmp == connect_counter[6] ) return 6;
  if( tmp == connect_counter[8] ) return 8;
  if( tmp == connect_counter[2] ) {
    Point p0,p1,p2;
    if( cur.p1 == next.p1 ) p0 = cur.p2, p1 = cur.p1, p2 = next.p2;
    if( cur.p1 == next.p2 ) p0 = cur.p2, p1 = cur.p1, p2 = next.p1;
    if( cur.p2 == next.p1 ) p0 = cur.p1, p1 = cur.p2, p2 = next.p2;
    if( cur.p2 == next.p2 ) p0 = cur.p1, p1 = cur.p2, p2 = next.p1;
    if( ccw(p0,p1,p2) == CLOCKWISE ) return 2;
    else                             return 5;
  }
  bool hasMiddle = false;
  rep(i,segs.size()) rep(j,segs.size()) if( i != j && intersectSS(segs[i],segs[j]) ) {
    if( segs[i].p1 == segs[j].p1 || segs[i].p1 == segs[j].p2 || segs[i].p2 == segs[j].p1 || segs[i].p2 == segs[j].p2 ) continue;
    hasMiddle = true;
    goto Skip;
  }
 Skip:;
  if( tmp == connect_counter[4] ) return (hasMiddle?4:7);
  if( tmp == connect_counter[0] ) return (hasMiddle?9:0);
  return -1;
}

void compute(int n,vector<Segment> &segs){
  vector<int> counter(10,0);
  
  rep(i,n) par[i] = i;
  rep(i,n) REP(j,i+1,n) if( intersectSS(segs[i],segs[j]) ) unit(i,j);
  int dex = 0;
  map<int,int> S;
  rep(i,n) if( !S.count(find(i)) ) S[find(i)] = dex++;
  int V = S.size();
  vector<vector<Segment> > buf(V,vector<Segment>());
  rep(i,n) buf[S[find(i)]].push_back(segs[i]);
  
  rep(i,V) {
    int result = WhatIsThis(buf[i]);
    ++counter[result];
  }

  rep(i,10) {
    if( i ) printf(" ");
    printf("%d",counter[i]);
  } puts("");
}

int main(){
  connect_counter.clear();
  connect_counter.resize(10,vector<int>());
  rep(i,10) rep(j,6) if( connect[i][j] != -1 ) connect_counter[i].push_back(connect[i][j]);


  int n;
  while( cin >> n, n ){
    vector<Segment> segs(n);
    rep(i,n) cin >> segs[i].p1.x >> segs[i].p1.y >> segs[i].p2.x >> segs[i].p2.y;
    compute(n,segs);
  }
  return 0;
}

Measured memory_bytes: 1364