/**
 * PGK wars.
 *
 * Copyright (C) 2004 Kenta Cho. Some rights reserved.
 * 
 * @p5 description
 * - 画面下の青い自機をマウスで操作してください
 * - マウスボタンを押すと弾を発射します
 * - 弾を画面中央のひし形に当てて画面上に押しやってください
 * - すべてのひし形を中央より上に押しやるとあなたの勝ちです
 * - 逆にすべてのひし形が中央より下にくると負けです
 * - 弾に接触すると一定時間自機が操作できなくなります
 */
public class Pgk extends BApplet {
	float BATTERY_SIZE = 10;
	float BATTERY_FIRE_INTERVAL = 50;
	Ship[] ship = new Ship[2];
	int PLAYER = 0;
	int COMPUTER = 1;
	Battery[] battery = new Battery[10];
	Bullet[] bullet = new Bullet[64];
	Particle[] particle = new Particle[32];
	BFont font;
	int TITLE = 0;
	int IN_GAME = 1;
	int GAMEOVER = 2;
	int state;
	int winner;
	Title title;
	Gameover gameover;

	void setup() {
		size(200, 400);
		framerate(30);
		smooth();
		cursor();
		font = loadFont("OCR-A.vlw.gz");
		textFont(font);
		for (int i = 0; i < ship.length; i++)
			ship[i] = new Ship();
		ship[0].type = PLAYER;
		ship[1].type = COMPUTER;
		for (int i = 0; i < battery.length; i++)
			battery[i] = new Battery();
		for (int i = 0; i < bullet.length; i++)
			bullet[i] = new Bullet();
		for (int i = 0; i < particle.length; i++)
			particle[i] = new Particle();
		title = new Title();
		gameover = new Gameover();
		startTitle();
	}

	void startTitle() {
		state = TITLE;
		for (int i = 0; i < battery.length; i++) {
			Battery bt = battery[i];
			bt.start((int) random(BATTERY_FIRE_INTERVAL));
			bt.pos.x = (i % 5) * (width / 5) + width / 10;
			bt.pos.y = (i / 5) * (height / 4) + height / 8 * 3;
		}
	}

	void startInGame() {
		state = IN_GAME;
		for (int i = 0; i < bullet.length; i++)
			bullet[i].start();
		for (int i = 0; i < ship.length; i++)
			ship[i].start();
		for (int i = 0; i < particle.length; i++)
			particle[i].start();
	}

	void startGameover() {
		state = GAMEOVER;
		gameover.start();
	}

	void loop() {
		background(80, 100, 50);
		stroke(200, 255, 200);
		line(0, height / 2, width, height / 2);
		if (state == TITLE) {
			for (int i = 0; i < battery.length; i++)
				battery[i].loop();
			title.loop();
		} else if (state == IN_GAME) {
			for (int i = 0; i < ship.length; i++)
				ship[i].loop();			
			for (int i = 0; i < battery.length; i++)
				battery[i].loop();
			for (int i = 0; i < bullet.length; i++)
				if (bullet[i].isExist)
					bullet[i].loop();
			for (int i = 0; i < particle.length; i++)
				if (particle[i].cnt >= 0)
					particle[i].loop();
			if (batteriesCheckGameSet())
				startGameover();
		} else if (state == GAMEOVER) {
			for (int i = 0; i < battery.length; i++)
				battery[i].loop();
			gameover.loop();
		}
	}

	/**
	 * My ship and enemy's ship
	 */
	class Ship {
		int RESPAWN_CNT = 240;
		int INVINSIBLE_CNT = 120;
		Vector pos = new Vector();
		Vector vel = new Vector();
		float MAX_SPEED = 8;
		float speed;
		Shot shot = new Shot();
		int type;
		Vector target = new Vector();
		int cnt;
		
		void start() {
			shot.start();
			if (type == PLAYER) {
				shot.dir = -1;
			}	else if (type == COMPUTER) {
				shot.dir = 1;
				setTarget();
			}
			restart();
			cnt = INVINSIBLE_CNT;
		}
		
