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