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