/*
	FlappyCat
	author: minbbb
  https://github.com/minbbb/FlappyCat
*/
#include <EEPROM.h>
#include <TVout.h>
#include <TVoutfonts/fontALL.h>
#include "cat.h"
#include "cloud.h"
#include "cloud2.h"
#include "catfly.h"

#define CONTROL_PIN 2	// pin to control
#define X_CLOUD 13	// x coordinate of the first cloud
#define Y_CLOUD 0	// y coordinate of the first cloud
#define X_CLOUD2 96	// x coordinate of the second cloud
#define Y_CLOUD2 79	// y coordinate of the second cloud
#define ACCEL_DOWN 0.1	// fall acceleration
#define ACCEL_UP 0.4	// takeoff acceleration

#define HEIGHT_BLOCK_DOWN 9	// height of the second pipe block
#define WIDTH_BLOCK_DOWN 23	// width of the second pipe block
#define WIDTH_BLOCK_UP 17	// width of the first pipe block
#define HEIGHT_HOLE 40	// pipe spacing in height
#define WIDTH_HOLE 80	// pipe spacing wide
#define COUNT_BLOCK 2	// number of pipes drawn at once

TVout TV;

int xBlock[COUNT_BLOCK];  // x coordinates of the pipes 
int yBlock[COUNT_BLOCK];  // y coordinates of the pipes
int xCat;	// x coordinate of the cat
int yCat;	// y coordinate of the cat
float aCat;	// acceleration of the cat
int score;

void setup() {
	pinMode(CONTROL_PIN, INPUT_PULLUP);
	TV.begin(NTSC, 128, 96);
	TV.select_font(font8x8);
	randomSeed(analogRead(0));
	preview();
}

void loop() {
	TV.clear_screen();
	for (int i = 0; i <= COUNT_BLOCK - 1; i++) {
		xBlock[i] = 0;
		yBlock[i] = 0;
	}
	xBlock[0] = 128;
	for (int i = 0; i < COUNT_BLOCK; i++) {
		yBlock[i] = random(2 + HEIGHT_BLOCK_DOWN, 94 - HEIGHT_BLOCK_DOWN - HEIGHT_HOLE);
		if (i >= 1) {
			xBlock[i] = xBlock[i - 1] + WIDTH_HOLE;
		}
	}

	startDemo();
	startGame();
}

