1/*******************************************************************************************
2*
3* raylib - sample game: gorilas
4*
5* Sample game Marc Palau 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 <stdio.h>
17#include <stdlib.h>
18#include <time.h>
19#include <math.h>
20
21#if defined(PLATFORM_WEB)
22 #include <emscripten/emscripten.h>
23#endif
24
25//----------------------------------------------------------------------------------
26// Some Defines
27//----------------------------------------------------------------------------------
28#define MAX_BUILDINGS 15
29#define MAX_EXPLOSIONS 200
30#define MAX_PLAYERS 2
31
32#define BUILDING_RELATIVE_ERROR 30 // Building size random range %
33#define BUILDING_MIN_RELATIVE_HEIGHT 20 // Minimum height in % of the screenHeight
34#define BUILDING_MAX_RELATIVE_HEIGHT 60 // Maximum height in % of the screenHeight
35#define BUILDING_MIN_GRAYSCALE_COLOR 120 // Minimum gray color for the buildings
36#define BUILDING_MAX_GRAYSCALE_COLOR 200 // Maximum gray color for the buildings
37
38#define MIN_PLAYER_POSITION 5 // Minimum x position %
39#define MAX_PLAYER_POSITION 20 // Maximum x position %
40
41#define GRAVITY 9.81f
42#define DELTA_FPS 60
43
44//----------------------------------------------------------------------------------
45// Types and Structures Definition
46//----------------------------------------------------------------------------------
47typedef struct Player {
48 Vector2 position;
49 Vector2 size;
50
51 Vector2 aimingPoint;
52 int aimingAngle;
53 int aimingPower;
54
55 Vector2 previousPoint;
56 int previousAngle;
57 int previousPower;
58
59 Vector2 impactPoint;
60
61 bool isLeftTeam; // This player belongs to the left or to the right team
62 bool isPlayer; // If is a player or an AI
63 bool isAlive;
64} Player;
65
66typedef struct Building {
67 Rectangle rectangle;
68 Color color;
69} Building;
70
71typedef struct Explosion {
72 Vector2 position;
73 int radius;
74 bool active;
75} Explosion;
76
77typedef struct Ball {
78 Vector2 position;
79 Vector2 speed;
80 int radius;
81 bool active;
82} Ball;
83
84//------------------------------------------------------------------------------------
85// Global Variables Declaration
86//------------------------------------------------------------------------------------
87static const int screenWidth = 800;
88static const int screenHeight = 450;
89
90static bool gameOver = false;
91static bool pause = false;
92
93static Player player[MAX_PLAYERS] = { 0 };
94static Building building[MAX_BUILDINGS] = { 0 };
95static Explosion explosion[MAX_EXPLOSIONS] = { 0 };
96static Ball ball = { 0 };
97
98static int playerTurn = 0;
99static bool ballOnAir = false;
100
101//------------------------------------------------------------------------------------
102// Module Functions Declaration (local)
103//------------------------------------------------------------------------------------
104static void InitGame(void); // Initialize game
105static void UpdateGame(void); // Update game (one frame)
106static void DrawGame(void); // Draw game (one frame)
107static void UnloadGame(void); // Unload game
108static void UpdateDrawFrame(void); // Update and Draw (one frame)
109
110// Additional module functions
111static void InitBuildings(void);
112static void InitPlayers(void);
113static bool UpdatePlayer(int playerTurn);
114static bool UpdateBall(int playerTurn);
115
116//------------------------------------------------------------------------------------
117// Program main entry point
118//------------------------------------------------------------------------------------
119int main(void)
120{
121 // Initialization (Note windowTitle is unused on Android)
122 //---------------------------------------------------------
123 InitWindow(screenWidth, screenHeight, "sample game: gorilas");
124
125 InitGame();
126
127#if defined(PLATFORM_WEB)
128 emscripten_set_main_loop(UpdateDrawFrame, 0, 1);
129#else
130
131 SetTargetFPS(60);
132 //--------------------------------------------------------------------------------------
133
134 // Main game loop
135 while (!WindowShouldClose()) // Detect window close button or ESC key
136 {
137 // Update and Draw
138 //----------------------------------------------------------------------------------
139 UpdateDrawFrame();
140 //----------------------------------------------------------------------------------
141 }
142#endif
143
144 // De-Initialization
145 //--------------------------------------------------------------------------------------
146 UnloadGame(); // Unload loaded data (textures, sounds, models...)
147
148 CloseWindow(); // Close window and OpenGL context
149 //--------------------------------------------------------------------------------------
150
151 return 0;
152}
153
154//------------------------------------------------------------------------------------
155// Module Functions Definitions (local)
156//------------------------------------------------------------------------------------
157
158// Initialize game variables
159void InitGame(void)
160{
161 // Init shoot
162 ball.radius = 10;
163 ballOnAir = false;
164 ball.active = false;
165
166 InitBuildings();
167 InitPlayers();
168
169 // Init explosions
170 for (int i = 0; i < MAX_EXPLOSIONS; i++)
171 {
172 explosion[i].position = (Vector2){ 0.0f, 0.0f };
173 explosion[i].radius = 30;
174 explosion[i].active = false;
175 }
176}
177
178// Update game (one frame)
179void UpdateGame(void)
180{
181 if (!gameOver)
182 {
183 if (IsKeyPressed('P')) pause = !pause;
184
185 if (!pause)
186 {
187 if (!ballOnAir) ballOnAir = UpdatePlayer(playerTurn); // If we are aiming
188 else
189 {
190 if (UpdateBall(playerTurn)) // If collision
191 {
192 // Game over logic
193 bool leftTeamAlive = false;
194 bool rightTeamAlive = false;
195
196 for (int i = 0; i < MAX_PLAYERS; i++)
197 {
198 if (player[i].isAlive)
199 {
200 if (player[i].isLeftTeam) leftTeamAlive = true;
201 if (!player[i].isLeftTeam) rightTeamAlive = true;
202 }
203 }
204
205 if (leftTeamAlive && rightTeamAlive)
206 {
207 ballOnAir = false;
208 ball.active = false;
209
210 playerTurn++;
211
212 if (playerTurn == MAX_PLAYERS) playerTurn = 0;
213 }
214 else
215 {
216 gameOver = true;
217
218 // if (leftTeamAlive) left team wins
219 // if (rightTeamAlive) right team wins
220 }
221 }
222 }
223 }
224 }
225 else
226 {
227 if (IsKeyPressed(KEY_ENTER))
228 {
229 InitGame();
230 gameOver = false;
231 }
232 }
233}
234
235// Draw game (one frame)
236void DrawGame(void)
237{
238 BeginDrawing();
239
240 ClearBackground(RAYWHITE);
241
242 if (!gameOver)
243 {
244 // Draw buildings
245 for (int i = 0; i < MAX_BUILDINGS; i++) DrawRectangleRec(building[i].rectangle, building[i].color);
246
247 // Draw explosions
248 for (int i = 0; i < MAX_EXPLOSIONS; i++)
249 {
250 if (explosion[i].active) DrawCircle(explosion[i].position.x, explosion[i].position.y, explosion[i].radius, RAYWHITE);
251 }
252
253 // Draw players
254 for (int i = 0; i < MAX_PLAYERS; i++)
255 {
256 if (player[i].isAlive)
257 {
258 if (player[i].isLeftTeam) DrawRectangle(player[i].position.x - player[i].size.x/2, player[i].position.y - player[i].size.y/2,
259 player[i].size.x, player[i].size.y, BLUE);
260 else DrawRectangle(player[i].position.x - player[i].size.x/2, player[i].position.y - player[i].size.y/2,
261 player[i].size.x, player[i].size.y, RED);
262 }
263 }
264
265 // Draw ball
266 if (ball.active) DrawCircle(ball.position.x, ball.position.y, ball.radius, MAROON);
267
268 // Draw the angle and the power of the aim, and the previous ones
269 if (!ballOnAir)
270 {
271 // Draw shot information
272 /*
273 if (player[playerTurn].isLeftTeam)
274 {
275 DrawText(TextFormat("Previous Point %i, %i", (int)player[playerTurn].previousPoint.x, (int)player[playerTurn].previousPoint.y), 20, 20, 20, DARKBLUE);
276 DrawText(TextFormat("Previous Angle %i", player[playerTurn].previousAngle), 20, 50, 20, DARKBLUE);
277 DrawText(TextFormat("Previous Power %i", player[playerTurn].previousPower), 20, 80, 20, DARKBLUE);
278 DrawText(TextFormat("Aiming Point %i, %i", (int)player[playerTurn].aimingPoint.x, (int)player[playerTurn].aimingPoint.y), 20, 110, 20, DARKBLUE);
279 DrawText(TextFormat("Aiming Angle %i", player[playerTurn].aimingAngle), 20, 140, 20, DARKBLUE);
280 DrawText(TextFormat("Aiming Power %i", player[playerTurn].aimingPower), 20, 170, 20, DARKBLUE);
281 }
282 else
283 {
284 DrawText(TextFormat("Previous Point %i, %i", (int)player[playerTurn].previousPoint.x, (int)player[playerTurn].previousPoint.y), screenWidth*3/4, 20, 20, DARKBLUE);
285 DrawText(TextFormat("Previous Angle %i", player[playerTurn].previousAngle), screenWidth*3/4, 50, 20, DARKBLUE);
286 DrawText(TextFormat("Previous Power %i", player[playerTurn].previousPower), screenWidth*3/4, 80, 20, DARKBLUE);
287 DrawText(TextFormat("Aiming Point %i, %i", (int)player[playerTurn].aimingPoint.x, (int)player[playerTurn].aimingPoint.y), screenWidth*3/4, 110, 20, DARKBLUE);
288 DrawText(TextFormat("Aiming Angle %i", player[playerTurn].aimingAngle), screenWidth*3/4, 140, 20, DARKBLUE);
289 DrawText(TextFormat("Aiming Power %i", player[playerTurn].aimingPower), screenWidth*3/4, 170, 20, DARKBLUE);
290 }
291 */
292
293 // Draw aim
294 if (player[playerTurn].isLeftTeam)
295 {
296 // Previous aiming
297 DrawTriangle((Vector2){ player[playerTurn].position.x - player[playerTurn].size.x/4, player[playerTurn].position.y - player[playerTurn].size.y/4 },
298 (Vector2){ player[playerTurn].position.x + player[playerTurn].size.x/4, player[playerTurn].position.y + player[playerTurn].size.y/4 },
299 player[playerTurn].previousPoint, GRAY);
300
301 // Actual aiming
302 DrawTriangle((Vector2){ player[playerTurn].position.x - player[playerTurn].size.x/4, player[playerTurn].position.y - player[playerTurn].size.y/4 },
303 (Vector2){ player[playerTurn].position.x + player[playerTurn].size.x/4, player[playerTurn].position.y + player[playerTurn].size.y/4 },
304 player[playerTurn].aimingPoint, DARKBLUE);
305 }
306 else
307 {
308 // Previous aiming
309 DrawTriangle((Vector2){ player[playerTurn].position.x - player[playerTurn].size.x/4, player[playerTurn].position.y + player[playerTurn].size.y/4 },
310 (Vector2){ player[playerTurn].position.x + player[playerTurn].size.x/4, player[playerTurn].position.y - player[playerTurn].size.y/4 },
311 player[playerTurn].previousPoint, GRAY);
312
313 // Actual aiming
314 DrawTriangle((Vector2){ player[playerTurn].position.x - player[playerTurn].size.x/4, player[playerTurn].position.y + player[playerTurn].size.y/4 },
315 (Vector2){ player[playerTurn].position.x + player[playerTurn].size.x/4, player[playerTurn].position.y - player[playerTurn].size.y/4 },
316 player[playerTurn].aimingPoint, MAROON);
317 }
318 }
319
320 if (pause) DrawText("GAME PAUSED", screenWidth/2 - MeasureText("GAME PAUSED", 40)/2, screenHeight/2 - 40, 40, GRAY);
321 }
322 else DrawText("PRESS [ENTER] TO PLAY AGAIN", GetScreenWidth()/2 - MeasureText("PRESS [ENTER] TO PLAY AGAIN", 20)/2, GetScreenHeight()/2 - 50, 20, GRAY);
323
324 EndDrawing();
325}
326
327// Unload game variables
328void UnloadGame(void)
329{
330 // TODO: Unload all dynamic loaded data (textures, sounds, models...)
331}
332
333// Update and Draw (one frame)
334void UpdateDrawFrame(void)
335{
336 UpdateGame();
337 DrawGame();
338}
339
340//--------------------------------------------------------------------------------------
341// Additional module functions
342//--------------------------------------------------------------------------------------
343static void InitBuildings(void)
344{
345 // Horizontal generation
346 int currentWidth = 0;
347
348 // We make sure the absolute error randomly generated for each building, has as a minimum value the screenWidth.
349 // This way all the screen will be filled with buildings. Each building will have a different, random width.
350
351 float relativeWidth = 100/(100 - BUILDING_RELATIVE_ERROR);
352 float buildingWidthMean = (screenWidth*relativeWidth/MAX_BUILDINGS) + 1; // We add one to make sure we will cover the whole screen.
353
354 // Vertical generation
355 int currentHeighth = 0;
356 int grayLevel;
357
358 // Creation
359 for (int i = 0; i < MAX_BUILDINGS; i++)
360 {
361 // Horizontal
362 building[i].rectangle.x = currentWidth;
363 building[i].rectangle.width = GetRandomValue(buildingWidthMean*(100 - BUILDING_RELATIVE_ERROR/2)/100 + 1, buildingWidthMean*(100 + BUILDING_RELATIVE_ERROR)/100);
364
365 currentWidth += building[i].rectangle.width;
366
367 // Vertical
368 currentHeighth = GetRandomValue(BUILDING_MIN_RELATIVE_HEIGHT, BUILDING_MAX_RELATIVE_HEIGHT);
369 building[i].rectangle.y = screenHeight - (screenHeight*currentHeighth/100);
370 building[i].rectangle.height = screenHeight*currentHeighth/100 + 1;
371
372 // Color
373 grayLevel = GetRandomValue(BUILDING_MIN_GRAYSCALE_COLOR, BUILDING_MAX_GRAYSCALE_COLOR);
374 building[i].color = (Color){ grayLevel, grayLevel, grayLevel, 255 };
375 }
376}
377
378static void InitPlayers(void)
379{
380 for (int i = 0; i < MAX_PLAYERS; i++)
381 {
382 player[i].isAlive = true;
383
384 // Decide the team of this player
385 if (i % 2 == 0) player[i].isLeftTeam = true;
386 else player[i].isLeftTeam = false;
387
388 // Now there is no AI
389 player[i].isPlayer = true;
390
391 // Set size, by default by now
392 player[i].size = (Vector2){ 40, 40 };
393
394 // Set position
395 if (player[i].isLeftTeam) player[i].position.x = GetRandomValue(screenWidth*MIN_PLAYER_POSITION/100, screenWidth*MAX_PLAYER_POSITION/100);
396 else player[i].position.x = screenWidth - GetRandomValue(screenWidth*MIN_PLAYER_POSITION/100, screenWidth*MAX_PLAYER_POSITION/100);
397
398 for (int j = 0; j < MAX_BUILDINGS; j++)
399 {
400 if (building[j].rectangle.x > player[i].position.x)
401 {
402 // Set the player in the center of the building
403 player[i].position.x = building[j-1].rectangle.x + building[j-1].rectangle.width/2;
404 // Set the player at the top of the building
405 player[i].position.y = building[j-1].rectangle.y - player[i].size.y/2;
406 break;
407 }
408 }
409
410 // Set statistics to 0
411 player[i].aimingPoint = player[i].position;
412 player[i].previousAngle = 0;
413 player[i].previousPower = 0;
414 player[i].previousPoint = player[i].position;
415 player[i].aimingAngle = 0;
416 player[i].aimingPower = 0;
417
418 player[i].impactPoint = (Vector2){ -100, -100 };
419 }
420}
421
422static bool UpdatePlayer(int playerTurn)
423{
424 // If we are aiming at the firing quadrant, we calculate the angle
425 if (GetMousePosition().y <= player[playerTurn].position.y)
426 {
427 // Left team
428 if (player[playerTurn].isLeftTeam && GetMousePosition().x >= player[playerTurn].position.x)
429 {
430 // Distance (calculating the fire power)
431 player[playerTurn].aimingPower = sqrt(pow(player[playerTurn].position.x - GetMousePosition().x, 2) + pow(player[playerTurn].position.y - GetMousePosition().y, 2));
432 // Calculates the angle via arcsin
433 player[playerTurn].aimingAngle = asin((player[playerTurn].position.y - GetMousePosition().y)/player[playerTurn].aimingPower)*RAD2DEG;
434 // Point of the screen we are aiming at
435 player[playerTurn].aimingPoint = GetMousePosition();
436
437 // Ball fired
438 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
439 {
440 player[playerTurn].previousPoint = player[playerTurn].aimingPoint;
441 player[playerTurn].previousPower = player[playerTurn].aimingPower;
442 player[playerTurn].previousAngle = player[playerTurn].aimingAngle;
443 ball.position = player[playerTurn].position;
444
445 return true;
446 }
447 }
448 // Right team
449 else if (!player[playerTurn].isLeftTeam && GetMousePosition().x <= player[playerTurn].position.x)
450 {
451 // Distance (calculating the fire power)
452 player[playerTurn].aimingPower = sqrt(pow(player[playerTurn].position.x - GetMousePosition().x, 2) + pow(player[playerTurn].position.y - GetMousePosition().y, 2));
453 // Calculates the angle via arcsin
454 player[playerTurn].aimingAngle = asin((player[playerTurn].position.y - GetMousePosition().y)/player[playerTurn].aimingPower)*RAD2DEG;
455 // Point of the screen we are aiming at
456 player[playerTurn].aimingPoint = GetMousePosition();
457
458 // Ball fired
459 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
460 {
461 player[playerTurn].previousPoint = player[playerTurn].aimingPoint;
462 player[playerTurn].previousPower = player[playerTurn].aimingPower;
463 player[playerTurn].previousAngle = player[playerTurn].aimingAngle;
464 ball.position = player[playerTurn].position;
465
466 return true;
467 }
468 }
469 else
470 {
471 player[playerTurn].aimingPoint = player[playerTurn].position;
472 player[playerTurn].aimingPower = 0;
473 player[playerTurn].aimingAngle = 0;
474 }
475 }
476 else
477 {
478 player[playerTurn].aimingPoint = player[playerTurn].position;
479 player[playerTurn].aimingPower = 0;
480 player[playerTurn].aimingAngle = 0;
481 }
482
483 return false;
484}
485
486static bool UpdateBall(int playerTurn)
487{
488 static int explosionNumber = 0;
489
490 // Activate ball
491 if (!ball.active)
492 {
493 if (player[playerTurn].isLeftTeam)
494 {
495 ball.speed.x = cos(player[playerTurn].previousAngle*DEG2RAD)*player[playerTurn].previousPower*3/DELTA_FPS;
496 ball.speed.y = -sin(player[playerTurn].previousAngle*DEG2RAD)*player[playerTurn].previousPower*3/DELTA_FPS;
497 ball.active = true;
498 }
499 else
500 {
501 ball.speed.x = -cos(player[playerTurn].previousAngle*DEG2RAD)*player[playerTurn].previousPower*3/DELTA_FPS;
502 ball.speed.y = -sin(player[playerTurn].previousAngle*DEG2RAD)*player[playerTurn].previousPower*3/DELTA_FPS;
503 ball.active = true;
504 }
505 }
506
507 ball.position.x += ball.speed.x;
508 ball.position.y += ball.speed.y;
509 ball.speed.y += GRAVITY/DELTA_FPS;
510
511 // Collision
512 if (ball.position.x + ball.radius < 0) return true;
513 else if (ball.position.x - ball.radius > screenWidth) return true;
514 else
515 {
516 // Player collision
517 for (int i = 0; i < MAX_PLAYERS; i++)
518 {
519 if (CheckCollisionCircleRec(ball.position, ball.radius, (Rectangle){ player[i].position.x - player[i].size.x/2, player[i].position.y - player[i].size.y/2,
520 player[i].size.x, player[i].size.y }))
521 {
522 // We can't hit ourselves
523 if (i == playerTurn) return false;
524 else
525 {
526 // We set the impact point
527 player[playerTurn].impactPoint.x = ball.position.x;
528 player[playerTurn].impactPoint.y = ball.position.y + ball.radius;
529
530 // We destroy the player
531 player[i].isAlive = false;
532 return true;
533 }
534 }
535 }
536
537 // Building collision
538 // NOTE: We only check building collision if we are not inside an explosion
539 for (int i = 0; i < MAX_BUILDINGS; i++)
540 {
541 if (CheckCollisionCircles(ball.position, ball.radius, explosion[i].position, explosion[i].radius - ball.radius))
542 {
543 return false;
544 }
545 }
546
547 for (int i = 0; i < MAX_BUILDINGS; i++)
548 {
549 if (CheckCollisionCircleRec(ball.position, ball.radius, building[i].rectangle))
550 {
551 // We set the impact point
552 player[playerTurn].impactPoint.x = ball.position.x;
553 player[playerTurn].impactPoint.y = ball.position.y + ball.radius;
554
555 // We create an explosion
556 explosion[explosionNumber].position = player[playerTurn].impactPoint;
557 explosion[explosionNumber].active = true;
558 explosionNumber++;
559
560 return true;
561 }
562 }
563 }
564
565 return false;
566}
567