import java.io.*;
import java.util.*;
import java.awt.geom.*;

/**
 * Try considering all possible orders to traverse the lines in to form
 * a polygon. Find the precise intersection points of all pairs of lines 
 * and then use these points to form the polygons and determine if the
 * line segments intersect.
 *
 * @author Finn Lidbetter
 */

public class findingpolly_finn_precise {
  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    int n = Integer.parseInt(br.readLine());
    FracLine[] lines = new FracLine[n];
    for (int i=0; i<n; i++) {
      String[] s = br.readLine().split(" ");
      int x1 = Integer.parseInt(s[0]);
      int y1 = Integer.parseInt(s[1]);
      int x2 = Integer.parseInt(s[2]);
      int y2 = Integer.parseInt(s[3]);
      lines[i] = new FracLine(x1,y1,x2,y2);
    }
    FracPt[][] intersections = new FracPt[n][n];
    for (int i=0; i<n; i++) {
      for (int j=0; j<n; j++) {
        if (i==j)
          continue;
        intersections[i][j] = FracLine.intersection(lines[i], lines[j]);
      }
    }
    // Try all permutations of the lines.
    int[] order = new int[n-1];
    int count = 0;
    for (int i=0; i<n-1; i++) {
      order[i] = i;
    }
    while (order!=null) {
      int[] wholeOrder = new int[n];
      for (int i=0; i<n-1; i++) {
        wholeOrder[i] = order[i];
      }
      // Fix the last line at the end to avoid double counting rotations.
      wholeOrder[n-1] = n-1;
      if (isSimple(wholeOrder, intersections, lines)) {
        count++;
      }
      order = nextPermutation(order);
    } 
    // We double counted reversals.
    System.out.println(count/2);
  }

  static int[] nextPermutation(int[] c) {
    int first = getFirst(c); 
    if (first == -1) 
      return null;
    int toSwap = c.length - 1;
    while (c[first] >= c[toSwap]) 
      --toSwap;
    swap(c, first++, toSwap); 
    toSwap = c.length - 1;
    while (first < toSwap) 
      swap(c, first++, toSwap--);
    return c; 
  }

  static int getFirst(int[] c) {
    for (int i = c.length - 2; i >= 0; --i) 
      if (c[i] < c[i + 1]) 
        return i;
    return -1; 
  }

  static void swap(int[] c, int i, int j) { 
    int tmp = c[i]; 
    c[i] = c[j]; 
    c[j] = tmp; 
  }

  static boolean isSimple(int[] order, FracPt[][] intersections, FracLine[] lines) {
    // Return true iff the given order of lines defines a simple polygon (no self intersections).
    int n = order.length;
    FracPt[] pts = new FracPt[n];
    
    for (int i=0; i<n; i++) {
      int j = order[i];
      int k = order[(i+1)%n];
      if (intersections[j][k]==null)
        return false;
      pts[i] = intersections[j][k];
    }
    for (int i=0; i<n; i++) {
      for (int j=i+1; j<n; j++) {
        if (pts[i].equals(pts[j]))
          return false;
      }
    }
    FracSegment[] segments = new FracSegment[n];
    for (int i=0; i<n; i++) {
      segments[i] = new FracSegment(pts[i], pts[(i+1)%n]);
    }

    for (int i=0; i<n; i++) {
      for (int j=i+2; j<n; j++) {
        if ((j+1)%n==i)
          continue;
        int iLineIndex = order[(i+1)%n];
        int jLineIndex = order[(j+1)%n];
        FracPt refPt = intersections[iLineIndex][jLineIndex];
        if (refPt!=null && intersects(segments[i], segments[j], refPt)) {
          return false;
        }
      }
    }
    return true;
  }
  public static boolean intersects(FracSegment l1, FracSegment l2, FracPt ref) {
    // ref is the intersection point of the infinite lines through l1, l2.
    return l1.contains(ref) && l2.contains(ref);
  }

}
class FracSegment {
  FracPt p1;
  FracPt p2;
  public FracSegment(FracPt pp1, FracPt pp2) {
    p1 = pp1;
    p2 = pp2;
  }
  public boolean contains(FracPt p) {
    // It is assumed that p is on the infinite line through p1,p2.
    int xSign = p1.x.compareTo(p2.x);
    if (xSign==0) {
      return p1.y.compareTo(p.y) != p2.y.compareTo(p.y);
    }
    return p1.x.compareTo(p.x) != p2.x.compareTo(p.x);
  }
}
class Frac implements Comparable<Frac> {
  static Frac ZERO = new Frac(0, 1);
  static Frac ONE = new Frac(1, 1);
  long n,d;
  public Frac(long nn, long dd) {
    if (nn==0) {
      n = 0;
      d = 1;
    } else {
      long gcf = gcd(nn, dd);
      n = nn/gcf;
      d = dd/gcf;
      if (d<0) {
        n *= -1;
        d *= -1;
      }
    }
  }
  public Frac add(Frac f2) {
    return new Frac(n*f2.d+d*f2.n, d*f2.d);
  }
  public Frac sub(Frac f2) {
    return new Frac(n*f2.d-d*f2.n, d*f2.d);
  }
  public Frac mult(Frac f2) {
    return new Frac(n*f2.n, d*f2.d);
  }
  public Frac divide(Frac f2) {
    return new Frac(n*f2.d, d*f2.n);
  }
  static long gcd(long a, long b) {
    return b == 0 ? (a<0?-a:a) : gcd(b, a % b); 
  }
  public boolean equals(Object o) {
    Frac f2 = (Frac) o;
    return n==f2.n && d==f2.d;
  }
  public int compareTo(Frac f2) {
    return Long.compare(n*f2.d, d*f2.n);
  }
  public String toString() {
    return String.format("%d/%d", n,d);
  }
}
class FracPt {
  Frac x,y;
  public FracPt(Frac xx, Frac yy) {
    x = xx;
    y = yy;
  }
  public boolean equals(Object o) {
    FracPt f2 = (FracPt)o;
    return x.equals(f2.x) && y.equals(f2.y);
  }
  public String toString() {
    return String.format("(%s,%s)", x.toString(), y.toString());
  }
}
class FracLine { // ax+by=c
  Frac a,b,c;
  FracLine(long x1, long y1, long x2, long y2) { 
    a = new Frac(y1 - y2, 1); 
    b = new Frac(x2 - x1, 1); 
    c = new Frac(x2*y1 - x1*y2, 1); 
    normalise(); 
  }
  void normalise() { 
    if (b.n==0) {
      c = c.divide(a);
      a = Frac.ONE;
      b = Frac.ZERO;
    } else { 
      a = a.divide(b);
      c = c.divide(b);
      b = Frac.ONE; 
    } 
  }
  static FracPt intersection(FracLine l1, FracLine l2) { // Returns null if l1 and l2 are parallel
    if (l1.a.sub(l2.a).n==0 && l1.b.sub(l2.b).n==0)
      return null;
    Frac x = null;
    Frac y = null;
    if (l1.b.n==0) {
      x = l1.c.divide(l1.a);
      y = (l2.c.sub(l2.a.mult(x))).divide(l2.b);
    }
    else if (l2.b.n==0) {
      x = l2.c.divide(l2.a);
      y = (l1.c.sub(l1.a.mult(x))).divide(l1.b);
    }
    else if (l1.a.n==0) { 
      y = l1.c.divide(l1.b);
      x = (l2.c.sub(l2.b.mult(y))).divide(l2.a);
    }
    else if (l2.a.n==0) { 
      y = l2.c.divide(l2.b);
      x = (l1.c.sub(l1.b.mult(y))).divide(l1.a);
    }
    else { 
      x = (l1.c.sub(l2.c)).divide(l1.a.sub(l2.a));
      y = (l1.c.sub(l1.a.mult(x))).divide(l1.b);
    }
    return new FracPt(x, y);
  } 
}