void preview() {
	int c = 2;

	for (int i = 0; i <= 24;) {
		TV.bitmap(X_CLOUD, c + Y_CLOUD, cloud);
		TV.bitmap(X_CLOUD2, c + Y_CLOUD2, cloud2);
		for (int ii = 0; ii <= 6; ii++) {
			if (i >= 0)
				TV.bitmap(i, 21, cat);
			else {
				TV.bitmap(i, 19, cat, 79, 56);
			}

			delay(50);
			i += 1;
		}

		if (i > 24)
			break;
  
		TV.draw_rect(X_CLOUD, c + Y_CLOUD, pgm_read_byte_near(cloud), pgm_read_byte_near(cloud + 1), 0, 0);
		TV.draw_rect(X_CLOUD2, c + Y_CLOUD2, pgm_read_byte_near(cloud2), pgm_read_byte_near(cloud2 + 1), 0, 0);
		c -= 2;

		TV.bitmap(X_CLOUD, c + Y_CLOUD, cloud);
		TV.bitmap(X_CLOUD2, c + Y_CLOUD2, cloud2);
  
		for (int ii = 0; ii <= 6; ii++) {
			if (i >= 0)
				TV.bitmap(i, 21, cat);
			else {
				TV.bitmap(i, 19, cat, 79, 56);
			}
			delay(50);
			i += 1;
		}
  
		if (i > 24)
			break;
  
		TV.draw_rect(X_CLOUD, c + Y_CLOUD, pgm_read_byte_near(cloud), pgm_read_byte_near(cloud + 1), 0, 0);
		TV.draw_rect(X_CLOUD2, c + Y_CLOUD2, pgm_read_byte_near(cloud2), pgm_read_byte_near(cloud2 + 1), 0, 0);
		c += 2;

		TV.bitmap(X_CLOUD, c + Y_CLOUD, cloud);
		TV.bitmap(X_CLOUD2, c + Y_CLOUD2, cloud2);
  
		for (int ii = 0; ii <= 6; ii++) {
			if (i >= 0)
				TV.bitmap(i, 21, cat);
			else {
				TV.bitmap(i, 19, cat, 79, 56);
			}
			delay(50);
			i += 1;
		}

		if (i > 24)
			break;

		TV.draw_rect(X_CLOUD, c + Y_CLOUD, pgm_read_byte_near(cloud), pgm_read_byte_near(cloud + 1), 0, 0);
		TV.draw_rect(X_CLOUD2, c + Y_CLOUD2, pgm_read_byte_near(cloud2), pgm_read_byte_near(cloud2 + 1), 0, 0);
		c += 2;

		TV.bitmap(X_CLOUD, c + Y_CLOUD, cloud);
		TV.bitmap(X_CLOUD2, c + Y_CLOUD2, cloud2);
		for (int ii = 0; ii <= 6; ii++) {
			if (i >= 0)
				TV.bitmap(i, 21, cat);
			else {
				TV.bitmap(i, 19, cat, 79, 56);
			}
			delay(50);
			i += 1;
		}

		if (i > 24)
			break;

		TV.draw_rect(X_CLOUD, c + Y_CLOUD, pgm_read_byte_near(cloud), pgm_read_byte_near(cloud + 1), 0, 0);
		TV.draw_rect(X_CLOUD2, c + Y_CLOUD2, pgm_read_byte_near(cloud2), pgm_read_byte_near(cloud2 + 1), 0, 0);
		c -= 2;
	}

	TV.print(45, 3, "FlappyCat");
	TV.select_font(font4x6);
	TV.print(7, 85, "Best:");

	String stringVar = String(EEPROM.read(0), DEC);
	char charBufVar[50];

	stringVar.toCharArray(charBufVar, 50);
	TV.print(27, 85, charBufVar);
	TV.select_font(font8x8);

	// pinMode(2, INPUT);

	unsigned int music[]	=	 {500, 600, 700, 400, 200, 300, 600, 200, 250, 400};
	unsigned int musicDelay[] = {100, 200, 100, 50 , 150, 100, 150, 100, 100, 400};
	unsigned int previewtime = millis();

	for (unsigned int t = 300, st = 3, pt = 0, mus = 0;;) {
		pt = millis() - previewtime;
		if (pt / t >= 1) {
			mus++;
			if (mus >= sizeof(music) / sizeof(unsigned int)) {
				mus = 0;
			}
			TV.tone(music[mus], musicDelay[mus]);
			previewtime = millis();
			TV.draw_rect(X_CLOUD, c + Y_CLOUD, 18, 13, 0, 0);
			TV.draw_rect(X_CLOUD2, c + Y_CLOUD2, 18, 13, 0, 0);
			st++;
			if (st >= 4)
				st = 0;
			switch (st) {
				case 0: {
						c = 2;
						break;
					}
				case 1: {
						c = 0;
						break;
					}
				case 2: {
						c = 2;
						break;
					}
				case 3: {
						c = 4;
						break;
					}
			}
			TV.bitmap(X_CLOUD, c + Y_CLOUD, cloud);
			TV.bitmap(X_CLOUD2, c + Y_CLOUD2, cloud2);
		}
		if (digitalRead(CONTROL_PIN) == LOW) {
			return;
		}
	}
}

void startDemo() {
	int checkButton = 0;
	xCat = 25;
	yCat = 29;	// Coordinate to which the cat will fall at the start
	aCat = 0;
	score = 0;
	TV.select_font(font4x6);
	TV.print(65, 40, "Press any key");
	TV.print(7, 85, "Best:");
	String stringVar = String(EEPROM.read(0), DEC);
	char charBufVar[50];
	stringVar.toCharArray(charBufVar, 50);
	TV.print(27, 85, charBufVar);
	TV.select_font(font8x8);
	TV.draw_line(0, 0, 128, 0, 1);
	TV.draw_line(0, 95, 128, 95, 1);
	for (;;) {
		flyCat();
		if (yCat >= 47) { // Coordinate to which the cat will bounce at the start
			jump();
		}
		if (digitalRead(CONTROL_PIN) == HIGH) {
			checkButton = 1;
		}
		if (digitalRead(CONTROL_PIN) == LOW && checkButton == 1) {
			TV.draw_rect(65, 40, 52, 6, 0, 0);
			return;
		}
	}
}

