annotate projects/tank_wars/game.cpp @ 10:49f22e1246b2

Tank wars!
author andrewm
date Thu, 13 Nov 2014 15:58:08 +0100
parents
children fbfeb5895efd
rev   line source
andrewm@10 1 /*
andrewm@10 2 * game.cpp
andrewm@10 3 *
andrewm@10 4 * Created on: Nov 10, 2014
andrewm@10 5 * Author: parallels
andrewm@10 6 */
andrewm@10 7
andrewm@10 8 #include <cmath>
andrewm@10 9 #include <cstdlib>
andrewm@10 10 #include "vector_graphics.h"
andrewm@10 11 #include "../../include/Utilities.h"
andrewm@10 12
andrewm@10 13 // Virtual screen size
andrewm@10 14 int screenWidth, screenHeight;
andrewm@10 15
andrewm@10 16 // Basic information on the terrain and the tanks
andrewm@10 17 float *groundLevel; // Y coordinate of the ground for each X coordinate
andrewm@10 18 float tank1X, tank1Y, tank2X, tank2Y; // Positions of the two tanks
andrewm@10 19 float tankRadius = 20; // Radius of the tanks
andrewm@10 20 float cannonLength = 40; // How long the cannon on each tank extends
andrewm@10 21 float gravity = 0.05; // Strength of gravity
andrewm@10 22
andrewm@10 23 // Current state of the game
andrewm@10 24 int playerHasWon = 0; // 1 if player 1 wins, 2 if player 2 wins, 0 if game in progress
andrewm@10 25 bool player1Turn = true; // true if it's player 1's turn; false otherwise
andrewm@10 26 float tank1CannonAngle = M_PI/2;
andrewm@10 27 float tank2CannonAngle = M_PI/2; // Direction the tank cannons are pointing
andrewm@10 28 float tank1CannonStrength = 3;
andrewm@10 29 float tank2CannonStrength = 3; // Strength of intended projectile launch
andrewm@10 30
andrewm@10 31 // Location of the projectile
andrewm@10 32 bool projectileInMotion = false;
andrewm@10 33 float projectilePositionX, projectilePositionY;
andrewm@10 34 float projectileVelocityX, projectileVelocityY;
andrewm@10 35
andrewm@10 36 // Useful utility function for generating random floating-point values
andrewm@10 37 float randomFloat(float low, float hi)
andrewm@10 38 {
andrewm@10 39 float r = (float)random() / (float)RAND_MAX;
andrewm@10 40 return map(r, 0, 1, low, hi);
andrewm@10 41 }
andrewm@10 42
andrewm@10 43 // Restart the game, without reallocating memory
andrewm@10 44 void restartGame()
andrewm@10 45 {
andrewm@10 46 float player1Height = randomFloat(screenHeight/2, screenHeight-5);
andrewm@10 47 float player2Height = randomFloat(screenHeight/2, screenHeight-5);
andrewm@10 48 for(int i = 0; i < screenWidth * 0.2; i++) {
andrewm@10 49 groundLevel[i] = player1Height;
andrewm@10 50 }
andrewm@10 51 for(int i = screenWidth * 0.2; i < screenWidth * 0.8; i++) {
andrewm@10 52 groundLevel[i] = player1Height + (player2Height - player1Height) * (i - screenWidth*0.2)/(screenWidth*0.6);
andrewm@10 53 }
andrewm@10 54 for(int i = screenWidth * 0.8; i < screenWidth; i++) {
andrewm@10 55 groundLevel[i] = player2Height;
andrewm@10 56 }
andrewm@10 57
andrewm@10 58 // Set the location of the two tanks so they rest on the ground at opposite sides
andrewm@10 59 tank1X = screenWidth * 0.1;
andrewm@10 60 tank1Y = player1Height;
andrewm@10 61 tank2X = screenWidth * 0.9;
andrewm@10 62 tank2Y = player2Height;
andrewm@10 63
andrewm@10 64 playerHasWon = 0;
andrewm@10 65 projectileInMotion = false;
andrewm@10 66 }
andrewm@10 67
andrewm@10 68 // Initialise the game
andrewm@10 69 void setupGame(int width, int height)
andrewm@10 70 {
andrewm@10 71 // Set the screen size
andrewm@10 72 screenWidth = width;
andrewm@10 73 screenHeight = height;
andrewm@10 74
andrewm@10 75 // Initialize the ground level
andrewm@10 76 groundLevel = new float[screenWidth];
andrewm@10 77
andrewm@10 78 restartGame();
andrewm@10 79 }
andrewm@10 80
andrewm@10 81 // Advance the turn to the next player
andrewm@10 82 void nextPlayersTurn() {
andrewm@10 83 player1Turn = !player1Turn;
andrewm@10 84 }
andrewm@10 85
andrewm@10 86
andrewm@10 87 // Move forward one frame on the game physics
andrewm@10 88 void nextGameFrame()
andrewm@10 89 {
andrewm@10 90 if(!projectileInMotion)
andrewm@10 91 return;
andrewm@10 92
andrewm@10 93 // Update position of projectile
andrewm@10 94 projectilePositionX += projectileVelocityX;
andrewm@10 95 projectilePositionY += projectileVelocityY;
andrewm@10 96 projectileVelocityY += gravity;
andrewm@10 97
andrewm@10 98 // Check collision with tanks first: a collision with tank 1 means player 2 wins and vice-versa
andrewm@10 99 if((tank1X - projectilePositionX)*(tank1X - projectilePositionX) +
andrewm@10 100 (tank1Y - projectilePositionY)*(tank1Y - projectilePositionY)
andrewm@10 101 <= tankRadius * tankRadius)
andrewm@10 102 {
andrewm@10 103 projectileInMotion = false;
andrewm@10 104 playerHasWon = 2;
andrewm@10 105 }
andrewm@10 106 else if((tank2X - projectilePositionX)*(tank2X - projectilePositionX) +
andrewm@10 107 (tank2Y - projectilePositionY)*(tank2Y - projectilePositionY)
andrewm@10 108 <= tankRadius * tankRadius)
andrewm@10 109 {
andrewm@10 110 projectileInMotion = false;
andrewm@10 111 playerHasWon = 1;
andrewm@10 112 }
andrewm@10 113 else if(projectilePositionX < 0 || projectilePositionX >= screenWidth) {
andrewm@10 114 // Check collision whether projectile has exited the screen to the left or right
andrewm@10 115 projectileInMotion = false;
andrewm@10 116 nextPlayersTurn();
andrewm@10 117 }
andrewm@10 118 else if(projectilePositionY >= groundLevel[(int)floorf(projectilePositionX)]) {
andrewm@10 119 // Check for projectile collision with ground
andrewm@10 120 projectileInMotion = false;
andrewm@10 121 nextPlayersTurn();
andrewm@10 122 }
andrewm@10 123 }
andrewm@10 124
andrewm@10 125 // Updates for game state
andrewm@10 126 void setTank1CannonAngle(float angle)
andrewm@10 127 {
andrewm@10 128 tank1CannonAngle = angle;
andrewm@10 129 }
andrewm@10 130
andrewm@10 131 void setTank2CannonAngle(float angle)
andrewm@10 132 {
andrewm@10 133 tank2CannonAngle = angle;
andrewm@10 134 }
andrewm@10 135
andrewm@10 136 void setTank1CannonStrength(float strength)
andrewm@10 137 {
andrewm@10 138 tank1CannonStrength = strength;
andrewm@10 139 }
andrewm@10 140
andrewm@10 141 void setTank2CannonStrength(float strength)
andrewm@10 142 {
andrewm@10 143 tank2CannonStrength = strength;
andrewm@10 144 }
andrewm@10 145
andrewm@10 146 // FIRE!
andrewm@10 147 void fireProjectile()
andrewm@10 148 {
andrewm@10 149 // Can't fire while projectile is already moving, or if someone has won
andrewm@10 150 if(projectileInMotion)
andrewm@10 151 return;
andrewm@10 152 if(playerHasWon != 0)
andrewm@10 153 return;
andrewm@10 154
andrewm@10 155 if(player1Turn) {
andrewm@10 156 projectilePositionX = tank1X + cannonLength * cosf(tank1CannonAngle);
andrewm@10 157 projectilePositionY = tank1Y - cannonLength * sinf(tank1CannonAngle);
andrewm@10 158 projectileVelocityX = tank1CannonStrength * cosf(tank1CannonAngle);
andrewm@10 159 projectileVelocityY = -tank1CannonStrength * sinf(tank1CannonAngle);
andrewm@10 160 }
andrewm@10 161 else {
andrewm@10 162 projectilePositionX = tank2X + cannonLength * cosf(tank2CannonAngle);
andrewm@10 163 projectilePositionY = tank2Y - cannonLength * sinf(tank2CannonAngle);
andrewm@10 164 projectileVelocityX = tank2CannonStrength * cosf(tank2CannonAngle);
andrewm@10 165 projectileVelocityY = -tank2CannonStrength * sinf(tank2CannonAngle);
andrewm@10 166 }
andrewm@10 167
andrewm@10 168 // GO!
andrewm@10 169 projectileInMotion = true;
andrewm@10 170 }
andrewm@10 171
andrewm@10 172 // Game state queries
andrewm@10 173 bool gameStatusPlayer1Turn()
andrewm@10 174 {
andrewm@10 175 return player1Turn;
andrewm@10 176 }
andrewm@10 177
andrewm@10 178 bool gameStatusProjectileInMotion()
andrewm@10 179 {
andrewm@10 180 return projectileInMotion;
andrewm@10 181 }
andrewm@10 182
andrewm@10 183 int gameStatusWinner()
andrewm@10 184 {
andrewm@10 185 return playerHasWon;
andrewm@10 186 }
andrewm@10 187
andrewm@10 188 // Clean up any allocated memory for the game
andrewm@10 189 void cleanupGame()
andrewm@10 190 {
andrewm@10 191 delete groundLevel;
andrewm@10 192 }
andrewm@10 193
andrewm@10 194 // Drawing routines. Arguments are (interleaved) buffer to render
andrewm@10 195 // into, the available size, and the target for how many samples
andrewm@10 196 // to use (actual usage might vary slightly). Regardless of
andrewm@10 197 // lengthTarget, never use more than bufferSize samples.
andrewm@10 198
andrewm@10 199 int drawGround(float *buffer, int bufferSize, int framesTarget)
andrewm@10 200 {
andrewm@10 201 int length;
andrewm@10 202
andrewm@10 203 // Calculate total length of ground line, to arrive at a speed calculation
andrewm@10 204 float totalLineLength = 0.4f*screenWidth
andrewm@10 205 + sqrtf(0.36f*screenWidth*screenWidth
andrewm@10 206 + (tank2Y-tank1Y)*(tank2Y-tank1Y));
andrewm@10 207
andrewm@10 208 // Speed is calculated in pixels per frame
andrewm@10 209 float speed = totalLineLength / (float)framesTarget;
andrewm@10 210
andrewm@10 211 // Draw three lines: platforms for tanks and the connecting line.
andrewm@10 212 // Eventually, render a more complex ground from the array.
andrewm@10 213 length = renderLine(0, tank1Y, screenWidth * 0.2, tank1Y,
andrewm@10 214 speed, buffer, bufferSize);
andrewm@10 215 length += renderLine(screenWidth * 0.2, tank1Y, screenWidth * 0.8, tank2Y,
andrewm@10 216 speed, &buffer[length], bufferSize - length);
andrewm@10 217 length += renderLine(screenWidth * 0.8, tank2Y, screenWidth, tank2Y,
andrewm@10 218 speed, &buffer[length], bufferSize - length);
andrewm@10 219
andrewm@10 220 return length;
andrewm@10 221 }
andrewm@10 222
andrewm@10 223 int drawTanks(float *buffer, int bufferSize, int framesTarget)
andrewm@10 224 {
andrewm@10 225 int length = 0;
andrewm@10 226
andrewm@10 227 // Calculate total length of tank lines, to arrive at a speed calculation
andrewm@10 228 float totalLineLength = 2.0*M_PI*tankRadius + 2.0*(cannonLength - tankRadius);
andrewm@10 229
andrewm@10 230 // Speed is calculated in pixels per frame
andrewm@10 231 float speed = totalLineLength / (float)framesTarget;
andrewm@10 232
andrewm@10 233 if(playerHasWon != 2) {
andrewm@10 234 // Tank 1 body = semicircle + line
andrewm@10 235 length += renderArc(tank1X, tank1Y, tankRadius, M_PI, 2.0 * M_PI,
andrewm@10 236 speed, buffer, bufferSize);
andrewm@10 237 length += renderLine(tank1X + tankRadius, tank1Y,
andrewm@10 238 tank1X - tankRadius, tank1Y,
andrewm@10 239 speed, &buffer[length], bufferSize - length);
andrewm@10 240 // Tank 1 cannon (line depending on angle)
andrewm@10 241 length += renderLine(tank1X + tankRadius * cosf(tank1CannonAngle),
andrewm@10 242 tank1Y - tankRadius * sinf(tank1CannonAngle),
andrewm@10 243 tank1X + cannonLength * cosf(tank1CannonAngle),
andrewm@10 244 tank1Y - cannonLength * sinf(tank1CannonAngle),
andrewm@10 245 speed, &buffer[length], bufferSize - length);
andrewm@10 246 }
andrewm@10 247
andrewm@10 248 if(playerHasWon != 1) {
andrewm@10 249 // Same idea for tank 2
andrewm@10 250 length += renderArc(tank2X, tank2Y, tankRadius, M_PI, 2.0 * M_PI,
andrewm@10 251 speed, &buffer[length], bufferSize - length);
andrewm@10 252 length += renderLine(tank2X + tankRadius, tank2Y,
andrewm@10 253 tank2X - tankRadius, tank2Y,
andrewm@10 254 speed, &buffer[length], bufferSize - length);
andrewm@10 255 length += renderLine(tank2X + tankRadius * cosf(tank2CannonAngle),
andrewm@10 256 tank2Y - tankRadius * sinf(tank2CannonAngle),
andrewm@10 257 tank2X + cannonLength * cosf(tank2CannonAngle),
andrewm@10 258 tank2Y - cannonLength * sinf(tank2CannonAngle),
andrewm@10 259 speed, &buffer[length], bufferSize - length);
andrewm@10 260 }
andrewm@10 261
andrewm@10 262 return length;
andrewm@10 263 }
andrewm@10 264
andrewm@10 265 int drawProjectile(float *buffer, int bufferSize, int framesTarget)
andrewm@10 266 {
andrewm@10 267 if(!projectileInMotion)
andrewm@10 268 return 0;
andrewm@10 269
andrewm@10 270 // Draw a point for a specified number of frames (each containing X and Y)
andrewm@10 271 // Return the number of items used in the buffer, which will be twice
andrewm@10 272 // the number of frames unless the buffer is full
andrewm@10 273
andrewm@10 274 if(bufferSize/2 < framesTarget) {
andrewm@10 275 renderPoint(projectilePositionX, projectilePositionY, buffer, bufferSize/2);
andrewm@10 276 return bufferSize;
andrewm@10 277 }
andrewm@10 278 else {
andrewm@10 279 renderPoint(projectilePositionX, projectilePositionY, buffer, framesTarget);
andrewm@10 280 return framesTarget*2;
andrewm@10 281 }
andrewm@10 282 }
andrewm@10 283
andrewm@10 284 // Main drawing routine entry point
andrewm@10 285 int drawGame(float *buffer, int bufferSize)
andrewm@10 286 {
andrewm@10 287 int length;
andrewm@10 288
andrewm@10 289 // Based on buffer size, come up with speeds for each of the elements
andrewm@10 290 // 50% of time to ground; 30% to the tanks and 20% to the projectile
andrewm@10 291 // Give a margin of 25% beyond so we don't run out of buffer space
andrewm@10 292 // if things take longer to draw than we guess they will
andrewm@10 293 const float amountToUse = 0.375; // 0.75/2 because two samples per frame
andrewm@10 294 const float groundFraction = 0.5 * amountToUse;
andrewm@10 295 const float tankFraction = 0.3 * amountToUse;
andrewm@10 296 const float projectileFraction = 0.2 * amountToUse;
andrewm@10 297
andrewm@10 298 length = drawGround(buffer, bufferSize, bufferSize * groundFraction);
andrewm@10 299 length += drawTanks(&buffer[length], bufferSize - length,
andrewm@10 300 bufferSize * tankFraction);
andrewm@10 301 length += drawProjectile(&buffer[length], bufferSize - length,
andrewm@10 302 bufferSize * projectileFraction);
andrewm@10 303
andrewm@10 304 return length;
andrewm@10 305 }