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 andrewm@10: #include andrewm@10: #include "vector_graphics.h" andrewm@68: #include 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@268: bool tankHitJustOccurred = 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@268: float player1Height = screenHeight * 3/4; // randomFloat(screenHeight/2, screenHeight-5); andrewm@268: float player2Height = screenHeight - 5; // 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@268: collisionJustOccurred = false; andrewm@268: tankHitJustOccurred = 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@268: collisionJustOccurred = false; andrewm@268: tankHitJustOccurred = 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@268: bool gameStatusTankHitOccurred() andrewm@268: { andrewm@268: if(tankHitJustOccurred) { andrewm@268: tankHitJustOccurred = false; andrewm@268: return true; andrewm@268: } andrewm@268: return false; andrewm@268: } andrewm@268: andrewm@268: 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: }