int flyCat() {
	TV.delay_frame(1);
	TV.draw_rect(xCat, yCat, 20, 14, 0, 0);
	yCat = yCat - aCat;
	if (yCat < 1) {
		yCat = 1;
		aCat = 0;
	}
	// Tracking fall to the floor
	if (yCat > 94 - pgm_read_byte_near(catFly + 1)) {
		yCat = 94 - pgm_read_byte_near(catFly + 1);
		TV.bitmap(xCat, yCat, catFly);
		TV.tone(300, 100);
		delay(100);
		TV.tone(200, 100);
		delay(100);
		return 1;
	}
	// Pipe collision tracking
	for (int i = 0; i < COUNT_BLOCK; i++) {
		if ((xCat + pgm_read_byte_near(catFly) >= xBlock[i] && xCat <= xBlock[i] + WIDTH_BLOCK_DOWN) && (yCat <= yBlock[i] || yCat + pgm_read_byte_near(catFly + 1) >= yBlock[i] + HEIGHT_HOLE)) {
			TV.bitmap(xCat, yCat, catFly);
			TV.tone(300, 100);
			delay(100);
			TV.tone(200, 100);
			delay(100);
			return 1;
		}
	}
	TV.bitmap(xCat, yCat, catFly);
	aCat = aCat - ACCEL_DOWN;
	return 0;
}

void jump() {
	aCat += ACCEL_UP;
}

void startGame() {
	for (;;) {
		TV.print(4, 4, score);
		moveBlock();
		if (flyCat()) {
			TV.draw_rect(45, 3, 67, 11, 0, 1);
			TV.print(47, 5, "You Lose");
			TV.print(4, 4, score);
			if (EEPROM.read(0) < score) {
				EEPROM.write(0, score);
			}
			delay(300);
			for (;;) {
				if (digitalRead(CONTROL_PIN) == LOW) {
					//go to start
					return;
				}
			}
		}
		if (digitalRead(CONTROL_PIN) == LOW) {
			jump();
		}
	}
}