		void restart() {
			pos.x = width / 2;			
			if (type == PLAYER)
				pos.y = height / 6 * 5;
			else if (type == COMPUTER)
				pos.y = height / 6;
			vel.x = vel.y = 0;
			speed = 0;
			cnt = RESPAWN_CNT;
		}

		void loop() {
			if (cnt > 0)
				cnt--;
			if (shot.isExist)
				shot.loop();
			if (cnt > INVINSIBLE_CNT)
				return;
			pos.x += vel.x;
			pos.y += vel.y;
			if (type == PLAYER) {
				playerMove();
				if (pos.y < height / 2)
					pos.y = height / 2;
				else if (pos.y > height)
					pos.y = height;
			}	else if (type == COMPUTER) {
				computerMove();
				if (pos.y < 0)
					pos.y = 0;
				else if (pos.y > height / 2)
					pos.y = height / 2;
			}
			if (pos.x < 0)
				pos.x = 0;
			if (pos.x > width)
				pos.x = width;
			if (cnt == 0 && bulletsCheckHit(pos)) {
				for (int i = 0; i < 16; i++) {
					Particle pt = particlesGetInstance();
					pt.set(pos);
				}
				restart();
			}
			if (cnt == 0 || cnt % 20 > 10)
				draw();
		}
		
		void playerMove() {
			int mox = mouseX - (int) pos.x;
			int moy = mouseY - (int) pos.y;
			float mouseDst = sqrt(mox * mox + moy * moy);
			if (mouseDst > 0) {
				vel.x += (mox / mouseDst * speed - vel.x) * 0.1f;
				vel.y += (moy / mouseDst * speed - vel.y) * 0.1f;
			}
			if (mouseDst > 5) {
				speed += (MAX_SPEED - speed) * 0.3f;
			} else {
				speed *= 0.5f;
				vel.x *= 0.2f;
				vel.y *= 0.2f;
			}
			if (!shot.isExist) {
				if (mousePressed)
					shot.set(pos);
			}
		}

		void computerMove() {
			int mox = (int) (target.x - pos.x);
			int moy = (int) (target.y - pos.y);
			float mouseDst = sqrt(mox * mox + moy * moy);
			if (mouseDst > 0) {
				vel.x += (mox / mouseDst * speed - vel.x) * 0.1f;
				vel.y += (moy / mouseDst * speed - vel.y) * 0.1f;
			}
			speed = MAX_SPEED;
			if (mouseDst < 5)
				setTarget();
			if (!shot.isExist && checkBatteryFront(pos.x))
				shot.set(pos);
		}
		
		void setTarget() {
			int bi = (int) random(battery.length);
			Battery tb = null;
			for (int i = 0; i < battery.length; i++) {
				tb = battery[bi];
				if (tb.type == PLAYER)
					break;
				bi++;
				if (bi >= battery.length)
					bi = 0;
			}
			target.x = tb.pos.x;
			target.y = random(tb.pos.y / 2);
		}
		
		boolean checkBatteryFront(float x) {
			for (int i = 0; i < battery.length; i++) {
				if (abs(battery[i].pos.x - x) < BATTERY_SIZE)
					return true;
			}
			return false;
		}

		void draw() {
			push();
			translate(pos.x, pos.y);
			if (type == PLAYER) {
				fill(100, 100, 200);
				stroke(180, 190, 250);
				scale(1, -1);
			} else if (type == COMPUTER) {
				fill(200, 100, 100);
				stroke(250, 190, 180);
			}
			beginShape(TRIANGLE_STRIP);
			vertex(-6, -8);
			vertex(0, 8);
			vertex(0, -2);
			vertex(6, -8);
			endShape();
			pop();
		}
	}

	/**
	 * Ship's shot.
	 */
	class Shot {
		float SPEED = 10;
		Vector pos = new Vector();
		float dir;
		boolean isExist;

