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

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