andrewm@10: /*
andrewm@10:  * game.cpp
andrewm@10:  *
andrewm@10:  *  Created on: Nov 10, 2014
andrewm@10:  *      Author: parallels
andrewm@10:  */
andrewm@10: 
andrewm@10: #include <cmath>
andrewm@10: #include <cstdlib>
andrewm@10: #include "vector_graphics.h"
andrewm@68: #include <Utilities.h>
andrewm@10: 
andrewm@10: // Virtual screen size
andrewm@10: int screenWidth, screenHeight;
andrewm@10: 
andrewm@10: // Basic information on the terrain and the tanks
andrewm@10: float *groundLevel;  // Y coordinate of the ground for each X coordinate
andrewm@10: float tank1X, tank1Y, tank2X, tank2Y; // Positions of the two tanks
andrewm@10: float tankRadius = 20;  // Radius of the tanks
andrewm@10: float cannonLength = 40;   // How long the cannon on each tank extends
andrewm@10: float gravity = 0.05;      // Strength of gravity
andrewm@10: 
andrewm@10: // Current state of the game
andrewm@10: int playerHasWon = 0; // 1 if player 1 wins, 2 if player 2 wins, 0 if game in progress
andrewm@10: bool player1Turn = true;  // true if it's player 1's turn; false otherwise
andrewm@10: float tank1CannonAngle = M_PI/2;
andrewm@10: float tank2CannonAngle = M_PI/2; // Direction the tank cannons are pointing
andrewm@10: float tank1CannonStrength = 3;
andrewm@10: float tank2CannonStrength = 3; // Strength of intended projectile launch
andrewm@10: 
andrewm@10: // Location of the projectile
andrewm@10: bool projectileInMotion = false;
andrewm@10: float projectilePositionX, projectilePositionY;
andrewm@10: float projectileVelocityX, projectileVelocityY;
andrewm@10: 
andrewm@22: // Infor needed for sound rendering
andrewm@22: bool collisionJustOccurred = false;
andrewm@22: 
andrewm@10: // Useful utility function for generating random floating-point values
andrewm@10: float randomFloat(float low, float hi)
andrewm@10: {
andrewm@10: 	float r = (float)random() / (float)RAND_MAX;
andrewm@10: 	return map(r, 0, 1, low, hi);
andrewm@10: }
andrewm@10: 
andrewm@10: // Restart the game, without reallocating memory
andrewm@10: void restartGame()
andrewm@10: {
andrewm@10: 	float player1Height = randomFloat(screenHeight/2, screenHeight-5);
andrewm@10: 	float player2Height = randomFloat(screenHeight/2, screenHeight-5);
andrewm@10: 	for(int i = 0; i < screenWidth * 0.2; i++) {
andrewm@10: 		groundLevel[i] = player1Height;
andrewm@10: 	}
andrewm@10: 	for(int i = screenWidth * 0.2; i < screenWidth * 0.8; i++) {
andrewm@10: 		groundLevel[i] = player1Height + (player2Height - player1Height) * (i - screenWidth*0.2)/(screenWidth*0.6);
andrewm@10: 	}
andrewm@10: 	for(int i = screenWidth * 0.8; i < screenWidth; i++) {
andrewm@10: 		groundLevel[i] = player2Height;
andrewm@10: 	}
andrewm@10: 
andrewm@10: 	// Set the location of the two tanks so they rest on the ground at opposite sides
andrewm@10: 	tank1X = screenWidth * 0.1;
andrewm@10: 	tank1Y = player1Height;
andrewm@10: 	tank2X = screenWidth * 0.9;
andrewm@10: 	tank2Y = player2Height;
andrewm@10: 
andrewm@10: 	playerHasWon = 0;
andrewm@10: 	projectileInMotion = false;
andrewm@10: }
andrewm@10: 
andrewm@10: // Initialise the game
andrewm@10: void setupGame(int width, int height)
andrewm@10: {
andrewm@10: 	// Set the screen size
andrewm@10: 	screenWidth = width;
andrewm@10: 	screenHeight = height;
andrewm@10: 
andrewm@10: 	// Initialize the ground level
andrewm@10: 	groundLevel = new float[screenWidth];
andrewm@10: 
andrewm@10: 	restartGame();
andrewm@10: }
andrewm@10: 
andrewm@10: // Advance the turn to the next player
andrewm@10: void nextPlayersTurn() {
andrewm@10: 	player1Turn = !player1Turn;
andrewm@10: }
andrewm@10: 
andrewm@10: 
andrewm@10: // Move forward one frame on the game physics
andrewm@10: void nextGameFrame()
andrewm@10: {
andrewm@10: 	if(!projectileInMotion)
andrewm@10: 		return;
andrewm@10: 
andrewm@10: 	// Update position of projectile
andrewm@10: 	projectilePositionX += projectileVelocityX;
andrewm@10: 	projectilePositionY += projectileVelocityY;
andrewm@10: 	projectileVelocityY += gravity;
andrewm@10: 
andrewm@10: 	// Check collision with tanks first: a collision with tank 1 means player 2 wins and vice-versa
andrewm@10: 	if((tank1X - projectilePositionX)*(tank1X - projectilePositionX) +
andrewm@10: 		(tank1Y - projectilePositionY)*(tank1Y - projectilePositionY)
andrewm@10: 		<= tankRadius * tankRadius)
andrewm@10: 	{
andrewm@10: 		projectileInMotion = false;
andrewm@22: 		collisionJustOccurred = true;
andrewm@10: 		playerHasWon = 2;
andrewm@10: 	}
andrewm@10: 	else if((tank2X - projectilePositionX)*(tank2X - projectilePositionX) +
andrewm@10: 		(tank2Y - projectilePositionY)*(tank2Y - projectilePositionY)
andrewm@10: 		<= tankRadius * tankRadius)
andrewm@10: 	{
andrewm@10: 		projectileInMotion = false;
andrewm@22: 		collisionJustOccurred = true;
andrewm@10: 		playerHasWon = 1;
andrewm@10: 	}
andrewm@10: 	else if(projectilePositionX < 0 || projectilePositionX >= screenWidth) {
andrewm@10: 		// Check collision whether projectile has exited the screen to the left or right
andrewm@10: 		projectileInMotion = false;
andrewm@22: 		collisionJustOccurred = true;
andrewm@10: 		nextPlayersTurn();
andrewm@10: 	}
andrewm@10: 	else if(projectilePositionY >= groundLevel[(int)floorf(projectilePositionX)]) {
andrewm@10: 		// Check for projectile collision with ground
andrewm@10: 		projectileInMotion = false;
andrewm@22: 		collisionJustOccurred = true;
andrewm@10: 		nextPlayersTurn();
andrewm@10: 	}
andrewm@10: }
andrewm@10: 
andrewm@10: // Updates for game state
andrewm@10: void setTank1CannonAngle(float angle)
andrewm@10: {
andrewm@10: 	tank1CannonAngle = angle;
andrewm@10: }
andrewm@10: 
andrewm@10: void setTank2CannonAngle(float angle)
andrewm@10: {
andrewm@10: 	tank2CannonAngle = angle;
andrewm@10: }
andrewm@10: 
andrewm@10: void setTank1CannonStrength(float strength)
andrewm@10: {
andrewm@10: 	tank1CannonStrength = strength;
andrewm@10: }
andrewm@10: 
andrewm@10: void setTank2CannonStrength(float strength)
andrewm@10: {
andrewm@10: 	tank2CannonStrength = strength;
andrewm@10: }
andrewm@10: 
andrewm@10: // FIRE!
andrewm@10: void fireProjectile()
andrewm@10: {
andrewm@10: 	// Can't fire while projectile is already moving, or if someone has won
andrewm@10: 	if(projectileInMotion)
andrewm@10: 		return;
andrewm@10: 	if(playerHasWon != 0)
andrewm@10: 		return;
andrewm@10: 
andrewm@10:     if(player1Turn) {
andrewm@10: 		projectilePositionX = tank1X + cannonLength * cosf(tank1CannonAngle);
andrewm@10: 		projectilePositionY = tank1Y - cannonLength * sinf(tank1CannonAngle);
andrewm@10: 		projectileVelocityX = tank1CannonStrength * cosf(tank1CannonAngle);
andrewm@10: 		projectileVelocityY = -tank1CannonStrength * sinf(tank1CannonAngle);
andrewm@10:     }
andrewm@10:     else {
andrewm@10: 		projectilePositionX = tank2X + cannonLength * cosf(tank2CannonAngle);
andrewm@10: 		projectilePositionY = tank2Y - cannonLength * sinf(tank2CannonAngle);
andrewm@10: 		projectileVelocityX = tank2CannonStrength * cosf(tank2CannonAngle);
andrewm@10: 		projectileVelocityY = -tank2CannonStrength * sinf(tank2CannonAngle);
andrewm@10:     }
andrewm@10: 
andrewm@10:     // GO!
andrewm@10:     projectileInMotion = true;
andrewm@10: }
andrewm@10: 
andrewm@10: // Game state queries
andrewm@10: bool gameStatusPlayer1Turn()
andrewm@10: {
andrewm@10: 	return player1Turn;
andrewm@10: }
andrewm@10: 
andrewm@10: bool gameStatusProjectileInMotion()
andrewm@10: {
andrewm@10: 	return projectileInMotion;
andrewm@10: }
andrewm@10: 
andrewm@10: int gameStatusWinner()
andrewm@10: {
andrewm@10: 	return playerHasWon;
andrewm@10: }
andrewm@10: 
andrewm@22: bool gameStatusCollisionOccurred()
andrewm@22: {
andrewm@22: 	if(collisionJustOccurred) {
andrewm@22: 		collisionJustOccurred = false;
andrewm@22: 		return true;
andrewm@22: 	}
andrewm@22: 	return false;
andrewm@22: }
andrewm@22: 
andrewm@22: float gameStatusProjectileHeight()
andrewm@22: {
andrewm@22: 	return projectilePositionY / (float)screenHeight;
andrewm@22: }
andrewm@22: 
andrewm@10: // Clean up any allocated memory for the game
andrewm@10: void cleanupGame()
andrewm@10: {
andrewm@10: 	delete groundLevel;
andrewm@10: }
andrewm@10: 
andrewm@10: // Drawing routines. Arguments are (interleaved) buffer to render
andrewm@10: // into, the available size, and the target for how many samples
andrewm@10: // to use (actual usage might vary slightly). Regardless of
andrewm@10: // lengthTarget, never use more than bufferSize samples.
andrewm@10: 
andrewm@10: int drawGround(float *buffer, int bufferSize, int framesTarget)
andrewm@10: {
andrewm@10: 	int length;
andrewm@10: 
andrewm@10: 	// Calculate total length of ground line, to arrive at a speed calculation
andrewm@10: 	float totalLineLength = 0.4f*screenWidth
andrewm@10: 							+ sqrtf(0.36f*screenWidth*screenWidth
andrewm@10: 									+ (tank2Y-tank1Y)*(tank2Y-tank1Y));
andrewm@10: 
andrewm@10: 	// Speed is calculated in pixels per frame
andrewm@10: 	float speed = totalLineLength / (float)framesTarget;
andrewm@10: 
andrewm@10: 	// Draw three lines: platforms for tanks and the connecting line.
andrewm@10: 	// Eventually, render a more complex ground from the array.
andrewm@10: 	length = renderLine(0, tank1Y, screenWidth * 0.2, tank1Y,
andrewm@10: 						speed, buffer, bufferSize);
andrewm@10: 	length += renderLine(screenWidth * 0.2, tank1Y, screenWidth * 0.8, tank2Y,
andrewm@10: 						speed, &buffer[length], bufferSize - length);
andrewm@10: 	length += renderLine(screenWidth * 0.8, tank2Y, screenWidth, tank2Y,
andrewm@10: 						speed, &buffer[length], bufferSize - length);
andrewm@10: 
andrewm@10: 	return length;
andrewm@10: }
andrewm@10: 
andrewm@10: int drawTanks(float *buffer, int bufferSize, int framesTarget)
andrewm@10: {
andrewm@10: 	int length = 0;
andrewm@10: 
andrewm@10: 	// Calculate total length of tank lines, to arrive at a speed calculation
andrewm@10: 	float totalLineLength = 2.0*M_PI*tankRadius + 2.0*(cannonLength - tankRadius);
andrewm@10: 
andrewm@10: 	// Speed is calculated in pixels per frame
andrewm@10: 	float speed = totalLineLength / (float)framesTarget;
andrewm@10: 
andrewm@10: 	if(playerHasWon != 2) {
andrewm@10: 		// Tank 1 body = semicircle + line
andrewm@10: 		length += renderArc(tank1X, tank1Y, tankRadius, M_PI, 2.0 * M_PI,
andrewm@10: 							speed, buffer, bufferSize);
andrewm@10: 		length += renderLine(tank1X + tankRadius, tank1Y,
andrewm@10: 							 tank1X - tankRadius, tank1Y,
andrewm@10: 							speed, &buffer[length], bufferSize - length);
andrewm@10: 		// Tank 1 cannon (line depending on angle)
andrewm@10: 		length += renderLine(tank1X + tankRadius * cosf(tank1CannonAngle),
andrewm@10: 			 tank1Y - tankRadius * sinf(tank1CannonAngle),
andrewm@10: 			 tank1X + cannonLength * cosf(tank1CannonAngle),
andrewm@10: 			 tank1Y - cannonLength * sinf(tank1CannonAngle),
andrewm@10: 			 speed, &buffer[length], bufferSize - length);
andrewm@10: 	}
andrewm@10: 
andrewm@10: 	if(playerHasWon != 1) {
andrewm@10: 		// Same idea for tank 2
andrewm@10: 		length += renderArc(tank2X, tank2Y, tankRadius, M_PI, 2.0 * M_PI,
andrewm@10: 							speed, &buffer[length], bufferSize - length);
andrewm@10: 		length += renderLine(tank2X + tankRadius, tank2Y,
andrewm@10: 							 tank2X - tankRadius, tank2Y,
andrewm@10: 							 speed, &buffer[length], bufferSize - length);
andrewm@10: 		length += renderLine(tank2X + tankRadius * cosf(tank2CannonAngle),
andrewm@10: 			 tank2Y - tankRadius * sinf(tank2CannonAngle),
andrewm@10: 			 tank2X + cannonLength * cosf(tank2CannonAngle),
andrewm@10: 			 tank2Y - cannonLength * sinf(tank2CannonAngle),
andrewm@10: 			 speed, &buffer[length], bufferSize - length);
andrewm@10: 	}
andrewm@10: 
andrewm@10: 	return length;
andrewm@10: }
andrewm@10: 
andrewm@10: int drawProjectile(float *buffer, int bufferSize, int framesTarget)
andrewm@10: {
andrewm@10: 	if(!projectileInMotion)
andrewm@10: 		return 0;
andrewm@10: 
andrewm@10: 	// Draw a point for a specified number of frames (each containing X and Y)
andrewm@10: 	// Return the number of items used in the buffer, which will be twice
andrewm@10: 	// the number of frames unless the buffer is full
andrewm@10: 
andrewm@10: 	if(bufferSize/2 < framesTarget) {
andrewm@10: 		renderPoint(projectilePositionX, projectilePositionY, buffer, bufferSize/2);
andrewm@10: 		return bufferSize;
andrewm@10: 	}
andrewm@10: 	else {
andrewm@10: 		renderPoint(projectilePositionX, projectilePositionY, buffer, framesTarget);
andrewm@10: 		return framesTarget*2;
andrewm@10: 	}
andrewm@10: }
andrewm@10: 
andrewm@10: // Main drawing routine entry point
andrewm@10: int drawGame(float *buffer, int bufferSize)
andrewm@10: {
andrewm@10: 	int length;
andrewm@10: 
andrewm@10: 	// Based on buffer size, come up with speeds for each of the elements
andrewm@10: 	// 50% of time to ground; 30% to the tanks and 20% to the projectile
andrewm@10: 	// Give a margin of 25% beyond so we don't run out of buffer space
andrewm@10: 	// if things take longer to draw than we guess they will
andrewm@10: 	const float amountToUse = 0.375; // 0.75/2 because two samples per frame
andrewm@10: 	const float groundFraction = 0.5 * amountToUse;
andrewm@10: 	const float tankFraction = 0.3 * amountToUse;
andrewm@10: 	const float projectileFraction = 0.2 * amountToUse;
andrewm@10: 
andrewm@10: 	length = drawGround(buffer, bufferSize, bufferSize * groundFraction);
andrewm@10: 	length += drawTanks(&buffer[length], bufferSize - length,
andrewm@10: 						bufferSize * tankFraction);
andrewm@10: 	length += drawProjectile(&buffer[length], bufferSize - length,
andrewm@10: 						bufferSize * projectileFraction);
andrewm@10: 
andrewm@10: 	return length;
andrewm@10: }