		void start() {
			isExist = false;
		}

		void setDir(float d) {
			dir = d;
		}
		
		void set(Vector p) {
			pos.x = p.x;
			pos.y = p.y;
			isExist = true;
		}
		
		void loop() {
			pos.y += SPEED * dir;
			if (pos.y < 0 || pos.y >= height || batteriesCheckHit(pos, dir))
				isExist = false;
			push();
			translate(pos.x, pos.y);
			fill(200, 200, 200);
			stroke(250, 250, 250);
			beginShape(QUADS);
			vertex(-2, -5);			
			vertex(2, -5);			
			vertex(2, 5);			
			vertex(-2, 5);			
			endShape();
			pop();
		}
	}

	/**
	 * Battery.
	 */
	class Battery {
		float SIZE = BATTERY_SIZE;
		float FLIP_MOVE = 3;
		Vector pos = new Vector();
		Vector vel = new Vector();
		int cnt;
		float fireInterval;
		int type;
		
		void start(int c) {
			vel.x = vel.y = 0;
			cnt = c;
			fireInterval = BATTERY_FIRE_INTERVAL;
		}
		
		boolean checkHit(Vector p, float d) {
			if (p.dist(pos) < SIZE) {
				vel.x += pos.x - p.x;
				vel.y += FLIP_MOVE * d;
				return true;
			}
			return false;
		}
		
		void loop() {
			pos.x += vel.x;
			if (pos.x < 0 || pos.x >= width) {
				vel.x = -vel.x;
				pos.x += vel.x * 2;
			}
			pos.y += vel.y;
			if (pos.y < 50)
				pos.y = 50;
			if (pos.y > height - 50)
				pos.y = height - 50;
			if (pos.y > height / 2)
			 	type = COMPUTER;
			else
				type = PLAYER;
			vel.x *= 0.95f;
			vel.y *= 0.95f;
			cnt--;
			if (cnt < 0) {
				cnt = (int) fireInterval;
				Bullet b = bulletsGetInstance();
				if (b != null) {
					Vector ap;
					if (type == COMPUTER)
						ap = ship[1].pos;
					else
						ap = ship[0].pos;
					b.set(pos, atan2(ap.x - pos.x, ap.y - pos.y), 3);
				}
			}
			if (fireInterval > 10)
				fireInterval -= 0.01;
			push();
			translate(pos.x, pos.y);
			if (type == COMPUTER) {
				fill(200, 100, 100);
				stroke(220, 200, 200);
			} else {
				fill(100, 100, 200);
				stroke(200, 200, 220);
			}
			beginShape(QUADS);
			vertex(SIZE, 0);
			vertex(0, SIZE);
			vertex(-SIZE, 0);
			vertex(0, -SIZE);
			endShape();
			pop();
		}
	}

	boolean batteriesCheckHit(Vector p, float d) {
		boolean r = false;
		for (int i = 0; i < battery.length; i++)
			if (battery[i].checkHit(p, d))
				r = true;
		return r;
	}

	boolean batteriesCheckGameSet() {
		int tp = battery[0].type;
		for (int i = 1; i < battery.length; i++) {
			if (battery[i].type != tp)
				return false;
		}
		winner = tp;
		return true;
	}

	/**
	 * Bullets generated from Battery.
	 */
	class Bullet {
		float SIZE = 4;
		Vector pos = new Vector();
		float deg;
		float speed;
		int cnt;
		boolean isExist;
		
		void start() {
			isExist = false;
		}
		
		void set(Vector p, float d, float s) {
			pos.x = p.x;
			pos.y = p.y;
			deg = d;
			speed = s;
			cnt = 0;
			isExist = true;
		}
		
