Update apps pt2
@@ -0,0 +1,24 @@
|
||||
Copyright (c) 2022-2023 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -13,23 +13,51 @@
|
||||
#include <math.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <asteroids_icons.h>
|
||||
|
||||
#define TAG "Asteroids" // Used for logging
|
||||
#define DEBUG_MSG 1
|
||||
#define DEBUG_MSG 0
|
||||
#define SCREEN_XRES 128
|
||||
#define SCREEN_YRES 64
|
||||
#define GAME_START_LIVES 3
|
||||
#define MAXLIVES 5 /* Max bonus lives allowed. */
|
||||
#define TTLBUL 30 /* Bullet time to live, in ticks. */
|
||||
#define MAXBUL 5 /* Max bullets on the screen. */
|
||||
#define MAXBUL 50 /* Max bullets on the screen. */
|
||||
//@todo MAX Asteroids
|
||||
#define MAXAST 32 /* Max asteroids on the screen. */
|
||||
#define MAXPOWERUPS 3 /* Max powerups allowed on screen */
|
||||
#define POWERUPSTTL 400 /* Max powerup time to live, in ticks. */
|
||||
#define SHIP_HIT_ANIMATION_LEN 15
|
||||
#define SAVING_DIRECTORY "/ext/apps_data/asteroids"
|
||||
#define SAVING_DIRECTORY "/ext/apps/Games"
|
||||
#define SAVING_FILENAME SAVING_DIRECTORY "/game_asteroids.save"
|
||||
#ifndef PI
|
||||
#define PI 3.14159265358979f
|
||||
#endif
|
||||
|
||||
/* ============================ Data structures ============================= */
|
||||
typedef enum PowerUpType {
|
||||
PowerUpTypeShield, // Shield
|
||||
PowerUpTypeLife, // Extra life
|
||||
PowerUpTypeFirePower, // Burst Fire power
|
||||
// PowerUpTypeRadialFire, // Radial Fire power
|
||||
PowerUpTypeNuke, // Nuke power
|
||||
// PowerUpTypeClone, // Clone ship
|
||||
// PowerUpTypeAssist, // Secondary ship
|
||||
Number_of_PowerUps // Used to count the number of powerups
|
||||
} PowerUpType;
|
||||
|
||||
// struct PowerUp
|
||||
typedef struct PowerUp {
|
||||
float x, y, vx, vy; /* Fields like in ship. */
|
||||
// rot, /* Fields like ship. */
|
||||
// rot_speed, /* Angular velocity (rot speed and sense). */
|
||||
float size; /* Power Up size */
|
||||
|
||||
uint32_t ttl; /* Time to live, in ticks. */
|
||||
uint32_t display_ttl; /* How long to display the powerup before it disappears */
|
||||
enum PowerUpType powerUpType; /* PowerUp type */
|
||||
bool isPowerUpActive; /* Is the powerup active? */
|
||||
} PowerUp;
|
||||
|
||||
typedef struct Ship {
|
||||
float x, /* Ship x position. */
|
||||
@@ -51,6 +79,7 @@ typedef struct Asteroid {
|
||||
uint8_t shape_seed; /* Seed to give random shape. */
|
||||
} Asteroid;
|
||||
|
||||
// @todo AsteroidsApp
|
||||
typedef struct AsteroidsApp {
|
||||
/* GUI */
|
||||
Gui* gui;
|
||||
@@ -61,6 +90,7 @@ typedef struct AsteroidsApp {
|
||||
/* Game state. */
|
||||
int running; /* Once false exists the app. */
|
||||
bool gameover; /* Gameover status. */
|
||||
bool paused; /* Game paused status. */
|
||||
uint32_t ticks; /* Game ticks. Increments at each refresh. */
|
||||
uint32_t score; /* Game score. */
|
||||
uint32_t highscore; /* Highscore. Shown on Game Over Screen */
|
||||
@@ -73,11 +103,14 @@ typedef struct AsteroidsApp {
|
||||
|
||||
/* Ship state. */
|
||||
struct Ship ship;
|
||||
struct PowerUp powerUps[MAXPOWERUPS]; /* Each powerup state. */
|
||||
int powerUps_num; /* Active powerups. */
|
||||
|
||||
/* Bullets state. */
|
||||
struct Bullet bullets[MAXBUL]; /* Each bullet state. */
|
||||
int bullets_num; /* Active bullets. */
|
||||
uint32_t last_bullet_tick; /* Tick the last bullet was fired. */
|
||||
uint32_t bullet_min_period; /* Minimum time between bullets in ms. */
|
||||
|
||||
/* Asteroids state. */
|
||||
Asteroid asteroids[MAXAST]; /* Each asteroid state. */
|
||||
@@ -133,9 +166,63 @@ const NotificationSequence sequence_bullet_fired = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sequence_powerup_collected = {
|
||||
&message_vibro_on,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_vibro_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sequence_nuke = {
|
||||
&message_blink_set_color_red,
|
||||
&message_blink_start_100,
|
||||
|
||||
&message_vibro_on,
|
||||
&message_delay_10,
|
||||
&message_vibro_off,
|
||||
|
||||
&message_vibro_on,
|
||||
&message_delay_10,
|
||||
&message_vibro_off,
|
||||
|
||||
&message_vibro_on,
|
||||
&message_delay_10,
|
||||
&message_vibro_off,
|
||||
&message_red_0,
|
||||
|
||||
&message_vibro_on,
|
||||
&message_delay_10,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_vibro_off,
|
||||
|
||||
&message_vibro_on,
|
||||
&message_delay_10,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_vibro_off,
|
||||
|
||||
&message_vibro_on,
|
||||
&message_delay_10,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_vibro_off,
|
||||
|
||||
&message_blink_stop,
|
||||
&message_vibro_off,
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* ============================== Prototyeps ================================ */
|
||||
|
||||
// Only functions called before their definition are here.
|
||||
bool isPowerUpActive(AsteroidsApp* app, enum PowerUpType powerUpType);
|
||||
bool isPowerUpAlreadyExists(AsteroidsApp* app, enum PowerUpType powerUpType);
|
||||
bool load_game(AsteroidsApp* app);
|
||||
void save_game(AsteroidsApp* app);
|
||||
void restart_game_after_gameover(AsteroidsApp* app);
|
||||
@@ -183,6 +270,8 @@ void lfsr_next(unsigned char* prev) {
|
||||
*prev ^= *prev << 7; /* Mix things a bit more. */
|
||||
}
|
||||
|
||||
/* ================================ Render ================================ */
|
||||
|
||||
/* Render the polygon 'poly' at x,y, rotated by the specified angle. */
|
||||
void draw_poly(Canvas* const canvas, Poly* poly, uint8_t x, uint8_t y, float a) {
|
||||
Poly rot;
|
||||
@@ -249,20 +338,127 @@ void draw_left_lives(Canvas* const canvas, AsteroidsApp* app) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Given the current position, update it according to the velocity and
|
||||
* wrap it back to the other side if the object went over the screen. */
|
||||
void update_pos_by_velocity(float* x, float* y, float vx, float vy) {
|
||||
/* Return back from one side to the other of the screen. */
|
||||
*x += vx;
|
||||
*y += vy;
|
||||
if(*x >= SCREEN_XRES)
|
||||
*x = 0;
|
||||
else if(*x < 0)
|
||||
*x = SCREEN_XRES - 1;
|
||||
if(*y >= SCREEN_YRES)
|
||||
*y = 0;
|
||||
else if(*y < 0)
|
||||
*y = SCREEN_YRES - 1;
|
||||
bool should_draw_powerUp(PowerUp* const p) {
|
||||
// Just return if power up has already been picked up
|
||||
if(p->display_ttl == 0) return false;
|
||||
|
||||
// Begin flashing power up when it is about to expire
|
||||
if(p->display_ttl < 100) {
|
||||
return p->display_ttl % 8 > 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void draw_powerUp_RemainingLife(Canvas* canvas, PowerUp* const p, int y_offset) {
|
||||
if(!p->isPowerUpActive) return;
|
||||
|
||||
/*
|
||||
Here we generate a reverse progress bar. The bar is 24 pixels wide and 1 pixel tall.
|
||||
Calculate the percentage of hitpoints left: hitpoints / total hitpoints
|
||||
Multiply the percentage by the width of the bar (in pixels): percentage * bar width
|
||||
Subtract the result from the width of the bar to get the filled portion of the bar: bar width - (percentage * bar width)
|
||||
Round the result to the nearest integer to get the final result.
|
||||
|
||||
400 / 400 = 1.0
|
||||
1.0 * 24 = 24
|
||||
24 - 24 = 0
|
||||
Round(0) = 0
|
||||
*/
|
||||
int progress_bar_width = SCREEN_XRES / 4;
|
||||
|
||||
if(p->ttl > 0) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
int remaining = ceil(((float)p->ttl / (float)POWERUPSTTL) * (float)progress_bar_width);
|
||||
|
||||
if(remaining > 0) {
|
||||
canvas_draw_line(
|
||||
canvas,
|
||||
(SCREEN_XRES / 2) - remaining, // x1
|
||||
3 + y_offset, //y1
|
||||
(SCREEN_XRES / 2) + remaining, //x2
|
||||
3 + y_offset); // y2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_powerUps(Canvas* const canvas, PowerUp* const p) {
|
||||
/*
|
||||
|
||||
* * * * * * * * * *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* F *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* * * * * * * * * *
|
||||
|
||||
BOX_SIZE = 10
|
||||
Box_Width = BOX_SIZE
|
||||
BOX_HEIGHT = BOX_SIZE
|
||||
BOX_X_POS = x - BOX_WIDTH/2
|
||||
BOX_Y_POS = y - BOX_HEIGHT/2
|
||||
POS_F_X = WIDTH/2
|
||||
POS_F_Y = HEIGHT/2
|
||||
|
||||
*/
|
||||
|
||||
//@todo render_callback
|
||||
|
||||
// Just return if power up has already been picked up
|
||||
// FURI_LOG_I(TAG, "[draw_powerUps] Display TTL: %lu", p->display_ttl);
|
||||
if(p->display_ttl == 0) return;
|
||||
if(!should_draw_powerUp(p)) return;
|
||||
|
||||
canvas_set_color(canvas, ColorXOR);
|
||||
|
||||
// Display power up to be picked up
|
||||
switch(p->powerUpType) {
|
||||
case PowerUpTypeFirePower:
|
||||
canvas_draw_icon(canvas, p->x, p->y, &I_firepower_shifted_9x10);
|
||||
break;
|
||||
case PowerUpTypeShield:
|
||||
canvas_draw_icon(canvas, p->x, p->y, &I_shield_frame);
|
||||
break;
|
||||
case PowerUpTypeLife:
|
||||
// Draw a heart
|
||||
canvas_draw_icon(canvas, p->x, p->y, &I_heart_10x10);
|
||||
break;
|
||||
case PowerUpTypeNuke:
|
||||
// canvas_draw_disc(canvas, p->x, p->y, p->size);
|
||||
// canvas_draw_str(canvas, p->x, p->y, "N");
|
||||
canvas_draw_icon(canvas, p->x, p->y, &I_nuke_10x10);
|
||||
break;
|
||||
// case PowerUpTypeRadialFire:
|
||||
// // Draw box with letter R inside
|
||||
// canvas_draw_str(canvas, p->x, p->y, "R");
|
||||
// break;
|
||||
// case PowerUpTypeAssist:
|
||||
// // Draw box with letter A inside
|
||||
// canvas_draw_str(canvas, p->x, p->y, "A");
|
||||
// break;
|
||||
// case PowerUpTypeClone:
|
||||
// // Draw box with letter C inside
|
||||
// canvas_draw_str(canvas, p->x, p->y, "C");
|
||||
// break;
|
||||
default:
|
||||
//@todo Uknown Power Up Type Detected
|
||||
// Draw box with letter U inside
|
||||
canvas_draw_str(canvas, p->x, p->y, "?");
|
||||
FURI_LOG_E(TAG, "Unexpected Power Up Type Detected: %i", p->powerUpType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_shield(Canvas* const canvas, AsteroidsApp* app) {
|
||||
if(isPowerUpActive(app, PowerUpTypeShield) == false) return;
|
||||
|
||||
canvas_set_color(canvas, ColorXOR);
|
||||
// canvas_draw_disc(canvas, app->ship.x, app->ship.y, 4);
|
||||
canvas_draw_circle(canvas, app->ship.x, app->ship.y, 8);
|
||||
}
|
||||
|
||||
/* Render the current game screen. */
|
||||
@@ -286,6 +482,9 @@ void render_callback(Canvas* const canvas, void* ctx) {
|
||||
/* Draw ship, asteroids, bullets. */
|
||||
draw_poly(canvas, &ShipPoly, app->ship.x, app->ship.y, app->ship.rot);
|
||||
|
||||
/* Draw shield if active. */
|
||||
draw_shield(canvas, app);
|
||||
|
||||
if(key_pressed_time(app, InputKeyUp) > 0) {
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_thrusters);
|
||||
draw_poly(canvas, &ShipFirePoly, app->ship.x, app->ship.y, app->ship.rot);
|
||||
@@ -295,6 +494,20 @@ void render_callback(Canvas* const canvas, void* ctx) {
|
||||
|
||||
for(int j = 0; j < app->asteroids_num; j++) draw_asteroid(canvas, &app->asteroids[j]);
|
||||
|
||||
for(int j = 0; j < app->powerUps_num; j++) {
|
||||
draw_powerUps(canvas, &app->powerUps[j]);
|
||||
draw_powerUp_RemainingLife(canvas, &app->powerUps[j], j);
|
||||
}
|
||||
|
||||
if(app->paused) {
|
||||
canvas_set_color(canvas, ColorXOR);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_rbox(canvas, 0, 0, SCREEN_XRES, SCREEN_YRES, 4);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, SCREEN_XRES / 2, SCREEN_YRES / 2, AlignCenter, AlignCenter, "Paused");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Game over text. */
|
||||
if(app->gameover) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
@@ -331,6 +544,22 @@ void render_callback(Canvas* const canvas, void* ctx) {
|
||||
|
||||
/* ============================ Game logic ================================== */
|
||||
|
||||
/* Given the current position, update it according to the velocity and
|
||||
* wrap it back to the other side if the object went over the screen. */
|
||||
void update_pos_by_velocity(float* x, float* y, float vx, float vy) {
|
||||
/* Return back from one side to the other of the screen. */
|
||||
*x += vx;
|
||||
*y += vy;
|
||||
if(*x >= SCREEN_XRES)
|
||||
*x = 0;
|
||||
else if(*x < 0)
|
||||
*x = SCREEN_XRES - 1;
|
||||
if(*y >= SCREEN_YRES)
|
||||
*y = 0;
|
||||
else if(*y < 0)
|
||||
*y = SCREEN_YRES - 1;
|
||||
}
|
||||
|
||||
float distance(float x1, float y1, float x2, float y2) {
|
||||
float dx = x1 - x2;
|
||||
float dy = y1 - y2;
|
||||
@@ -363,9 +592,16 @@ bool objects_are_colliding(float x1, float y1, float r1, float x2, float y2, flo
|
||||
return dx * dx + dy * dy < rsum * rsum;
|
||||
}
|
||||
|
||||
/* ================================ Bullets ================================ */
|
||||
//@todo ship_fire_bullet
|
||||
/* Create a new bullet headed in the same direction of the ship. */
|
||||
void ship_fire_bullet(AsteroidsApp* app) {
|
||||
if(app->bullets_num == MAXBUL) return;
|
||||
// No power ups, only 5 bullets allowed
|
||||
if(isPowerUpActive(app, PowerUpTypeFirePower) == false && app->bullets_num >= 5) return;
|
||||
|
||||
// Double the Fire Power
|
||||
if(isPowerUpActive(app, PowerUpTypeFirePower) && (app->bullets_num >= (MAXBUL))) return;
|
||||
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_bullet_fired);
|
||||
Bullet* b = &app->bullets[app->bullets_num];
|
||||
b->x = app->ship.x;
|
||||
@@ -401,6 +637,7 @@ void remove_bullet(AsteroidsApp* app, int bid) {
|
||||
if(n && bid != n) app->bullets[bid] = app->bullets[n];
|
||||
}
|
||||
|
||||
/* ================================ Asteroids ================================ */
|
||||
/* Create a new asteroid, away from the ship. Return the
|
||||
* pointer to the asteroid object, so that the caller can change
|
||||
* certain things of the asteroid if needed. */
|
||||
@@ -466,6 +703,159 @@ void asteroid_was_hit(AsteroidsApp* app, int id) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================ Power Up ================================ */
|
||||
bool isPowerUpCollidingWithEachOther(AsteroidsApp* app, float x, float y, float size) {
|
||||
for(int i = 0; i < app->powerUps_num; i++) {
|
||||
PowerUp* p2 = &app->powerUps[i];
|
||||
if(objects_are_colliding(x, y, size, p2->x, p2->y, p2->size, 1)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool should_trigger_rare_powerUp(PowerUpType selected_powerUpType) {
|
||||
switch(selected_powerUpType) {
|
||||
case PowerUpTypeLife: // Make extra life power up more rare
|
||||
return rand() % 10 != 0;
|
||||
case PowerUpTypeShield: // Make shield power up more rare
|
||||
return rand() % 4 != 0;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//@todo Add PowerUp
|
||||
PowerUp* add_powerUp(AsteroidsApp* app) {
|
||||
FURI_LOG_I(TAG, "add_powerUp: %i", app->powerUps_num);
|
||||
if(app->powerUps_num == MAXPOWERUPS) return NULL; // Max Power Ups reached
|
||||
if(app->lives == MAXLIVES) return NULL; // Max Lives reached
|
||||
|
||||
// Randomly select power up for display
|
||||
PowerUpType selected_powerUpType = rand() % Number_of_PowerUps;
|
||||
FURI_LOG_I(TAG, "[add_powerUp] Power Up Selected: %i", selected_powerUpType);
|
||||
|
||||
// Don't add already existing power ups
|
||||
if(isPowerUpAlreadyExists(app, selected_powerUpType)) {
|
||||
FURI_LOG_D(TAG, "[add_powerUp] Power Up %i already active", selected_powerUpType);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Make some power ups more rare
|
||||
if(!should_trigger_rare_powerUp(selected_powerUpType)) {
|
||||
FURI_LOG_D(TAG, "[add_powerUp] Power Up %i not triggered", selected_powerUpType);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
float size = 10;
|
||||
float min_distance = 20;
|
||||
float x, y;
|
||||
do {
|
||||
//Make sure power up is not spawned on the edge of the screen
|
||||
x = rand() % (SCREEN_XRES - (int)size);
|
||||
y = rand() % (SCREEN_YRES - (int)size);
|
||||
|
||||
//Also keep it away from the lives and score at the top of screen
|
||||
if(y < size) y = size;
|
||||
} while(
|
||||
((distance(app->ship.x, app->ship.y, x, y) < min_distance + size) ||
|
||||
isPowerUpCollidingWithEachOther(app, x, y, size)));
|
||||
|
||||
PowerUp* p = &app->powerUps[app->powerUps_num++];
|
||||
p->x = x;
|
||||
p->y = y;
|
||||
//@todo Disable Velocity
|
||||
p->vx = 0; //2 * (-.5 + ((float)rand() / RAND_MAX));
|
||||
p->vy = 0; //2 * (-.5 + ((float)rand() / RAND_MAX));
|
||||
p->display_ttl = 200;
|
||||
p->ttl = POWERUPSTTL;
|
||||
p->size = size;
|
||||
// p->size = size;
|
||||
// p->rot = 0;
|
||||
// p->rot_speed = ((float)rand() / RAND_MAX) / 10;
|
||||
// if(app->ticks & 1) p->rot_speed = -(p->rot_speed);
|
||||
|
||||
//@todo add powerup type, for now hardcoding to firepower
|
||||
p->powerUpType = selected_powerUpType;
|
||||
p->isPowerUpActive = false;
|
||||
FURI_LOG_I(TAG, "[add_powerUp] Power Up Added: %i", p->powerUpType);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool isPowerUpActive(AsteroidsApp* const app, PowerUpType const powerUpType) {
|
||||
for(int i = 0; i < app->powerUps_num; i++) {
|
||||
// PowerUp* p = &app->powerUps[i];
|
||||
// if(p->powerUpType == powerUpType && p->ttl > 0 && p->display_ttl == 0) return true;
|
||||
if(app->powerUps[i].isPowerUpActive && app->powerUps[i].powerUpType == powerUpType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isPowerUpAlreadyExists(AsteroidsApp* const app, PowerUpType const powerUpType) {
|
||||
for(int i = 0; i < app->powerUps_num; i++) {
|
||||
if(app->powerUps[i].powerUpType == powerUpType) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//@todo remove_powerUp
|
||||
void remove_powerUp(AsteroidsApp* app, int id) {
|
||||
FURI_LOG_I(TAG, "remove_powerUp: %i", id);
|
||||
// Invalid ID, ignore
|
||||
if(id < 0) {
|
||||
FURI_LOG_E(TAG, "remove_powerUp: Invalid ID: %i", id);
|
||||
return;
|
||||
}
|
||||
// TODO: Break this out into object types that set the game state
|
||||
// Return the bullet period to normal
|
||||
if(app->powerUps[id].powerUpType == PowerUpTypeFirePower) {
|
||||
app->bullet_min_period = 200;
|
||||
}
|
||||
|
||||
/* Replace the top powerUp with the empty space left
|
||||
* by the removal of this one. This way we always take the
|
||||
* array dense, which is an advantage when looping. */
|
||||
int n = --app->powerUps_num;
|
||||
if(n && id != n) app->powerUps[id] = app->powerUps[n];
|
||||
}
|
||||
|
||||
void remove_all_astroids_and_bullets(AsteroidsApp* app) {
|
||||
app->score += app->asteroids_num;
|
||||
app->asteroids_num = 0;
|
||||
app->bullets_num = 0;
|
||||
}
|
||||
|
||||
//@todo powerUp_was_hit
|
||||
void powerUp_was_hit(AsteroidsApp* app, int id) {
|
||||
PowerUp* p = &app->powerUps[id];
|
||||
if(p->display_ttl == 0) return; // Don't collect if already collected or expired
|
||||
|
||||
// Vibrate to indicate power up was collected
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_powerup_collected);
|
||||
|
||||
switch(p->powerUpType) {
|
||||
case PowerUpTypeLife:
|
||||
if(app->lives < MAXLIVES) app->lives++;
|
||||
remove_powerUp(app, id);
|
||||
break;
|
||||
case PowerUpTypeFirePower:
|
||||
p->ttl = POWERUPSTTL / 2;
|
||||
app->bullet_min_period = 100;
|
||||
break;
|
||||
case PowerUpTypeNuke:
|
||||
//TODO: Animate explosion
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_nuke);
|
||||
// Simulate nuke explosion
|
||||
remove_all_astroids_and_bullets(app);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
p->display_ttl = 0;
|
||||
p->isPowerUpActive = true;
|
||||
}
|
||||
|
||||
/* ================================ Game States ================================ */
|
||||
/* 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) {
|
||||
@@ -494,7 +884,9 @@ void restart_game(AsteroidsApp* app) {
|
||||
app->ship.vx = 0;
|
||||
app->ship.vy = 0;
|
||||
app->bullets_num = 0;
|
||||
app->powerUps_num = 0;
|
||||
app->last_bullet_tick = 0;
|
||||
app->bullet_min_period = 200;
|
||||
app->asteroids_num = 0;
|
||||
app->ship_hit = 0;
|
||||
}
|
||||
@@ -510,6 +902,7 @@ void restart_game_after_gameover(AsteroidsApp* app) {
|
||||
restart_game(app);
|
||||
}
|
||||
|
||||
/* ================================ Position & Status ================================ */
|
||||
/* Move bullets. */
|
||||
void update_bullets_position(AsteroidsApp* app) {
|
||||
for(int j = 0; j < app->bullets_num; j++) {
|
||||
@@ -536,6 +929,68 @@ void update_asteroids_position(AsteroidsApp* app) {
|
||||
}
|
||||
}
|
||||
|
||||
bool should_add_powerUp(AsteroidsApp* app) {
|
||||
srand(furi_get_tick());
|
||||
int random_number = rand() % 100 + 1;
|
||||
|
||||
// The chance of spawning a power-up decreases as the game goes on
|
||||
int threshold = 100 - (app->score * 5);
|
||||
|
||||
// Make sure the threshold doesn't go below 10
|
||||
threshold = (threshold < 10) ? 10 : threshold;
|
||||
// FURI_LOG_I(
|
||||
// TAG,
|
||||
// "Random number: %d, threshold: %d Bool: %d",
|
||||
// random_number,
|
||||
// threshold,
|
||||
// random_number <= threshold);
|
||||
return random_number <= threshold;
|
||||
}
|
||||
|
||||
void update_powerUps_position(AsteroidsApp* app) {
|
||||
for(int j = 0; j < app->powerUps_num; j++) {
|
||||
// @todo update_powerUps_position
|
||||
if(app->powerUps[j].display_ttl > 0) {
|
||||
update_pos_by_velocity(
|
||||
&app->powerUps[j].x, &app->powerUps[j].y, app->powerUps[j].vx, app->powerUps[j].vy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @todo update_powerUp_status
|
||||
/* This updates the state of each power up collected and removes them if they have expired. */
|
||||
void update_powerUp_status(AsteroidsApp* app) {
|
||||
for(int j = 0; j < app->powerUps_num; j++) {
|
||||
if(app->powerUps[j].ttl > 0 && app->powerUps[j].isPowerUpActive) {
|
||||
// Only decrement ttl if we actually picked up power up
|
||||
app->powerUps[j].ttl--;
|
||||
} else if(app->powerUps[j].display_ttl > 0) {
|
||||
app->powerUps[j].display_ttl--;
|
||||
} else if(app->powerUps[j].ttl == 0 || app->powerUps[j].display_ttl == 0) {
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"[update_powerUp_status] Power up expired!, ttl: %lu, display_ttl: %lu id: %d",
|
||||
app->powerUps[j].ttl,
|
||||
app->powerUps[j].display_ttl,
|
||||
j);
|
||||
// we've reached the end of life of the power up
|
||||
// Time to remove it
|
||||
app->powerUps[j].isPowerUpActive = false;
|
||||
remove_powerUp(app, j);
|
||||
j--; /* Process this power up index again: the removal will
|
||||
fill it with the top power up to take the array dense. */
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"[update_powerUp_status] Power up error! Invalid Index: %d ttl: %lu display_ttl: %lu PowerUp_Num: %d",
|
||||
j,
|
||||
app->powerUps[j].ttl,
|
||||
app->powerUps[j].display_ttl,
|
||||
app->powerUps_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Collision detection and game state update based on collisions. */
|
||||
void detect_collisions(AsteroidsApp* app) {
|
||||
/* Detect collision between bullet and asteroid. */
|
||||
@@ -560,8 +1015,26 @@ void detect_collisions(AsteroidsApp* app) {
|
||||
for(int j = 0; j < app->asteroids_num; j++) {
|
||||
Asteroid* a = &app->asteroids[j];
|
||||
if(objects_are_colliding(a->x, a->y, a->size, app->ship.x, app->ship.y, 4, 1)) {
|
||||
ship_was_hit(app);
|
||||
break;
|
||||
if(isPowerUpActive(app, PowerUpTypeShield)) {
|
||||
// Asteroid was hit with shield
|
||||
notification_message(
|
||||
furi_record_open(RECORD_NOTIFICATION), &sequence_bullet_fired);
|
||||
asteroid_was_hit(app, j);
|
||||
j--; /* Scan this j value again. */
|
||||
} else {
|
||||
// No sheild active, take damage
|
||||
ship_was_hit(app);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Detect collision between ship and powerUp. */
|
||||
for(int j = 0; j < app->powerUps_num; j++) {
|
||||
PowerUp* p = &app->powerUps[j];
|
||||
if(objects_are_colliding(p->x, p->y, p->size, app->ship.x, app->ship.y, 4, 1)) {
|
||||
powerUp_was_hit(app, j);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -599,6 +1072,12 @@ void game_tick(void* ctx) {
|
||||
update_asteroids_position(app);
|
||||
view_port_update(app->view_port);
|
||||
return;
|
||||
} else if(app->paused) {
|
||||
if(key_pressed_time(app, InputKeyBack) > 100 || key_pressed_time(app, InputKeyOk) > 100) {
|
||||
app->paused = false;
|
||||
}
|
||||
view_port_update(app->view_port);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handle keypresses. */
|
||||
@@ -617,19 +1096,32 @@ void game_tick(void* ctx) {
|
||||
* asteroids_update_keypress_state() since depends on exact
|
||||
* pressure timing. */
|
||||
if(app->fire) {
|
||||
uint32_t bullet_min_period = 200; // In milliseconds
|
||||
uint32_t now = furi_get_tick();
|
||||
if(now - app->last_bullet_tick >= bullet_min_period) {
|
||||
if(now - app->last_bullet_tick >= app->bullet_min_period) {
|
||||
ship_fire_bullet(app);
|
||||
app->last_bullet_tick = now;
|
||||
}
|
||||
app->fire = false;
|
||||
}
|
||||
|
||||
// DEBUG: Show Power Up Status
|
||||
// for(int j = 0; j < app->powerUps_num; j++) {
|
||||
// PowerUp* p = &app->powerUps[j];
|
||||
// FURI_LOG_I(
|
||||
// TAG,
|
||||
// "Power Up Type: %d TTL: %lu Display_TTL: %lu PowerUpNum: %i",
|
||||
// p->powerUpType,
|
||||
// p->ttl,
|
||||
// p->display_ttl,
|
||||
// app->powerUps_num);
|
||||
// }
|
||||
|
||||
/* 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);
|
||||
update_powerUp_status(app); //@todo update_powerUp_status
|
||||
update_powerUps_position(app);
|
||||
detect_collisions(app);
|
||||
|
||||
/* From time to time, create a new asteroid. The more asteroids
|
||||
@@ -639,6 +1131,13 @@ void game_tick(void* ctx) {
|
||||
add_asteroid(app);
|
||||
}
|
||||
|
||||
/* From time to time add a random power up */
|
||||
//@todo game tick
|
||||
// if(app->powerUps_num == 0 || random() % (500 + (100 * (int)app->score)) <= app->powerUps_num) {
|
||||
if(should_add_powerUp(app)) {
|
||||
add_powerUp(app);
|
||||
}
|
||||
|
||||
app->ticks++;
|
||||
view_port_update(app->view_port);
|
||||
}
|
||||
@@ -760,8 +1259,17 @@ int32_t asteroids_app_entry(void* p) {
|
||||
while(app->running) {
|
||||
FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100);
|
||||
if(qstat == FuriStatusOk) {
|
||||
if(DEBUG_MSG)
|
||||
FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u", input.type, input.key);
|
||||
// if(DEBUG_MSG)
|
||||
// FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u", input.type, input.key);
|
||||
/* Handle Pause */
|
||||
if(input.type == InputTypeShort && input.key == InputKeyBack) {
|
||||
app->paused = !app->paused;
|
||||
if(app->paused) {
|
||||
furi_timer_stop(timer);
|
||||
} else {
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 10);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle navigation here. Then handle view-specific inputs
|
||||
* in the view specific handling function. */
|
||||
@@ -775,11 +1283,11 @@ int32_t asteroids_app_entry(void* p) {
|
||||
} else {
|
||||
/* Useful to understand if the app is still alive when it
|
||||
* does not respond because of bugs. */
|
||||
if(DEBUG_MSG) {
|
||||
static int c = 0;
|
||||
c++;
|
||||
if(!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
|
||||
}
|
||||
// if(DEBUG_MSG) {
|
||||
// static int c = 0;
|
||||
// c++;
|
||||
// if(!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,14 @@ App(
|
||||
name="Asteroids",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="asteroids_app_entry",
|
||||
cdefines=["APP_PROTOVIEW"],
|
||||
cdefines=["APP_ASTEROIDS"],
|
||||
requires=["gui"],
|
||||
stack_size=8 * 1024,
|
||||
order=50,
|
||||
fap_icon="appicon.png",
|
||||
fap_category="Games",
|
||||
fap_icon_assets="assets", # Image assets to compile for this application
|
||||
fap_description="An implementation of the classic arcade game Asteroids",
|
||||
fap_author="antirez, SimplyMinimal",
|
||||
fap_weburl="https://github.com/SimplyMinimal/FlipperZero-Asteroids",
|
||||
)
|
||||
|
||||
|
After Width: | Height: | Size: 287 B |
|
After Width: | Height: | Size: 378 B |
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 123 B |
|
After Width: | Height: | Size: 137 B |
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 299 B |
|
After Width: | Height: | Size: 300 B |
|
After Width: | Height: | Size: 320 B |
|
After Width: | Height: | Size: 203 B |
|
After Width: | Height: | Size: 299 B |