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