public static int h = 20; // x distance between pipes
public static int g = 20; // y distance between top and bottom pipes
public static int u = 10; // action flap
public static int d = 1; // action not flap
public static int t = 100; // window height

public static int[][] generate_data(int[] actions, int[] centers) {
	int[] p = new int[] { 0, t / 2 };
	int[][] x = new int[actions.length + 1][2];
	int cur = 1;
	for (int i = 0; i < actions.length; i++) {
		p[0]++;
		p[1] += actions[i] * u - (1 - actions[i]) * d;
		x[i + 1][0] = h - p[0] % h;
		x[i + 1][1] = centers[cur] - p[1];
		if (p[0] % h == 0)
			cur++;
	}
	return x;
}

public static int activate_nn(double[][] w1, double[] w2, int[] x) {
	int m = w1[0].length;
	double z = 0;
	for (int i = 0; i < m; i++)
		z += w2[i] * 1.0 / (1.0 + Math.exp(-x[0] * w1[0][i] - x[1] * w1[1][i] - w1[2][i]));
	z += w2[m];
	return (1.0 / (1.0 + Math.exp(-z)) >= 0.5 ? 1 : 0);
}

public static int[] activate_nn_data(double[][] w1, double[] w2, int[] centers) {
	int[] p = new int[] { 0, t / 2 };
	int n = centers.length * h;
	int[] a = new int[n];
	int[] x = new int[2];
	int ai = 0;
	int cur = 0;
	for (int i = 0; i < n; i++) {
		ai = activate_nn(w1, w2, x);
		p[0]++;
		p[1] += ai * u - (1 - ai) * d;
		x[0] = h - p[0] % h;
		x[1] = centers[cur] - p[1];
		if (p[0] % h == 0)
			cur++;
	}
	return a;
}

public static int fitness(int[] actions, int[] centers) {
	int[] p = new int[] { 0, t / 2 };
	int[][] x = new int[actions.length + 1][2];
	int cur = 1;
	for (int i = 0; i < actions.length; i++) {
		p[0]++;
		p[1] += actions[i] * u - (1 - actions[i]) * d;
		if (p[0] % h == 0) {
			if (p[1] > centers[cur] + g / 2 || p[1] < centers[cur] - g / 2)
				break;
			cur++;
		}
	}
	return p[0] - Math.abs(p[1] - centers[cur]);
}

// This assumes the centers are fixed, it will overfit the data and is not
// recommended
public static void genetic_training(double[][][] w1, double[][] w2, int[] centers, int iteration, int cross_point,
		double mutation) {
	int n = w1.length;
	int m = w1[0].length;
	int[] fits = new int[n];
	int[] actions = new int[centers.length * h];
	for (int t = 0; t < iteration; t++) {
		double total = 0;
		for (int i = 0; i < n; i++) {
			actions = activate_nn_data(w1[i], w2[i], centers);
			fits[i] = fitness(actions, centers);
			total += fits[i];
		}
		double[] prob = new double[n];
		for (int i = 0; i < n; i++)
			prob[i] = fits[i] / total;
		double[][][] w1p = new double[n][m][3];
		double[][] w2p = new double[n][m + 1];
		int i1 = 0;
		int i2 = 0;
		for (int i = 0; i < n / 2; i++) {
			i1 = random(prob);
			i2 = random(prob);
			// write your own cross over code
			for (int j = 0; j < m; j++) {
				for (int k = 0; k < 3; k++) {
					w1p[2 * i][k][j] = (j < cross_point ? w1[i1][k][j] : w1[i2][k][j]);
					w1p[2 * i + 1][k][j] = (j >= cross_point ? w1[i1][k][j] : w1[i2][k][j]);
				}
				w2p[2 * i][j] = (j < cross_point ? w2[i1][j] : w2[i2][j]);
				w2p[2 * i + 1][j] = (j >= cross_point ? w2[i1][j] : w2[i2][j]);
			}
			w2p[2 * i][m] = w2[i1][m];
			w2p[2 * i + 1][m] = w2[i2][m];
		}
		for (int i = 0; i < n; i++) {
			// write your mutation code
			for (int j = 0; j < m; j++) {
				if (Math.random() < mutation)
					w1p[i][0][j] *= 0.5 + Math.random();
				if (Math.random() < mutation)
					w1p[i][1][j] *= 0.5 + Math.random();
				if (Math.random() < mutation)
					w2p[i][j] *= 0.5 + Math.random();
			}
		}
		w1 = w1p;
		w2 = w2p;
	}
}

public static int random(double[] p) {
	int n = p.length;
	double u = Math.random();
	double cdf = 0;
	for (int i = 0; i < n; i++) {
		cdf += p[i];
		if (u < cdf)
			return i;
	}
	return n - 1;
}