unleashed-firmware/applications/plugins/zombiez/zombiez.c
MX 72f250195c
Playing games now affect Flipper's level
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
2023-02-05 15:39:10 +03:00

404 lines
13 KiB
C

#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <dolphin/dolphin.h>
//ORIGINAL REPO: https://github.com/Dooskington/flipperzero-zombiez
//AUTHORS: https://github.com/Dooskington | https://github.com/DevMilanIan
#include "zombiez.h"
#define ZOMBIES_MAX 3
#define ZOMBIES_WIDTH 5
#define ZOMBIES_HEIGHT 8
#define PROJECTILES_MAX 10
#define MIN_Y 5
#define MAX_Y 58
#define WALL_X 16
#define PLAYER_START_X 8
#define PLAYER_START_Y (MAX_Y - MIN_Y) / 2
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef enum { GameStatePlaying, GameStateGameOver } GameState;
typedef struct {
int x;
int y;
} Point;
typedef struct {
Point position;
int hp;
} Player;
typedef struct {
Point position;
int hp;
} Zombie;
typedef struct {
Point position;
} Projectile;
typedef struct {
GameState game_state;
Player player;
size_t zombies_count;
Zombie* zombies[ZOMBIES_MAX];
size_t projectiles_count;
Projectile* projectiles[PROJECTILES_MAX];
uint16_t score;
bool input_shoot;
} PluginState;
static void render_callback(Canvas* const canvas, void* ctx) {
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
if(plugin_state == NULL) {
return;
}
canvas_draw_frame(canvas, 0, 0, 128, 64);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas,
plugin_state->player.position.x,
plugin_state->player.position.y,
AlignCenter,
AlignCenter,
"@");
canvas_draw_line(canvas, WALL_X, 0, WALL_X, 64);
canvas_draw_line(canvas, WALL_X + 2, 4, WALL_X + 2, 59);
for(int i = 0; i < PROJECTILES_MAX; ++i) {
Projectile* p = plugin_state->projectiles[i];
if(p != NULL) {
canvas_draw_disc(canvas, p->position.x, p->position.y, 3);
}
}
for(int i = 0; i < ZOMBIES_MAX; ++i) {
Zombie* z = plugin_state->zombies[i];
if(z != NULL) {
for(int h = 0; h < ZOMBIES_HEIGHT; h++) {
for(int w = 0; w < ZOMBIES_WIDTH; w++) {
// Switch animation
int zIdx = 0;
if(z->position.x % 2 == 0) {
zIdx = 1;
}
// Draw zombie pixels
if(zombie_array[zIdx][h][w] == 1) {
int x = z->position.x + w;
int y = z->position.y + h;
canvas_draw_dot(canvas, x, y);
}
}
}
}
}
int heart;
if((plugin_state->player.hp - 10) > 5) { // 16, 17, 18, 19, 20
heart = 0;
} else if((plugin_state->player.hp - 5) > 5) { // 11, 12, 13, 14, 15
heart = 1;
} else if((plugin_state->player.hp - 3) > 2) { // 6, 7, 8, 9, 10
heart = 2;
} else if(plugin_state->player.hp > 0) { // 1, 2, 3, 4, 5
heart = 3;
} else { // 0
heart = 4;
}
// visual representation of health
for(int h = 0; h < 5; h++) {
for(int w = 0; w < 5; w++) {
if(heart_array[heart][h][w] == 1) {
int x = 124 - w;
int y = 56 + h;
canvas_draw_dot(canvas, x, y);
}
}
}
// buffer hp + score
char hpBuffer[8];
char scoreBuffer[14];
if(plugin_state->game_state == GameStatePlaying) {
// display ammo / reload
if(plugin_state->projectiles_count >= PROJECTILES_MAX) {
canvas_draw_str_aligned(canvas, 24, 10, AlignLeft, AlignCenter, "RELOAD");
} else {
for(uint8_t i = 0; i < (PROJECTILES_MAX - plugin_state->projectiles_count); i++) {
canvas_draw_box(canvas, 24 + (4 * i), 6, 2, 4);
}
}
// display hp + score
snprintf(hpBuffer, sizeof(hpBuffer), "%u", plugin_state->player.hp);
canvas_draw_str_aligned(canvas, 118, 62, AlignRight, AlignBottom, hpBuffer);
snprintf(scoreBuffer, sizeof(scoreBuffer), "%u", plugin_state->score);
canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, scoreBuffer);
}
// Game Over banner
if(plugin_state->game_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);
snprintf(scoreBuffer, sizeof(scoreBuffer), "Score: %u", plugin_state->score);
canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, scoreBuffer);
}
//char* info = (char*)malloc(16 * sizeof(char));
//asprintf(&info, "%d, %d", plugin_state->x, plugin_state->y);
//canvas_draw_str_aligned(canvas, 32, 16, AlignLeft, AlignBottom, info);
//free(info);
release_mutex((ValueMutex*)ctx, plugin_state);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void tick(PluginState* const plugin_state) {
if(plugin_state->input_shoot && (plugin_state->projectiles_count < PROJECTILES_MAX)) {
Projectile* p = (Projectile*)malloc(sizeof(Projectile));
p->position.x = plugin_state->player.position.x;
p->position.y = plugin_state->player.position.y;
size_t idx = plugin_state->projectiles_count;
plugin_state->projectiles[idx] = p;
plugin_state->projectiles_count += 1;
}
for(int i = 0; i < ZOMBIES_MAX; ++i) {
if(!plugin_state->zombies[i]) {
Zombie* z = (Zombie*)malloc(sizeof(Zombie));
//z->hp = 20;
z->position.x = 126;
z->position.y = MIN_Y + (rand() % (MAX_Y - MIN_Y));
plugin_state->zombies[i] = z;
plugin_state->zombies_count += 1;
}
}
for(int i = 0; i < PROJECTILES_MAX; ++i) {
Projectile* p = plugin_state->projectiles[i];
if(p != NULL) {
p->position.x += 2;
for(int i = 0; i < ZOMBIES_MAX; ++i) {
Zombie* z = plugin_state->zombies[i];
if(z != NULL) {
if( // projectile close enough to zombie
(((z->position.x - p->position.x) <= 2) &&
((z->position.y - p->position.y) <= 4)) &&
(((p->position.x - z->position.x) <= 2) &&
((p->position.y - z->position.y) <= 6))) {
//z->hp -= 5;
//if(z->hp <= 0) {
plugin_state->zombies_count -= 1;
free(z);
plugin_state->zombies[i] = NULL;
plugin_state->score++;
//if(plugin_state->score % 15 == 0) DOLPHIN_DEED(getRandomDeed());
//}
} else if(z->position.x <= WALL_X && z->position.x > 0) { // zombie got to the wall
plugin_state->zombies_count -= 1;
free(z);
plugin_state->zombies[i] = NULL;
if(plugin_state->player.hp > 0) {
plugin_state->player.hp--;
} else {
plugin_state->game_state = GameStateGameOver;
}
} else {
if(furi_get_tick() % 2 == 0) z->position.x--;
}
}
}
if(p->position.x >= 128) {
free(p);
plugin_state->projectiles[i] = NULL;
}
}
}
plugin_state->input_shoot = false;
}
static void timer_callback(void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
PluginEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
static void zombiez_state_init(PluginState* const plugin_state) {
plugin_state->player.position.x = PLAYER_START_X;
plugin_state->player.position.y = PLAYER_START_Y;
plugin_state->player.hp = 20;
plugin_state->projectiles_count = 0;
plugin_state->zombies_count = 0;
plugin_state->score = 0;
for(int i = 0; i < PROJECTILES_MAX; i++) {
plugin_state->projectiles[i] = NULL;
}
for(int i = 0; i < ZOMBIES_MAX; i++) {
plugin_state->zombies[i] = NULL;
}
plugin_state->game_state = GameStatePlaying;
plugin_state->input_shoot = false;
}
int32_t zombiez_game_app(void* p) {
UNUSED(p);
uint32_t return_code = 0;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
PluginState* plugin_state = malloc(sizeof(PluginState));
zombiez_state_init(plugin_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
FURI_LOG_E("Zombiez", "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, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
// 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);
PluginEvent event;
bool isRunning = true;
while(isRunning) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyUp:
if(plugin_state->player.position.y > MIN_Y &&
plugin_state->game_state == GameStatePlaying) {
plugin_state->player.position.y--;
}
break;
case InputKeyDown:
if(plugin_state->player.position.y < MAX_Y &&
plugin_state->game_state == GameStatePlaying) {
plugin_state->player.position.y++;
}
break;
case InputKeyOk:
if(plugin_state->projectiles_count < PROJECTILES_MAX &&
plugin_state->game_state == GameStatePlaying) {
plugin_state->input_shoot = true;
}
break;
case InputKeyBack:
break;
default:
break;
}
} else if(
event.input.type == InputTypeRepeat &&
plugin_state->game_state == GameStatePlaying) {
switch(event.input.key) {
case InputKeyUp:
if(plugin_state->player.position.y > (MIN_Y + 1)) {
plugin_state->player.position.y -= 4;
}
break;
case InputKeyDown:
if(plugin_state->player.position.y < (MAX_Y - 1)) {
plugin_state->player.position.y += 4;
}
break;
default:
break;
}
} else if(event.input.type == InputTypeLong) {
if(event.input.key == InputKeyOk) {
if(plugin_state->game_state == GameStateGameOver) {
zombiez_state_init(plugin_state);
} else if(plugin_state->projectiles_count >= PROJECTILES_MAX) {
plugin_state->projectiles_count = 0;
plugin_state->player.hp++;
}
} else if(event.input.key == InputKeyBack) {
isRunning = false;
}
}
} else if(event.type == EventTypeTick) {
tick(plugin_state);
}
} else {
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_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:
free(plugin_state);
furi_message_queue_free(event_queue);
return return_code;
}