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

/**
 * Try considering all possible orders to traverse the lines in to form
 * a polygon. Use floating point arithmetic with an appropriately chosen 
 * epsilon value to detect when computed intersection points correspond
 * to the same point.
 *
 * @author Finn Lidbetter
 */

public class findingpolly_finn_double {
  static double EPS = 1e-12;
  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    int n = Integer.parseInt(br.readLine());
    Line[] lines = new Line[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 Line(x1,y1,x2,y2);
    }
    Point2D[][] intersections = new Point2D.Double[n][n];
    for (int i=0; i<n; i++) {
      for (int j=0; j<n; j++) {
        if (i==j)
          continue;
        intersections[i][j] = Line.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)) {
        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, Point2D[][] intersections) {
    // Return true iff the given order of lines defines a simple polygon (no self intersections).
    int n = order.length;
    Point2D[] pts = new Point2D.Double[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 (equalPts(pts[i], pts[j]))
          return false;
      }
    }
    Line2D[] segments = new Line2D.Double[n];
    for (int i=0; i<n; i++) {
      segments[i] = new Line2D.Double(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];
        Point2D refPt = intersections[iLineIndex][jLineIndex];
        if (refPt!=null && intersects(segments[i], segments[j], refPt)) {
          return false;
        }
      }
    }
    return true;
  }
  public static boolean equalPts(Point2D p1, Point2D p2) {
    if (Math.abs(p1.getX() - p2.getX())<EPS && Math.abs(p1.getY() - p2.getY())<EPS) {
      return true;
    }
    return false;
  }

  public static boolean intersects(Line2D l1, Line2D l2, Point2D ref) {
    // ref is the intersection point of the infinite lines through l1, l2.
    return contains(ref, l1) && contains(ref, l2);
  }

  public static boolean contains(Point2D pt, Line2D line) {
    // It is assumed that pt is on the infinite line through line's endpoints.
    double xSign = line.getP1().getX() - line.getP2().getX();
    double delta1 = 0;
    double delta2 = 0;
    if (Math.abs(xSign)<EPS) {
      delta1 = line.getP1().getY() - pt.getY();
      delta2 = line.getP2().getY() - pt.getY();
    } else {
      delta1 = line.getP1().getX() - pt.getX();
      delta2 = line.getP2().getX() - pt.getX();
    }
    if (Math.abs(delta1)<EPS) {
      delta1 = 0;
    }
    if (Math.abs(delta2)<EPS) {
      delta2 = 0;
    }
    return Math.signum(delta1) != Math.signum(delta2);
  }
}
class Line { // ax+by=c
  static double EPS = 0.00000000001;
  double a,b,c;
  Line(long x1, long y1, long x2, long y2) { 
    a = y1-y2;
    b = x2-x1;
    c = x2*y1 - x1*y2;
    normalise(); 
  }
  void normalise() {
    if (Math.abs(b)<EPS) {
      c = c/a;
      a = 1;
      b = 0;
    } else {
      a = a/b;
      c = c/b;
      b = 1;
    } 
  }
  static Point2D intersection(Line l1, Line l2) { // Returns null if l1 and l2 are parallel
    if (Math.abs(l1.a - l2.a)<EPS && Math.abs(l1.b - l2.b)<EPS)
      return null;
    double x = Double.NaN;
    double y = Double.NaN;
    if (Math.abs(l1.b)<EPS) {
      x = l1.c / l1.a;
      y = (l2.c - l2.a*x) / l2.b;
    }
    else if (Math.abs(l2.b)<EPS) {
      x = l2.c / l2.a;
      y = (l1.c - l1.a * x) / l1.b;
    }
    else if (Math.abs(l1.a)<EPS) { 
      y = l1.c / l1.b;
      x = (l2.c - l2.b*y) / l2.a;
    }
    else if (Math.abs(l2.a)<EPS) { 
      y = l2.c / l2.b;
      x = (l1.c - l1.b*y) / l1.a;
    }
    else {
      x = (l1.c - l2.c) / (l1.a - l2.a);
      y = (l1.c - l1.a*x) / l1.b;
    }
    return new Point2D.Double(x, y);
  } 
}
