2022-09-15 01:42:17 +00:00
|
|
|
#include <furi.h>
|
|
|
|
#include <gui/gui.h>
|
|
|
|
#include <input/input.h>
|
|
|
|
#include <stdlib.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) {
|
2022-09-15 02:17:19 +00:00
|
|
|
canvas_draw_disc(canvas, p->position.x, p->position.y, 3);
|
2022-09-15 01:42:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
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)) {
|
2022-09-15 02:17:19 +00:00
|
|
|
plugin_state->player.position.y -= 4;
|
2022-09-15 01:42:17 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case InputKeyDown:
|
|
|
|
if(plugin_state->player.position.y < (MAX_Y - 1)) {
|
2022-09-15 02:17:19 +00:00
|
|
|
plugin_state->player.position.y += 4;
|
2022-09-15 01:42:17 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|