void moveBlock() {
	// Calculation of coordinates
	for (int i = 0; i < COUNT_BLOCK; i++) {
		xBlock[i] = xBlock[i] - 1;
		if (xBlock[i] <= -WIDTH_BLOCK_DOWN) {
			switch (i) {
				case 0: {
						xBlock[i] = xBlock[1] + WIDTH_HOLE;
						break;
					}
				case 1: {
						xBlock[i] = xBlock[0] + WIDTH_HOLE;
						break;
					}
			}
			yBlock[i] = random(2 + HEIGHT_BLOCK_DOWN, 94 - HEIGHT_BLOCK_DOWN - HEIGHT_HOLE);
		}
	}

	// Drawing
	for (int i = 0; i < COUNT_BLOCK; i++) {
		if (xBlock[i] <= 127 - WIDTH_BLOCK_DOWN && xBlock[i] >= 0) {
			TV.draw_rect(xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), 0, WIDTH_BLOCK_UP, yBlock[i] - HEIGHT_BLOCK_DOWN, 0, 1);
			TV.draw_rect(xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), yBlock[i] + HEIGHT_HOLE + HEIGHT_BLOCK_DOWN, WIDTH_BLOCK_UP, 95 - yBlock[i] - HEIGHT_HOLE - HEIGHT_BLOCK_DOWN, 0, 1);

			TV.draw_rect(xBlock[i], yBlock[i] - HEIGHT_BLOCK_DOWN, WIDTH_BLOCK_DOWN, HEIGHT_BLOCK_DOWN, 0, 1);
			TV.draw_rect(xBlock[i], yBlock[i] + HEIGHT_HOLE, WIDTH_BLOCK_DOWN, HEIGHT_BLOCK_DOWN, 0, 1);
			continue;
		}

		// if the pipe is x >= 128
		if (xBlock[i] <= 127 && xBlock[i] >= 127 - WIDTH_BLOCK_DOWN) {
			if (xBlock[i] <= 127 - ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)) && xBlock[i] >= 127 - (((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)) + WIDTH_BLOCK_UP)) { //
				TV.draw_rect(xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), 0, 127 - xBlock[i] - ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), yBlock[i] - HEIGHT_BLOCK_DOWN, 0, 1);
				TV.draw_rect(xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), yBlock[i] + HEIGHT_HOLE + HEIGHT_BLOCK_DOWN, 127 - xBlock[i] - ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), 95 - yBlock[i] - HEIGHT_HOLE - HEIGHT_BLOCK_DOWN, 0, 1);
			} else {
				if (xBlock[i] <= 127 - ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2))) {
					TV.draw_rect(xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), 0, WIDTH_BLOCK_UP, yBlock[i] - HEIGHT_BLOCK_DOWN, 0, 1);
					TV.draw_rect(xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), yBlock[i] + HEIGHT_HOLE + HEIGHT_BLOCK_DOWN, WIDTH_BLOCK_UP, 95 - yBlock[i] - HEIGHT_HOLE - HEIGHT_BLOCK_DOWN, 0, 1);
				}
			}
			TV.draw_rect(xBlock[i], yBlock[i] - HEIGHT_BLOCK_DOWN, 127 - xBlock[i], HEIGHT_BLOCK_DOWN, 0, 1);
			TV.draw_rect(xBlock[i], yBlock[i] + HEIGHT_HOLE, 127 - xBlock[i], HEIGHT_BLOCK_DOWN, 0, 1);
			continue;
		}

		// if the pipe is x <= 0
		if (xBlock[i] >= -WIDTH_BLOCK_DOWN && xBlock[i] <= 0) {
			if (xBlock[i] <= -((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)) && xBlock[i] >= -(((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)) + WIDTH_BLOCK_UP) ) {
				TV.draw_rect(0, 0, WIDTH_BLOCK_UP + (xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2))), yBlock[i] - HEIGHT_BLOCK_DOWN, 0, 1);
				TV.draw_rect(0, yBlock[i] + HEIGHT_HOLE + HEIGHT_BLOCK_DOWN, WIDTH_BLOCK_UP + (xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2))), 95 - yBlock[i] - HEIGHT_HOLE - HEIGHT_BLOCK_DOWN, 0, 1);
			} else {
				if (xBlock[i] >= -(WIDTH_BLOCK_DOWN - ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)))) {
					TV.draw_rect(xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), 0, WIDTH_BLOCK_UP, yBlock[i] - HEIGHT_BLOCK_DOWN, 0, 1);
					TV.draw_rect(xBlock[i] + ((WIDTH_BLOCK_DOWN / 2) - (WIDTH_BLOCK_UP / 2)), yBlock[i] + HEIGHT_HOLE + HEIGHT_BLOCK_DOWN, WIDTH_BLOCK_UP, 95 - yBlock[i] - HEIGHT_HOLE - HEIGHT_BLOCK_DOWN, 0, 1);
				}
			}
			TV.draw_rect(0, yBlock[i] - HEIGHT_BLOCK_DOWN, WIDTH_BLOCK_DOWN + xBlock[i], HEIGHT_BLOCK_DOWN, 0, 1);
			TV.draw_rect(0, yBlock[i] + HEIGHT_HOLE, WIDTH_BLOCK_DOWN + xBlock[i], HEIGHT_BLOCK_DOWN, 0, 1);
			continue;
		}
	}

	// Scoring
	for (int i = 0; i < COUNT_BLOCK; i++) {
		if (xCat + pgm_read_byte_near(catFly) - (WIDTH_BLOCK_DOWN / 2) == xBlock[i]) {
			score++;
			TV.tone(500, 100);
		}
	}

	// Drawing the top and bottom lines
	TV.draw_line(0, 0, 128, 0, 1);
	TV.draw_line(0, 95, 128, 95, 1);
}