mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-12-01 00:39:12 +00:00
72f250195c
All games now will increase flipper's level when you start them or win in some of them Games with endless play like tetris or flappy bird has no winning logic so they will increase level only when you start them
374 lines
12 KiB
C
374 lines
12 KiB
C
#include <stdlib.h>
|
|
|
|
#include <FlappyBird_icons.h>
|
|
#include <furi.h>
|
|
#include <gui/gui.h>
|
|
#include <gui/icon_animation_i.h>
|
|
#include <input/input.h>
|
|
#include <dolphin/dolphin.h>
|
|
|
|
#define TAG "Flappy"
|
|
#define DEBUG false
|
|
|
|
#define FLAPPY_BIRD_HEIGHT 15
|
|
#define FLAPPY_BIRD_WIDTH 10
|
|
|
|
#define FLAPPY_PILAR_MAX 6
|
|
#define FLAPPY_PILAR_DIST 35
|
|
|
|
#define FLAPPY_GAB_HEIGHT 25
|
|
#define FLAPPY_GAB_WIDTH 10
|
|
|
|
#define FLAPPY_GRAVITY_JUMP -1.1
|
|
#define FLAPPY_GRAVITY_TICK 0.15
|
|
|
|
#define FLIPPER_LCD_WIDTH 128
|
|
#define FLIPPER_LCD_HEIGHT 64
|
|
|
|
typedef enum {
|
|
EventTypeTick,
|
|
EventTypeKey,
|
|
} EventType;
|
|
|
|
typedef struct {
|
|
int x;
|
|
int y;
|
|
} POINT;
|
|
|
|
typedef struct {
|
|
float gravity;
|
|
POINT point;
|
|
IconAnimation* sprite;
|
|
} BIRD;
|
|
|
|
typedef struct {
|
|
POINT point;
|
|
int height;
|
|
int visible;
|
|
bool passed;
|
|
} PILAR;
|
|
|
|
typedef enum {
|
|
GameStateLife,
|
|
GameStateGameOver,
|
|
} State;
|
|
|
|
typedef struct {
|
|
BIRD bird;
|
|
int points;
|
|
int pilars_count;
|
|
PILAR pilars[FLAPPY_PILAR_MAX];
|
|
bool debug;
|
|
State state;
|
|
} GameState;
|
|
|
|
typedef struct {
|
|
EventType type;
|
|
InputEvent input;
|
|
} GameEvent;
|
|
|
|
typedef enum {
|
|
DirectionUp,
|
|
DirectionRight,
|
|
DirectionDown,
|
|
DirectionLeft,
|
|
} Direction;
|
|
|
|
static void flappy_game_random_pilar(GameState* const game_state) {
|
|
PILAR pilar;
|
|
|
|
pilar.passed = false;
|
|
pilar.visible = 1;
|
|
pilar.height = random() % (FLIPPER_LCD_HEIGHT - FLAPPY_GAB_HEIGHT) + 1;
|
|
pilar.point.y = 0;
|
|
pilar.point.x = FLIPPER_LCD_WIDTH + FLAPPY_GAB_WIDTH + 1;
|
|
|
|
game_state->pilars_count++;
|
|
game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX] = pilar;
|
|
}
|
|
|
|
static void flappy_game_state_init(GameState* const game_state) {
|
|
BIRD bird;
|
|
bird.gravity = 0.0f;
|
|
bird.point.x = 15;
|
|
bird.point.y = 32;
|
|
bird.sprite = icon_animation_alloc(&A_bird);
|
|
|
|
game_state->debug = DEBUG;
|
|
game_state->bird = bird;
|
|
game_state->pilars_count = 0;
|
|
game_state->points = 0;
|
|
game_state->state = GameStateLife;
|
|
memset(game_state->pilars, 0, sizeof(game_state->pilars));
|
|
|
|
flappy_game_random_pilar(game_state);
|
|
}
|
|
|
|
static void flappy_game_state_free(GameState* const game_state) {
|
|
icon_animation_free(game_state->bird.sprite);
|
|
free(game_state);
|
|
}
|
|
|
|
static void flappy_game_tick(GameState* const game_state) {
|
|
if(game_state->state == GameStateLife) {
|
|
if(!game_state->debug) {
|
|
game_state->bird.gravity += FLAPPY_GRAVITY_TICK;
|
|
game_state->bird.point.y += game_state->bird.gravity;
|
|
}
|
|
|
|
// Checking the location of the last respawned pilar.
|
|
PILAR* pilar = &game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX];
|
|
if(pilar->point.x == (FLIPPER_LCD_WIDTH - FLAPPY_PILAR_DIST))
|
|
flappy_game_random_pilar(game_state);
|
|
|
|
// Updating the position/status of the pilars (visiblity, posotion, game points)
|
|
// | | | | |
|
|
// | | | | |
|
|
// |__| | |__|
|
|
// _____X | X_____
|
|
// | | | | | // [Pos + Width of pilar] >= [Bird Pos]
|
|
// |_____| | |_____|
|
|
// X <----> | X <->
|
|
// Bird Pos + Lenght of the bird] >= [Pilar]
|
|
for(int i = 0; i < FLAPPY_PILAR_MAX; i++) {
|
|
PILAR* pilar = &game_state->pilars[i];
|
|
if(pilar != NULL && pilar->visible && game_state->state == GameStateLife) {
|
|
pilar->point.x--;
|
|
if(game_state->bird.point.x >= pilar->point.x + FLAPPY_GAB_WIDTH &&
|
|
pilar->passed == false) {
|
|
pilar->passed = true;
|
|
game_state->points++;
|
|
}
|
|
if(pilar->point.x < -FLAPPY_GAB_WIDTH) pilar->visible = 0;
|
|
|
|
if(game_state->bird.point.y <= 0 - FLAPPY_BIRD_WIDTH) {
|
|
game_state->bird.point.y = 64;
|
|
}
|
|
|
|
if(game_state->bird.point.y > 64 - FLAPPY_BIRD_WIDTH) {
|
|
game_state->bird.point.y = FLIPPER_LCD_HEIGHT - FLAPPY_BIRD_WIDTH;
|
|
}
|
|
|
|
// Bird inbetween pipes
|
|
if((game_state->bird.point.x + FLAPPY_BIRD_HEIGHT >= pilar->point.x) &&
|
|
(game_state->bird.point.x <= pilar->point.x + FLAPPY_GAB_WIDTH)) {
|
|
// Bird below Bottom Pipe
|
|
if(game_state->bird.point.y + FLAPPY_BIRD_WIDTH - 2 >=
|
|
pilar->height + FLAPPY_GAB_HEIGHT) {
|
|
game_state->state = GameStateGameOver;
|
|
break;
|
|
}
|
|
|
|
// Bird above Upper Pipe
|
|
if(game_state->bird.point.y < pilar->height) {
|
|
game_state->state = GameStateGameOver;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void flappy_game_flap(GameState* const game_state) {
|
|
game_state->bird.gravity = FLAPPY_GRAVITY_JUMP;
|
|
}
|
|
|
|
static void flappy_game_render_callback(Canvas* const canvas, void* ctx) {
|
|
const GameState* game_state = acquire_mutex((ValueMutex*)ctx, 25);
|
|
if(game_state == NULL) {
|
|
return;
|
|
}
|
|
|
|
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
|
|
|
if(game_state->state == GameStateLife) {
|
|
// Pilars
|
|
for(int i = 0; i < FLAPPY_PILAR_MAX; i++) {
|
|
const PILAR* pilar = &game_state->pilars[i];
|
|
if(pilar != NULL && pilar->visible == 1) {
|
|
canvas_draw_frame(
|
|
canvas, pilar->point.x, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height);
|
|
|
|
canvas_draw_frame(
|
|
canvas, pilar->point.x + 1, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height);
|
|
|
|
canvas_draw_frame(
|
|
canvas,
|
|
pilar->point.x + 2,
|
|
pilar->point.y,
|
|
FLAPPY_GAB_WIDTH - 1,
|
|
pilar->height);
|
|
|
|
canvas_draw_frame(
|
|
canvas,
|
|
pilar->point.x,
|
|
pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT,
|
|
FLAPPY_GAB_WIDTH,
|
|
FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT);
|
|
|
|
canvas_draw_frame(
|
|
canvas,
|
|
pilar->point.x + 1,
|
|
pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT,
|
|
FLAPPY_GAB_WIDTH - 1,
|
|
FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT);
|
|
|
|
canvas_draw_frame(
|
|
canvas,
|
|
pilar->point.x + 2,
|
|
pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT,
|
|
FLAPPY_GAB_WIDTH - 1,
|
|
FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT);
|
|
}
|
|
}
|
|
|
|
// Switch animation
|
|
game_state->bird.sprite->frame = 1;
|
|
if(game_state->bird.gravity < -0.5)
|
|
game_state->bird.sprite->frame = 0;
|
|
else if(game_state->bird.gravity > 0.5)
|
|
game_state->bird.sprite->frame = 2;
|
|
|
|
canvas_draw_icon_animation(
|
|
canvas, game_state->bird.point.x, game_state->bird.point.y, game_state->bird.sprite);
|
|
|
|
canvas_set_font(canvas, FontSecondary);
|
|
char buffer[12];
|
|
snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
|
|
canvas_draw_str_aligned(canvas, 100, 12, AlignCenter, AlignBottom, buffer);
|
|
|
|
if(game_state->debug) {
|
|
char coordinates[20];
|
|
snprintf(coordinates, sizeof(coordinates), "Y: %u", game_state->bird.point.y);
|
|
canvas_draw_str_aligned(canvas, 1, 12, AlignCenter, AlignBottom, coordinates);
|
|
}
|
|
}
|
|
|
|
if(game_state->state == GameStateGameOver) {
|
|
// Screen is 128x64 px
|
|
canvas_set_color(canvas, ColorWhite);
|
|
canvas_draw_box(canvas, 34, 20, 62, 24);
|
|
|
|
canvas_set_color(canvas, ColorBlack);
|
|
canvas_draw_frame(canvas, 34, 20, 62, 24);
|
|
|
|
canvas_set_font(canvas, FontPrimary);
|
|
canvas_draw_str(canvas, 37, 31, "Game Over");
|
|
|
|
canvas_set_font(canvas, FontSecondary);
|
|
char buffer[12];
|
|
snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
|
|
canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
|
|
}
|
|
|
|
release_mutex((ValueMutex*)ctx, game_state);
|
|
}
|
|
|
|
static void flappy_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
|
furi_assert(event_queue);
|
|
|
|
GameEvent event = {.type = EventTypeKey, .input = *input_event};
|
|
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
|
}
|
|
|
|
static void flappy_game_update_timer_callback(FuriMessageQueue* event_queue) {
|
|
furi_assert(event_queue);
|
|
|
|
GameEvent event = {.type = EventTypeTick};
|
|
furi_message_queue_put(event_queue, &event, 0);
|
|
}
|
|
|
|
int32_t flappy_game_app(void* p) {
|
|
UNUSED(p);
|
|
int32_t return_code = 0;
|
|
|
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
|
|
|
|
GameState* game_state = malloc(sizeof(GameState));
|
|
flappy_game_state_init(game_state);
|
|
|
|
ValueMutex state_mutex;
|
|
if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
|
|
FURI_LOG_E(TAG, "cannot create mutex\r\n");
|
|
return_code = 255;
|
|
goto free_and_exit;
|
|
}
|
|
|
|
// Set system callbacks
|
|
ViewPort* view_port = view_port_alloc();
|
|
view_port_draw_callback_set(view_port, flappy_game_render_callback, &state_mutex);
|
|
view_port_input_callback_set(view_port, flappy_game_input_callback, event_queue);
|
|
|
|
FuriTimer* timer =
|
|
furi_timer_alloc(flappy_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
|
|
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
|
|
|
|
// Open GUI and register view_port
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
|
// Call dolphin deed on game start
|
|
DOLPHIN_DEED(DolphinDeedPluginGameStart);
|
|
|
|
GameEvent event;
|
|
for(bool processing = true; processing;) {
|
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
|
GameState* game_state = (GameState*)acquire_mutex_block(&state_mutex);
|
|
|
|
if(event_status == FuriStatusOk) {
|
|
// press events
|
|
if(event.type == EventTypeKey) {
|
|
if(event.input.type == InputTypePress) {
|
|
switch(event.input.key) {
|
|
case InputKeyUp:
|
|
if(game_state->state == GameStateLife) {
|
|
flappy_game_flap(game_state);
|
|
}
|
|
|
|
break;
|
|
case InputKeyDown:
|
|
break;
|
|
case InputKeyRight:
|
|
break;
|
|
case InputKeyLeft:
|
|
break;
|
|
case InputKeyOk:
|
|
if(game_state->state == GameStateGameOver) {
|
|
flappy_game_state_init(game_state);
|
|
}
|
|
|
|
if(game_state->state == GameStateLife) {
|
|
flappy_game_flap(game_state);
|
|
}
|
|
|
|
break;
|
|
case InputKeyBack:
|
|
processing = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else if(event.type == EventTypeTick) {
|
|
flappy_game_tick(game_state);
|
|
}
|
|
}
|
|
|
|
view_port_update(view_port);
|
|
release_mutex(&state_mutex, game_state);
|
|
}
|
|
|
|
furi_timer_free(timer);
|
|
view_port_enabled_set(view_port, false);
|
|
gui_remove_view_port(gui, view_port);
|
|
furi_record_close(RECORD_GUI);
|
|
view_port_free(view_port);
|
|
delete_mutex(&state_mutex);
|
|
|
|
free_and_exit:
|
|
flappy_game_state_free(game_state);
|
|
furi_message_queue_free(event_queue);
|
|
|
|
return return_code;
|
|
}
|