1/*******************************************************************************************
2*
3* raylib - sample game: pang
4*
5* Sample game developed by Ian Eito and Albert Martos and Ramon Santamaria
6*
7* This game has been created using raylib v1.3 (www.raylib.com)
8* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
9*
10* Copyright (c) 2015 Ramon Santamaria (@raysan5)
11*
12********************************************************************************************/
13
14#include "raylib.h"
15
16#include <math.h>
17
18#if defined(PLATFORM_WEB)
19 #include <emscripten/emscripten.h>
20#endif
21
22//----------------------------------------------------------------------------------
23// Some Defines
24//----------------------------------------------------------------------------------
25#define PLAYER_BASE_SIZE 20.0f
26#define PLAYER_SPEED 5.0f
27#define PLAYER_MAX_SHOOTS 1
28
29#define MAX_BIG_BALLS 2
30#define BALLS_SPEED 2.0f
31
32//----------------------------------------------------------------------------------
33// Types and Structures Definition
34//----------------------------------------------------------------------------------
35typedef struct Player {
36 Vector2 position;
37 Vector2 speed;
38 Vector3 collider;
39 float rotation;
40} Player;
41
42typedef struct Shoot {
43 Vector2 position;
44 Vector2 speed;
45 float radius;
46 float rotation;
47 int lifeSpawn;
48 bool active;
49} Shoot;
50
51typedef struct Ball {
52 Vector2 position;
53 Vector2 speed;
54 float radius;
55 int points;
56 bool active;
57} Ball;
58
59typedef struct Points {
60 Vector2 position;
61 int value;
62 float alpha;
63} Points;
64
65//------------------------------------------------------------------------------------
66// Global Variables Declaration
67//------------------------------------------------------------------------------------
68static const int screenWidth = 800;
69static const int screenHeight = 450;
70
71static int framesCounter = 0;
72static bool gameOver = false;
73static bool pause = false;
74static int score = 0;
75
76static Player player = { 0 };
77static Shoot shoot[PLAYER_MAX_SHOOTS] = { 0 };
78static Ball bigBalls[MAX_BIG_BALLS] = { 0 };
79static Ball mediumBalls[MAX_BIG_BALLS*2] = { 0 };
80static Ball smallBalls[MAX_BIG_BALLS*4] = { 0 };
81static Points points[5] = { 0 };
82
83// NOTE: Defined triangle is isosceles with common angles of 70 degrees.
84static float shipHeight = 0.0f;
85static float gravity = 0.0f;
86
87static int countmediumBallss = 0;
88static int countsmallBallss = 0;
89static int meteorsDestroyed = 0;
90static Vector2 linePosition = { 0 };
91
92static bool victory = false;
93static bool lose = false;
94static bool awake = false;
95
96//------------------------------------------------------------------------------------
97// Module Functions Declaration (local)
98//------------------------------------------------------------------------------------
99static void InitGame(void); // Initialize game
100static void UpdateGame(void); // Update game (one frame)
101static void DrawGame(void); // Draw game (one frame)
102static void UnloadGame(void); // Unload game
103static void UpdateDrawFrame(void); // Update and Draw (one frame)
104
105//------------------------------------------------------------------------------------
106// Program main entry point
107//------------------------------------------------------------------------------------
108int main(void)
109{
110 // Initialization (Note windowTitle is unused on Android)
111 //---------------------------------------------------------
112 InitWindow(screenWidth, screenHeight, "sample game: pang");
113
114 InitGame();
115
116#if defined(PLATFORM_WEB)
117 emscripten_set_main_loop(UpdateDrawFrame, 0, 1);
118#else
119 SetTargetFPS(60);
120 //--------------------------------------------------------------------------------------
121
122 // Main game loop
123 while (!WindowShouldClose()) // Detect window close button or ESC key
124 {
125 // Update and Draw
126 //----------------------------------------------------------------------------------
127 UpdateDrawFrame();
128 //----------------------------------------------------------------------------------
129 }
130#endif
131 // De-Initialization
132 //--------------------------------------------------------------------------------------
133 UnloadGame(); // Unload loaded data (textures, sounds, models...)
134
135 CloseWindow(); // Close window and OpenGL context
136 //--------------------------------------------------------------------------------------
137
138 return 0;
139}
140
141//------------------------------------------------------------------------------------
142// Module Functions Definitions (local)
143//------------------------------------------------------------------------------------
144
145// Initialize game variables
146static void InitGame(void)
147{
148 int posx, posy;
149 int velx = 0;
150 int vely = 0;
151
152 framesCounter = 0;
153 gameOver = false;
154 pause = false;
155 score = 0;
156
157 victory = false;
158 lose = false;
159 awake = true;
160 gravity = 0.25f;
161
162 linePosition = (Vector2){ 0.0f , 0.0f };
163 shipHeight = (PLAYER_BASE_SIZE/2)/tanf(20*DEG2RAD);
164
165 // Initialization player
166 player.position = (Vector2){ screenWidth/2, screenHeight };
167 player.speed = (Vector2){ PLAYER_SPEED, PLAYER_SPEED };
168 player.rotation = 0;
169 player.collider = (Vector3){ player.position.x, player.position.y - shipHeight/2.0f, 12.0f };
170
171 meteorsDestroyed = 0;
172
173 // Initialize shoots
174 for (int i = 0; i < PLAYER_MAX_SHOOTS; i++)
175 {
176 shoot[i].position = (Vector2){ 0, 0 };
177 shoot[i].speed = (Vector2){ 0, 0 };
178 shoot[i].radius = 2;
179 shoot[i].active = false;
180 shoot[i].lifeSpawn = 0;
181 }
182
183 // Initialize big meteors
184 for (int i = 0; i < MAX_BIG_BALLS; i++)
185 {
186 bigBalls[i].radius = 40.0f;
187 posx = GetRandomValue(0 + bigBalls[i].radius, screenWidth - bigBalls[i].radius);
188 posy = GetRandomValue(0 + bigBalls[i].radius, screenHeight/2);
189
190 bigBalls[i].position = (Vector2){ posx, posy };
191
192 while ((velx == 0) || (vely == 0))
193 {
194 velx = GetRandomValue(-BALLS_SPEED, BALLS_SPEED);
195 vely = GetRandomValue(-BALLS_SPEED, BALLS_SPEED);
196 }
197
198 bigBalls[i].speed = (Vector2){ velx, vely };
199 bigBalls[i].points = 200;
200 bigBalls[i].active = true;
201 }
202
203 // Initialize medium meteors
204 for (int i = 0; i < MAX_BIG_BALLS*2; i++)
205 {
206 mediumBalls[i].position = (Vector2){-100, -100};
207 mediumBalls[i].speed = (Vector2){0,0};
208 mediumBalls[i].radius = 20.0f;
209 mediumBalls[i].points = 100;
210 mediumBalls[i].active = false;
211 }
212
213 // Initialize small meteors
214 for (int i = 0; i < MAX_BIG_BALLS*4; i++)
215 {
216 smallBalls[i].position = (Vector2){ -100, -100 };
217 smallBalls[i].speed = (Vector2){ 0, 0 };
218 smallBalls[i].radius = 10.0f;
219 smallBalls[i].points = 50;
220 smallBalls[i].active = false;
221 }
222
223 // Initialize animated points
224 for (int i = 0; i < 5; i++)
225 {
226 points[i].position = (Vector2){ 0, 0 };
227 points[i].value = 0;
228 points[i].alpha = 0.0f;
229 }
230
231 countmediumBallss = 0;
232 countsmallBallss = 0;
233}
234
235// Update game (one frame)
236void UpdateGame(void)
237{
238 if (!gameOver && !victory)
239 {
240 if (IsKeyPressed('P')) pause = !pause;
241
242 if (!pause)
243 {
244 // Player logic
245 if (IsKeyDown(KEY_LEFT)) player.position.x -= player.speed.x;
246 if (IsKeyDown(KEY_RIGHT)) player.position.x += player.speed.x;
247
248 // Player vs wall collision logic
249 if (player.position.x + PLAYER_BASE_SIZE/2 > screenWidth) player.position.x = screenWidth - PLAYER_BASE_SIZE/2;
250 else if (player.position.x - PLAYER_BASE_SIZE/2 < 0) player.position.x = 0 + PLAYER_BASE_SIZE/2;
251
252 // Player shot logic
253 if (IsKeyPressed(KEY_SPACE))
254 {
255 for (int i = 0; i < PLAYER_MAX_SHOOTS; i++)
256 {
257 if (!shoot[i].active)
258 {
259 shoot[i].position = (Vector2){ player.position.x, player.position.y - shipHeight };
260 shoot[i].speed.y = PLAYER_SPEED;
261 shoot[i].active = true;
262
263 linePosition = (Vector2){ player.position.x, player.position.y};
264
265 break;
266 }
267 }
268 }
269
270 // Shoot life timer
271 for (int i = 0; i < PLAYER_MAX_SHOOTS; i++)
272 {
273 if (shoot[i].active) shoot[i].lifeSpawn++;
274 }
275
276 // Shot logic
277 for (int i = 0; i < PLAYER_MAX_SHOOTS; i++)
278 {
279 if (shoot[i].active)
280 {
281 shoot[i].position.y -= shoot[i].speed.y;
282
283 // Shot vs walls collision logic
284 if ((shoot[i].position.x > screenWidth + shoot[i].radius) || (shoot[i].position.x < 0 - shoot[i].radius) ||
285 (shoot[i].position.y > screenHeight + shoot[i].radius) || (shoot[i].position.y < 0 - shoot[i].radius))
286 {
287 shoot[i].active = false;
288 shoot[i].lifeSpawn = 0;
289 }
290
291 // Player shot life spawn
292 if (shoot[i].lifeSpawn >= 120)
293 {
294 shoot[i].position = (Vector2){ 0.0f, 0.0f };
295 shoot[i].speed = (Vector2){ 0.0f, 0.0f };
296 shoot[i].lifeSpawn = 0;
297 shoot[i].active = false;
298 }
299 }
300 }
301
302 // Player vs meteors collision logic
303 player.collider = (Vector3){player.position.x, player.position.y - shipHeight/2, 12};
304
305 for (int i = 0; i < MAX_BIG_BALLS; i++)
306 {
307 if (CheckCollisionCircles((Vector2){ player.collider.x, player.collider.y }, player.collider.z, bigBalls[i].position, bigBalls[i].radius) && bigBalls[i].active)
308 {
309 gameOver = true;
310 }
311 }
312
313 for (int i = 0; i < MAX_BIG_BALLS*2; i++)
314 {
315 if (CheckCollisionCircles((Vector2){ player.collider.x, player.collider.y }, player.collider.z, mediumBalls[i].position, mediumBalls[i].radius) && mediumBalls[i].active)
316 {
317 gameOver = true;
318 }
319 }
320
321 for (int i = 0; i < MAX_BIG_BALLS*4; i++)
322 {
323 if (CheckCollisionCircles((Vector2){ player.collider.x, player.collider.y }, player.collider.z, smallBalls[i].position, smallBalls[i].radius) && smallBalls[i].active)
324 {
325 gameOver = true;
326 }
327 }
328
329 // Meteors logic (big)
330 for (int i = 0; i < MAX_BIG_BALLS; i++)
331 {
332 if (bigBalls[i].active)
333 {
334 // Meteor movement logic
335 bigBalls[i].position.x += bigBalls[i].speed.x;
336 bigBalls[i].position.y += bigBalls[i].speed.y;
337
338 // Meteor vs wall collision logic
339 if (((bigBalls[i].position.x + bigBalls[i].radius) >= screenWidth) || ((bigBalls[i].position.x - bigBalls[i].radius) <= 0)) bigBalls[i].speed.x *= -1;
340 if ((bigBalls[i].position.y - bigBalls[i].radius) <= 0) bigBalls[i].speed.y *= -1.5;
341
342 if ((bigBalls[i].position.y + bigBalls[i].radius) >= screenHeight)
343 {
344 bigBalls[i].speed.y *= -1;
345 bigBalls[i].position.y = screenHeight - bigBalls[i].radius;
346 }
347
348 bigBalls[i].speed.y += gravity;
349 }
350 }
351
352 // Meteors logic (medium)
353 for (int i = 0; i < MAX_BIG_BALLS*2; i++)
354 {
355 if (mediumBalls[i].active)
356 {
357 // Meteor movement logic
358 mediumBalls[i].position.x += mediumBalls[i].speed.x;
359 mediumBalls[i].position.y += mediumBalls[i].speed.y;
360
361 // Meteor vs wall collision logic
362 if (mediumBalls[i].position.x + mediumBalls[i].radius >= screenWidth || mediumBalls[i].position.x - mediumBalls[i].radius <= 0) mediumBalls[i].speed.x *= -1;
363 if (mediumBalls[i].position.y - mediumBalls[i].radius <= 0) mediumBalls[i].speed.y *= -1;
364 if (mediumBalls[i].position.y + mediumBalls[i].radius >= screenHeight)
365 {
366 mediumBalls[i].speed.y *= -1;
367 mediumBalls[i].position.y = screenHeight - mediumBalls[i].radius;
368 }
369
370 mediumBalls[i].speed.y += gravity + 0.12f;
371 }
372 }
373
374 // Meteors logic (small)
375 for (int i = 0; i < MAX_BIG_BALLS*4; i++)
376 {
377 if (smallBalls[i].active)
378 {
379 // Meteor movement logic
380 smallBalls[i].position.x += smallBalls[i].speed.x;
381 smallBalls[i].position.y += smallBalls[i].speed.y;
382
383 // Meteor vs wall collision logic
384 if (smallBalls[i].position.x + smallBalls[i].radius >= screenWidth || smallBalls[i].position.x - smallBalls[i].radius <= 0) smallBalls[i].speed.x *= -1;
385 if (smallBalls[i].position.y - smallBalls[i].radius <= 0) smallBalls[i].speed.y *= -1;
386 if (smallBalls[i].position.y + smallBalls[i].radius >= screenHeight)
387 {
388 smallBalls[i].speed.y *= -1;
389 smallBalls[i].position.y = screenHeight - smallBalls[i].radius;
390 }
391
392 smallBalls[i].speed.y += gravity + 0.25f;
393 }
394 }
395
396 // Player-shot vs meteors logic
397 for (int i = 0; i < PLAYER_MAX_SHOOTS; i++)
398 {
399 if ((shoot[i].active))
400 {
401 for (int a = 0; a < MAX_BIG_BALLS; a++)
402 {
403 if (bigBalls[a].active && (bigBalls[a].position.x - bigBalls[a].radius <= linePosition.x && bigBalls[a].position.x + bigBalls[a].radius >= linePosition.x)
404 && (bigBalls[a].position.y + bigBalls[a].radius >= shoot[i].position.y))
405 {
406 shoot[i].active = false;
407 shoot[i].lifeSpawn = 0;
408 bigBalls[a].active = false;
409 meteorsDestroyed++;
410 score += bigBalls[a].points;
411
412 for (int z = 0; z < 5; z++)
413 {
414 if (points[z].alpha == 0.0f)
415 {
416 points[z].position = bigBalls[a].position;
417 points[z].value = bigBalls[a].points;
418 points[z].alpha = 1.0f;
419 z = 5;
420 }
421 }
422
423 for (int j = 0; j < 2; j ++)
424 {
425 if ((countmediumBallss%2) == 0)
426 {
427 mediumBalls[countmediumBallss].position = (Vector2){bigBalls[a].position.x, bigBalls[a].position.y};
428 mediumBalls[countmediumBallss].speed = (Vector2){ -1*BALLS_SPEED, BALLS_SPEED };
429 }
430 else
431 {
432 mediumBalls[countmediumBallss].position = (Vector2){bigBalls[a].position.x, bigBalls[a].position.y};
433 mediumBalls[countmediumBallss].speed = (Vector2){ BALLS_SPEED, BALLS_SPEED };
434 }
435
436 mediumBalls[countmediumBallss].active = true;
437 countmediumBallss ++;
438 }
439
440 a = MAX_BIG_BALLS;
441 }
442 }
443 }
444
445 if ((shoot[i].active))
446 {
447 for (int b = 0; b < MAX_BIG_BALLS*2; b++)
448 {
449 if (mediumBalls[b].active && (mediumBalls[b].position.x - mediumBalls[b].radius <= linePosition.x && mediumBalls[b].position.x + mediumBalls[b].radius >= linePosition.x)
450 && (mediumBalls[b].position.y + mediumBalls[b].radius >= shoot[i].position.y))
451 {
452 shoot[i].active = false;
453 shoot[i].lifeSpawn = 0;
454 mediumBalls[b].active = false;
455 meteorsDestroyed++;
456 score += mediumBalls[b].points;
457
458 for (int z = 0; z < 5; z++)
459 {
460 if (points[z].alpha == 0.0f)
461 {
462 points[z].position = mediumBalls[b].position;
463 points[z].value = mediumBalls[b].points;
464 points[z].alpha = 1.0f;
465 z = 5;
466 }
467 }
468
469 for (int j = 0; j < 2; j ++)
470 {
471 if (countsmallBallss%2 == 0)
472 {
473 smallBalls[countsmallBallss].position = (Vector2){mediumBalls[b].position.x, mediumBalls[b].position.y};
474 smallBalls[countsmallBallss].speed = (Vector2){ BALLS_SPEED*-1, BALLS_SPEED*-1};
475 }
476 else
477 {
478 smallBalls[countsmallBallss].position = (Vector2){mediumBalls[b].position.x, mediumBalls[b].position.y};
479 smallBalls[countsmallBallss].speed = (Vector2){ BALLS_SPEED, BALLS_SPEED*-1};
480 }
481
482 smallBalls[countsmallBallss].active = true;
483 countsmallBallss ++;
484 }
485
486 b = MAX_BIG_BALLS*2;
487 }
488 }
489 }
490
491 if ((shoot[i].active))
492 {
493 for (int c = 0; c < MAX_BIG_BALLS*4; c++)
494 {
495 if (smallBalls[c].active && (smallBalls[c].position.x - smallBalls[c].radius <= linePosition.x && smallBalls[c].position.x + smallBalls[c].radius >= linePosition.x)
496 && (smallBalls[c].position.y + smallBalls[c].radius >= shoot[i].position.y))
497 {
498 shoot[i].active = false;
499 shoot[i].lifeSpawn = 0;
500 smallBalls[c].active = false;
501 meteorsDestroyed++;
502 score += smallBalls[c].points;
503
504 for (int z = 0; z < 5; z++)
505 {
506 if (points[z].alpha == 0.0f)
507 {
508 points[z].position = smallBalls[c].position;
509 points[z].value = smallBalls[c].points;
510 points[z].alpha = 1.0f;
511 z = 5;
512 }
513 }
514
515 c = MAX_BIG_BALLS*4;
516 }
517 }
518 }
519 }
520
521 if (meteorsDestroyed == (MAX_BIG_BALLS + MAX_BIG_BALLS*2 + MAX_BIG_BALLS*4)) victory = true;
522 }
523 }
524 else
525 {
526 if (IsKeyPressed(KEY_ENTER))
527 {
528 InitGame();
529 gameOver = false;
530 }
531 }
532
533 // Points move-up and fade logic
534 for (int z = 0; z < 5; z++)
535 {
536 if (points[z].alpha > 0.0f)
537 {
538 points[z].position.y -= 2;
539 points[z].alpha -= 0.02f;
540 }
541
542 if (points[z].alpha < 0.0f) points[z].alpha = 0.0f;
543 }
544}
545
546// Draw game (one frame)
547void DrawGame(void)
548{
549 BeginDrawing();
550
551 ClearBackground(RAYWHITE);
552
553 if (!gameOver)
554 {
555 // Draw player
556 Vector2 v1 = { player.position.x + sinf(player.rotation*DEG2RAD)*(shipHeight), player.position.y - cosf(player.rotation*DEG2RAD)*(shipHeight) };
557 Vector2 v2 = { player.position.x - cosf(player.rotation*DEG2RAD)*(PLAYER_BASE_SIZE/2), player.position.y - sinf(player.rotation*DEG2RAD)*(PLAYER_BASE_SIZE/2) };
558 Vector2 v3 = { player.position.x + cosf(player.rotation*DEG2RAD)*(PLAYER_BASE_SIZE/2), player.position.y + sinf(player.rotation*DEG2RAD)*(PLAYER_BASE_SIZE/2) };
559 DrawTriangle(v1, v2, v3, MAROON);
560
561 // Draw meteors (big)
562 for (int i = 0;i < MAX_BIG_BALLS; i++)
563 {
564 if (bigBalls[i].active) DrawCircleV(bigBalls[i].position, bigBalls[i].radius, DARKGRAY);
565 else DrawCircleV(bigBalls[i].position, bigBalls[i].radius, Fade(LIGHTGRAY, 0.3f));
566 }
567
568 // Draw meteors (medium)
569 for (int i = 0;i < MAX_BIG_BALLS*2; i++)
570 {
571 if (mediumBalls[i].active) DrawCircleV(mediumBalls[i].position, mediumBalls[i].radius, GRAY);
572 else DrawCircleV(mediumBalls[i].position, mediumBalls[i].radius, Fade(LIGHTGRAY, 0.3f));
573 }
574
575 // Draw meteors (small)
576 for (int i = 0;i < MAX_BIG_BALLS*4; i++)
577 {
578 if (smallBalls[i].active) DrawCircleV(smallBalls[i].position, smallBalls[i].radius, GRAY);
579 else DrawCircleV(smallBalls[i].position, smallBalls[i].radius, Fade(LIGHTGRAY, 0.3f));
580 }
581
582 // Draw shoot
583 for (int i = 0; i < PLAYER_MAX_SHOOTS; i++)
584 {
585 if (shoot[i].active) DrawLine(linePosition.x, linePosition.y, shoot[i].position.x, shoot[i].position.y, RED);
586 }
587
588 // Draw score points
589 for (int z = 0; z < 5; z++)
590 {
591 if (points[z].alpha > 0.0f)
592 {
593 DrawText(TextFormat("+%02i", points[z].value), points[z].position.x, points[z].position.y, 20, Fade(BLUE, points[z].alpha));
594 }
595 }
596
597 // Draw score (UI)
598 DrawText(TextFormat("SCORE: %i", score), 10, 10, 20, LIGHTGRAY);
599
600 if (victory)
601 {
602 DrawText("YOU WIN!", screenWidth/2 - MeasureText("YOU WIN!", 60)/2, 100, 60, LIGHTGRAY);
603 DrawText("PRESS [ENTER] TO PLAY AGAIN", GetScreenWidth()/2 - MeasureText("PRESS [ENTER] TO PLAY AGAIN", 20)/2, GetScreenHeight()/2 - 50, 20, LIGHTGRAY);
604 }
605
606 if (pause) DrawText("GAME PAUSED", screenWidth/2 - MeasureText("GAME PAUSED", 40)/2, screenHeight/2 - 40, 40, LIGHTGRAY);
607 }
608 else DrawText("PRESS [ENTER] TO PLAY AGAIN", GetScreenWidth()/2 - MeasureText("PRESS [ENTER] TO PLAY AGAIN", 20)/2, GetScreenHeight()/2 - 50, 20, LIGHTGRAY);
609
610 EndDrawing();
611}
612
613// Unload game variables
614void UnloadGame(void)
615{
616 // TODO: Unload all dynamic loaded data (textures, sounds, models...)
617}
618
619// Update and Draw (one frame)
620void UpdateDrawFrame(void)
621{
622 UpdateGame();
623 DrawGame();
624}
625