		void loop() {
			pos.x += sin(deg) * speed;
			pos.y += cos(deg) * speed;
			if (pos.x < 0 || pos.x >= width || pos.y < 0 || pos.y >= height) {
				isExist = false;
				return;
			}
			cnt++;
			push();
			translate(pos.x, pos.y);
			fill(200, 200, 100);
			stroke(250, 250, 100);
			rotate(cnt * 0.11f);			
			beginShape(QUADS);
			vertex(SIZE, 0);
			vertex(0, SIZE);
			vertex(-SIZE, 0);
			vertex(0, -SIZE);
			endShape();
			pop();
		}
	}

	int bulletIdx = 0;

	Bullet bulletsGetInstance() {
		for (int i = 0; i < bullet.length; i++) {
			if (!bullet[bulletIdx].isExist)
				return bullet[bulletIdx];
			bulletIdx--;
			if (bulletIdx < 0)
				bulletIdx = bullet.length - 1;
		}
		return null;
	}

	boolean bulletsCheckHit(Vector p) {
		for (int i = 0; i < bullet.length; i++) {
			if (bullet[i].isExist) {
				if (bullet[i].pos.dist(p) < 3)
					return true;
			}
		}
		return false;
	}		

	/**
	 * Particle.
	 */
	class Particle {
		Vector pos = new Vector();
		Vector vel = new Vector();
		int cnt;
		int size;

		void start() {
			cnt = -1;
		}

		void set(Vector p) {
			pos.x = p.x;
			pos.y = p.y;
			vel.x = random(10) - 5;
			vel.y = random(10) - 5;
			cnt = 15 + (int) random(15);
			size = 5 + (int) random(5);
		}

		void loop() {
			pos.x += vel.x;
			pos.y += vel.y;
			vel.x *= 0.96;
			vel.y *= 0.96;
			cnt--;
			size++;
			stroke(230, 250, 230);
			noFill();
			rect(pos.x, pos.y, size, size);
		}
	}

	int particleIdx = 0;

	Particle particlesGetInstance() {
		particleIdx--;
		if (particleIdx < 0)
			particleIdx = particle.length - 1;
		return particle[particleIdx];
	}

	/**
	 * Vector.
	 */
	class Vector {
		float x, y;
		
		public float dist(Vector v) {
		  float ax = abs(x - v.x);
		  float ay = abs(y - v.y);
		  if (ax > ay)
				return ax + ay / 2;
		  else
				return ay + ax / 2;
		}
	}

	/**
	 * Title screen.
	 */
	class Title {
		boolean startPressed;

		void loop() {
			boolean onStart;
			if (mouseX > width / 2 - 60
				&& mouseX < width / 2 + 60
				&& mouseY > height - 100
				&& mouseY < height - 60)
				onStart = true;
			else
				onStart = false;
			push();
			translate(0, 0, 30);
			textSize(32);
			textMode(ALIGN_CENTER);
			fill(255, 255, 255);
			text("PGK wars", width / 2, height / 3);
			stroke(250, 100, 50);
			if (onStart)
				fill(150, 250, 200);
			else
				fill(100, 200, 150);
			strokeWeight(2);
			rect(width / 2 - 50, height - 100, 100, 25);
			strokeWeight(1);
			fill(255, 255, 255);
			textSize(25);
			text("START", width / 2, height - 80);
			pop();
			if (onStart && mousePressed)
				startPressed = true;
			if (!mousePressed && startPressed) {
				startPressed = false;
				if (onStart)
					startInGame();
			}
		}
	}

	/**
	 * Gameover screen.
	 */
	class Gameover {
		int cnt;

		void start() {
			cnt = 0;
		}

		void loop() {
			push();
			translate(0, 0, 30);
			textSize(24);
			textMode(ALIGN_CENTER);
			fill(255, 255, 255);
			if (winner == PLAYER)
				text("You won", width / 2, height / 3);
			else
				text("You lost", width / 2, height / 3);
			pop();
			cnt++;
			if (cnt > 300 || (cnt > 30 && mousePressed)) {
				startTitle();
				return;
			}
		}
	}
}
