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