Browse Source

GAME OVER screen.

antirez 3 years ago
parent
commit
9bb3f4c80f
1 changed files with 114 additions and 60 deletions
  1. 114 60
      app.c

+ 114 - 60
app.c

@@ -54,7 +54,7 @@ typedef struct AsteroidsApp {
 
     /* Game state. */
     int running;            /* Once false exists the app. */
-    int gameover;           /* Gameover status. */
+    bool gameover;          /* Gameover status. */
     uint32_t ticks;         /* Game ticks. Increments at each refresh. */
     uint32_t score;         /* Game score. */
     uint32_t lives;         /* Number of lives in the current game. */
@@ -81,6 +81,13 @@ typedef struct AsteroidsApp {
     bool fire;                 /* Short press detected: fire a bullet. */
 } AsteroidsApp;
 
+/* ============================== Prototyeps ================================ */
+
+// Only functions called before their definition are here.
+
+void restart_game_after_gameover(AsteroidsApp *app);
+uint32_t key_pressed_time(AsteroidsApp *app, InputKey key);
+
 /* ============================ 2D drawing ================================== */
 
 /* This structure represents a polygon of at most POLY_MAX points.
@@ -215,7 +222,7 @@ void render_callback(Canvas *const canvas, void *ctx) {
 
     /* Clear screen. */
     canvas_set_color(canvas, ColorWhite);
-    canvas_draw_box(canvas, 0, 0, 127, 63);
+    canvas_draw_box(canvas, 0, 0, SCREEN_XRES-1, SCREEN_YRES-1);
 
     /* Draw score. */
     canvas_set_color(canvas, ColorBlack);
@@ -235,6 +242,15 @@ void render_callback(Canvas *const canvas, void *ctx) {
 
     for (int j = 0; j < app->asteroids_num; j++)
         draw_asteroid(canvas,&app->asteroids[j]);
+
+    /* Game over text. */
+    if (app->gameover) {
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str(canvas, 28, 35, "GAME   OVER");
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str(canvas, 25, 50, "Press OK to restart");
+    }
 }
 
 /* ============================ Game logic ================================== */
@@ -256,9 +272,9 @@ float distance(float x1, float y1, float x2, float y2) {
  * spheres (this is why this function only takes the radius). This
  * is, after all, kinda accurate for asteroids, for bullets, and
  * even for the ship "core" itself. */
-bool detect_collision(float x1, float y1, float r1,
-                      float x2, float y2, float r2,
-                      float factor)
+bool objects_are_colliding(float x1, float y1, float r1,
+                           float x2, float y2, float r2,
+                           float factor)
 {
     /* The objects are colliding if the distance between object 1 and 2
      * is smaller than the sum of the two radiuses r1 and r2.
@@ -372,14 +388,24 @@ void asteroid_was_hit(AsteroidsApp *app, int id) {
     }
 }
 
+/* Set gameover state. When in game-over mode, the game displays a gameover
+ * text with a background of many asteroids floating around. */
+void game_over(AsteroidsApp *app) {
+    restart_game_after_gameover(app);
+    app->gameover = true;
+    int asteroids = 8;
+    while(asteroids-- && add_asteroid(app) != NULL);
+}
+
 /* Function called when a collision between the asteroid and the
  * ship is detected. */
 void ship_was_hit(AsteroidsApp *app) {
     app->ship_hit = SHIP_HIT_ANIMATION_LEN;
-    if (app->lives)
+    if (app->lives) {
         app->lives--;
-    else
-        app->gameover = true;
+    } else {
+        game_over(app);
+    }
 }
 
 /* Restart game after the ship is hit. Will reset the ship position, bullets
@@ -398,55 +424,16 @@ void restart_game(AsteroidsApp *app) {
 /* Called after gameover to restart the game. This function
  * also calls restart_game(). */
 void restart_game_after_gameover(AsteroidsApp *app) {
-    app->gameover = 0;
+    app->gameover = false;
     app->ticks = 0;
     app->score = 0;
+    app->ship_hit = 0;
     app->lives = GAME_START_LIVES;
     restart_game(app);
 }
 
-/* This is the main game execution function, called 10 times for
- * second (with the Flipper screen latency, an higher FPS does not
- * make sense). In this function we update the position of objects based
- * on velocity. Detect collisions. Update the score and so forth.
- *
- * Each time this function is called, app->tick is incremented. */
-void game_tick(void *ctx) {
-    AsteroidsApp *app = ctx;
-
-    /* This is a special condition: ship was hit, we frozen the game
-     * as long as ship_hit isn't zero again, and show an animation of
-     * a rotating ship. */
-    if (app->ship_hit) {
-        app->ship.rot += 0.5;
-        app->ship_hit--;
-        view_port_update(app->view_port);
-        if (app->ship_hit == 0) {
-            restart_game(app);
-        }
-        return;
-    }
-
-    /* Handle keypresses. */
-    if (app->pressed[InputKeyLeft]) app->ship.rot -= .35;
-    if (app->pressed[InputKeyRight]) app->ship.rot += .35;
-    if (app->pressed[InputKeyOk]) {
-        app->ship.vx -= 0.5*(float)sin(app->ship.rot);
-        app->ship.vy += 0.5*(float)cos(app->ship.rot);
-    }
-
-    /* Fire a bullet if needed. app->fire is set in
-     * asteroids_update_keypress_state() since depends on exact
-     * pressure timing. */
-    if (app->fire) {
-        ship_fire_bullet(app);
-        app->fire = false;
-    }
-
-    /* Update ship position according to its velocity. */
-    update_pos_by_velocity(&app->ship.x,&app->ship.y,app->ship.vx,app->ship.vy);
-
-    /* Update bullets position. */
+/* Move bullets. */
+void update_bullets_position(AsteroidsApp *app) {
     for (int j = 0; j < app->bullets_num; j++) {
         update_pos_by_velocity(&app->bullets[j].x,&app->bullets[j].y,
                                app->bullets[j].vx,app->bullets[j].vy);
@@ -456,8 +443,10 @@ void game_tick(void *ctx) {
                     fill it with the top bullet to take the array dense. */
         }
     }
+}
 
-    /* Update asteroids position. */
+/* Move asteroids. */
+void update_asteroids_position(AsteroidsApp *app) {
     for (int j = 0; j < app->asteroids_num; j++) {
         update_pos_by_velocity(&app->asteroids[j].x,&app->asteroids[j].y,
                                app->asteroids[j].vx,app->asteroids[j].vy);
@@ -465,14 +454,17 @@ void game_tick(void *ctx) {
         if (app->asteroids[j].rot < 0) app->asteroids[j].rot = 2*PI;
         else if (app->asteroids[j].rot > 2*PI) app->asteroids[j].rot = 0;
     }
+}
 
+/* Collision detection and game state update based on collisions. */
+void detect_collisions(AsteroidsApp *app) {
     /* Detect collision between bullet and asteroid. */
     for (int j = 0; j < app->bullets_num; j++) {
         Bullet *b = &app->bullets[j];
         for (int i = 0; i < app->asteroids_num; i++) {
             Asteroid *a = &app->asteroids[i];
-            if (detect_collision(a->x, a->y, a->size,
-                                 b->x, b->y, 1, 1))
+            if (objects_are_colliding(a->x, a->y, a->size,
+                                      b->x, b->y, 1, 1))
             {
                 asteroid_was_hit(app,i);
                 remove_bullet(app,j);
@@ -489,13 +481,69 @@ void game_tick(void *ctx) {
     /* Detect collision between ship and asteroid. */
     for (int j = 0; j < app->asteroids_num; j++) {
         Asteroid *a = &app->asteroids[j];
-        if (detect_collision(a->x, a->y, a->size,
-                             app->ship.x, app->ship.y, 4, 1))
+        if (objects_are_colliding(a->x, a->y, a->size,
+                                  app->ship.x, app->ship.y, 4, 1))
         {
             ship_was_hit(app);
             break;
         }
     }
+}
+
+/* This is the main game execution function, called 10 times for
+ * second (with the Flipper screen latency, an higher FPS does not
+ * make sense). In this function we update the position of objects based
+ * on velocity. Detect collisions. Update the score and so forth.
+ *
+ * Each time this function is called, app->tick is incremented. */
+void game_tick(void *ctx) {
+    AsteroidsApp *app = ctx;
+
+    /* There are two special screens:
+     *
+     * 1. Ship was hit, we frozen the game as long as ship_hit isn't zero
+     * again, and show an animation of a rotating ship. */
+    if (app->ship_hit) {
+        app->ship.rot += 0.5;
+        app->ship_hit--;
+        view_port_update(app->view_port);
+        if (app->ship_hit == 0) {
+            restart_game(app);
+        }
+        return;
+    } else if (app->gameover) {
+    /* 2. Game over. We need to update only background asteroids. In this
+     * state the game just displays a GAME OVER text with the floating
+     * asteroids in backgroud. */
+        if (key_pressed_time(app,InputKeyOk) > 100) {
+            restart_game_after_gameover(app);
+        }
+        update_asteroids_position(app);
+        view_port_update(app->view_port);
+        return;
+    }
+
+    /* Handle keypresses. */
+    if (app->pressed[InputKeyLeft]) app->ship.rot -= .35;
+    if (app->pressed[InputKeyRight]) app->ship.rot += .35;
+    if (app->pressed[InputKeyOk]) {
+        app->ship.vx -= 0.5*(float)sin(app->ship.rot);
+        app->ship.vy += 0.5*(float)cos(app->ship.rot);
+    }
+
+    /* Fire a bullet if needed. app->fire is set in
+     * asteroids_update_keypress_state() since depends on exact
+     * pressure timing. */
+    if (app->fire) {
+        ship_fire_bullet(app);
+        app->fire = false;
+    }
+
+    /* Update positions and detect collisions. */
+    update_pos_by_velocity(&app->ship.x,&app->ship.y,app->ship.vx,app->ship.vy);
+    update_bullets_position(app);
+    update_asteroids_position(app);
+    detect_collisions(app);
 
     /* From time to time, create a new asteroid. The more asteroids
      * already on the screen, the smaller probability of creating
@@ -534,6 +582,7 @@ AsteroidsApp* asteroids_app_alloc() {
 
     app->running = 1;       /* Turns 0 when back is pressed. */
     restart_game_after_gameover(app);
+    game_over(app);
     memset(app->pressed,0,sizeof(app->pressed));
     return app;
 }
@@ -555,12 +604,19 @@ void asteroids_app_free(AsteroidsApp *app) {
     free(app);
 }
 
+/* Return the time in milliseconds the specified key is continuously
+ * pressed. Or 0 if it is not pressed. */
+uint32_t key_pressed_time(AsteroidsApp *app, InputKey key) {
+    return app->pressed[key] == 0 ? 0 :
+           furi_get_tick() - app->pressed[key];
+}
+
 /* Handle keys interaction. */
 void asteroids_update_keypress_state(AsteroidsApp *app, InputEvent input) {
     if (input.type == InputTypePress) {
         app->pressed[input.key] = furi_get_tick();
     } else if (input.type == InputTypeRelease) {
-        uint32_t dur = furi_get_tick() - app->pressed[input.key];
+        uint32_t dur = key_pressed_time(app,input.key);
         app->pressed[input.key] = 0;
         if (dur < 200 && input.key == InputKeyOk) app->fire = true;
     }
@@ -576,9 +632,7 @@ int32_t asteroids_app_entry(void* p) {
 
     /* This is the main event loop: here we get the events that are pushed
      * in the queue by input_callback(), and process them one after the
-     * other. The timeout is 100 milliseconds, so if not input is received
-     * before such time, we exit the queue_get() function and call
-     * view_port_update() in order to refresh our screen content. */
+     * other. */
     InputEvent input;
     while(app->running) {
         FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100);