Ivan Barsukov 1 год назад
Родитель
Сommit
c6d911f697

+ 116 - 0
src/levels/level_game/context_menu.c

@@ -0,0 +1,116 @@
+#include "context_menu.h"
+
+#include <stddef.h>
+
+#include <furi.h>
+#include <gui/modules/variable_item_list.h>
+
+#include "../../engine/game_manager.h"
+#include "../../gui_bridge/input_converter.h"
+
+// #include "../../game.h"
+// #include "../../game_settings.h"
+
+typedef struct
+{
+    InputConverter* input_converter;
+
+    ContextMenuBackCallback back_callback;
+    void* back_callback_context;
+
+    size_t current_index;
+
+} ContextMenuContext;
+
+void
+context_menu_back_callback_set(Entity* self,
+                               ContextMenuBackCallback back_callback,
+                               void* callback_context)
+{
+    ContextMenuContext* entity_context = entity_context_get(self);
+    entity_context->back_callback = back_callback;
+    entity_context->back_callback_context = callback_context;
+}
+
+static void
+context_menu_start(Entity* self, GameManager* manager, void* _entity_context)
+{
+    UNUSED(self);
+    UNUSED(manager);
+
+    ContextMenuContext* entity_context = _entity_context;
+
+    // Alloc converter
+    entity_context->input_converter = input_converter_alloc();
+
+    // Callback
+    entity_context->back_callback = NULL;
+    entity_context->back_callback_context = NULL;
+}
+
+static void
+context_menu_stop(Entity* self, GameManager* manager, void* _entity_context)
+{
+    UNUSED(self);
+    UNUSED(manager);
+
+    ContextMenuContext* entity_context = _entity_context;
+    input_converter_free(entity_context->input_converter);
+}
+
+static void
+context_menu_update(Entity* self, GameManager* manager, void* _entity_context)
+{
+    UNUSED(self);
+    ContextMenuContext* entity_context = _entity_context;
+
+    InputState input = game_manager_input_get(manager);
+    input_converter_process_state(entity_context->input_converter, &input);
+
+    InputEvent event;
+    while (input_converter_get_event(entity_context->input_converter, &event) ==
+           FuriStatusOk) {
+        if (event.key == InputKeyBack && event.type == InputTypeShort &&
+            entity_context->back_callback != NULL) {
+            entity_context->back_callback(
+              entity_context->back_callback_context);
+            return;
+        }
+    }
+}
+
+static void
+gray_canvas(Canvas* const canvas)
+{
+    // canvas_set_color(canvas, ColorWhite);
+    canvas_clear(canvas);
+    for (size_t x = 0; x < 128; x += 2) {
+        for (size_t y = 0; y < 64; y++) {
+            canvas_draw_dot(canvas, x + (y % 2 == 1 ? 0 : 1), y);
+        }
+    }
+    // canvas_set_color(canvas, ColorBlack);
+}
+
+static void
+context_menu_render(Entity* self,
+                    GameManager* manager,
+                    Canvas* canvas,
+                    void* _entity_context)
+{
+    UNUSED(self);
+    UNUSED(manager);
+    UNUSED(_entity_context);
+
+    gray_canvas(canvas);
+}
+
+const EntityDescription context_menu_description = {
+    .start = context_menu_start,
+    .stop = context_menu_stop,
+    .update = context_menu_update,
+    .render = context_menu_render,
+    .collision = NULL,
+    .event = NULL,
+    .context_size = sizeof(ContextMenuContext),
+};

+ 12 - 0
src/levels/level_game/context_menu.h

@@ -0,0 +1,12 @@
+#pragma once
+
+#include "../../engine/entity.h"
+
+typedef void (*ContextMenuBackCallback)(void* context);
+
+void
+context_menu_back_callback_set(Entity* entity,
+                               ContextMenuBackCallback back_callback,
+                               void* callback_context);
+
+extern const EntityDescription context_menu_description;

+ 134 - 0
src/levels/level_game/enemy_entity.c

@@ -0,0 +1,134 @@
+#include "enemy_entity.h"
+
+#include "../../engine/game_manager.h"
+#include "../../game.h"
+
+#include "level_game.h"
+#include "player_entity.h"
+
+#define ENEMY_SIZE 6
+
+static Vector
+random_pos(void)
+{
+    return (Vector){ rand() % 120 + 4, rand() % 58 + 4 };
+}
+
+void
+spawn_enemy(GameManager* manager)
+{
+    GameContext* game_context = game_manager_game_context_get(manager);
+    Level* level = game_manager_current_level_get(manager);
+    Entity* enemy = level_add_entity(level, &enemy_description);
+
+    // Add to enemies list
+    GameLevelContext* level_context = level_context_get(level);
+    EntityList_push_back(level_context->enemies, enemy);
+
+    // Set enemy position
+    entity_pos_set(enemy, random_pos());
+
+    // Add collision rect to enemy entity
+    entity_collider_add_rect(enemy, ENEMY_SIZE, ENEMY_SIZE);
+
+    // Load target sprite
+    EnemyContext* enemy_context = entity_context_get(enemy);
+    enemy_context->sprite = game_manager_sprite_load(manager, "enemy.fxbm");
+    enemy_context->direction = (EnemyDirection)(rand() % 4);
+
+    float speed;
+    switch (game_context->difficulty) {
+        case DifficultyEasy:
+            speed = 0.25f;
+            break;
+        case DifficultyHard:
+            speed = 1.0f;
+            break;
+        case DifficultyInsane:
+            speed = 1.5f;
+            break;
+        default:
+            speed = 0.5f;
+            break;
+    }
+    enemy_context->speed = speed;
+}
+
+static void
+enemy_update(Entity* self, GameManager* manager, void* context)
+{
+    Level* level = game_manager_current_level_get(manager);
+    GameLevelContext* level_context = level_context_get(level);
+    if (level_context->is_paused) {
+        return;
+    }
+
+    EnemyContext* enemy_context = context;
+    Vector pos = entity_pos_get(self);
+
+    // Control player movement
+    if (enemy_context->direction == EnemyDirectionUp)
+        pos.y -= enemy_context->speed;
+    if (enemy_context->direction == EnemyDirectionDown)
+        pos.y += enemy_context->speed;
+    if (enemy_context->direction == EnemyDirectionLeft)
+        pos.x -= enemy_context->speed;
+    if (enemy_context->direction == EnemyDirectionRight)
+        pos.x += enemy_context->speed;
+
+    // Clamp player position to screen bounds, and set it
+    pos.x = CLAMP(pos.x, 125, 3);
+    pos.y = CLAMP(pos.y, 61, 3);
+    entity_pos_set(self, pos);
+
+    if (enemy_context->direction == EnemyDirectionUp && pos.y <= 3.0f)
+        enemy_context->direction = EnemyDirectionDown;
+    else if (enemy_context->direction == EnemyDirectionDown && pos.y >= 61.0f)
+        enemy_context->direction = EnemyDirectionUp;
+    else if (enemy_context->direction == EnemyDirectionLeft && pos.x <= 3.0f)
+        enemy_context->direction = EnemyDirectionRight;
+    else if (enemy_context->direction == EnemyDirectionRight && pos.x >= 125.0f)
+        enemy_context->direction = EnemyDirectionLeft;
+}
+
+static void
+enemy_render(Entity* self, GameManager* manager, Canvas* canvas, void* context)
+{
+    UNUSED(context);
+    UNUSED(manager);
+
+    // Get enemy position
+    Vector pos = entity_pos_get(self);
+
+    // Draw enemy
+    EnemyContext* enemy_context = entity_context_get(self);
+    canvas_draw_sprite(canvas, enemy_context->sprite, pos.x - 3, pos.y - 3);
+}
+
+static void
+enemy_collision(Entity* self,
+                Entity* other,
+                GameManager* manager,
+                void* context)
+{
+    UNUSED(self);
+    UNUSED(context);
+
+    if (entity_description_get(other) != &player_description) {
+        return;
+    }
+
+    // Game over
+    GameContext* game_context = game_manager_game_context_get(manager);
+    game_manager_next_level_set(manager, game_context->levels.game_over);
+}
+
+const EntityDescription enemy_description = {
+    .start = NULL,
+    .stop = NULL,
+    .update = enemy_update,
+    .render = enemy_render,
+    .collision = enemy_collision,
+    .event = NULL,
+    .context_size = sizeof(EnemyContext),
+};

+ 25 - 0
src/levels/level_game/enemy_entity.h

@@ -0,0 +1,25 @@
+#pragma once
+
+#include "../../engine/entity.h"
+
+typedef struct Sprite Sprite;
+
+typedef enum
+{
+    EnemyDirectionUp,
+    EnemyDirectionDown,
+    EnemyDirectionLeft,
+    EnemyDirectionRight,
+} EnemyDirection;
+
+typedef struct
+{
+    Sprite* sprite;
+    EnemyDirection direction;
+    float speed;
+} EnemyContext;
+
+void
+spawn_enemy(GameManager* manager);
+
+extern const EntityDescription enemy_description;

+ 224 - 0
src/levels/level_game/level_game.c

@@ -0,0 +1,224 @@
+#include "level_game.h"
+
+#include <stddef.h>
+
+#include <m-list.h>
+
+#include <dolphin/dolphin.h>
+#include <notification/notification_messages.h>
+
+#include "../../engine/vector.h"
+
+#include "../../game.h"
+#include "../../game_notifications.h"
+
+#include "context_menu.h"
+
+#include "enemy_entity.h"
+#include "player_entity.h"
+
+#define TARGET_ANIMATION_DURATION 30.0f
+#define TARGET_SIZE 1
+#define TARGET_RADIUS 1
+#define TARGET_ANIMATION_RADIUS 10
+
+static Vector
+random_pos(void)
+{
+    return (Vector){ rand() % 120 + 4, rand() % 58 + 4 };
+}
+
+/****** Entities: Target ******/
+
+typedef struct
+{
+    Sprite* sprite;
+    float time;
+} TargetContext;
+
+static const EntityDescription target_description;
+
+static Entity*
+create_target(Level* level, GameManager* manager)
+{
+    Entity* target = level_add_entity(level, &target_description);
+
+    // Set target position
+    entity_pos_set(target, random_pos());
+
+    // Add collision circle to target entity
+    entity_collider_add_circle(target, TARGET_RADIUS);
+
+    // Load target sprite
+    TargetContext* target_context = entity_context_get(target);
+    target_context->sprite = game_manager_sprite_load(manager, "target.fxbm");
+
+    return target;
+}
+
+static void
+reset_target(Entity* target)
+{
+    // Set player position.
+    entity_pos_set(target, random_pos());
+
+    // Reset animation
+    TargetContext* target_context = entity_context_get(target);
+    target_context->time = 0.0f;
+}
+
+static void
+target_update(Entity* self, GameManager* manager, void* context)
+{
+    UNUSED(self);
+    UNUSED(manager);
+
+    // Start level animation
+    TargetContext* target = context;
+    if (target->time >= TARGET_ANIMATION_DURATION) {
+        return;
+    }
+    target->time += 1.0f;
+}
+
+static void
+target_render(Entity* self, GameManager* manager, Canvas* canvas, void* context)
+{
+    UNUSED(context);
+    UNUSED(manager);
+
+    // Get target position
+    Vector pos = entity_pos_get(self);
+
+    TargetContext* target = context;
+    if (target->time < TARGET_ANIMATION_DURATION) {
+        float step = TARGET_ANIMATION_RADIUS / TARGET_ANIMATION_DURATION;
+        float radius = CLAMP(TARGET_ANIMATION_RADIUS - target->time * step,
+                             TARGET_ANIMATION_RADIUS,
+                             TARGET_RADIUS);
+        canvas_draw_circle(canvas, pos.x, pos.y, radius);
+        canvas_draw_dot(canvas, pos.x, pos.y);
+        return;
+    }
+
+    // Draw target
+    TargetContext* target_context = entity_context_get(self);
+    canvas_draw_sprite(canvas, target_context->sprite, pos.x - 1, pos.y - 1);
+}
+
+static void
+target_collision(Entity* self,
+                 Entity* other,
+                 GameManager* manager,
+                 void* context)
+{
+    UNUSED(context);
+
+    if (entity_description_get(other) != &player_description) {
+        return;
+    }
+
+    // Increase score
+    GameContext* game_context = game_manager_game_context_get(manager);
+    ++game_context->score;
+
+    // Move target to new random position
+    reset_target(self);
+    spawn_enemy(manager);
+
+    // Notify
+    game_notify(game_context, &sequence_earn_point);
+}
+
+static const EntityDescription target_description = {
+    .start = NULL,
+    .stop = NULL,
+    .update = target_update,
+    .render = target_render,
+    .collision = target_collision,
+    .event = NULL,
+    .context_size = sizeof(TargetContext),
+};
+
+/***** Level *****/
+
+static void
+level_game_alloc(Level* level, GameManager* manager, void* context)
+{
+    GameLevelContext* level_context = context;
+
+    // Add entities to the level
+    level_context->player = player_spawn(level, manager);
+    level_context->target = create_target(level, manager);
+    EntityList_init(level_context->enemies);
+    level_context->is_paused = false;
+    level_context->pause_menu = NULL;
+}
+
+static void
+level_game_start(Level* level, GameManager* manager, void* context)
+{
+    UNUSED(level);
+
+    dolphin_deed(DolphinDeedPluginGameStart);
+
+    GameContext* game_context = game_manager_game_context_get(manager);
+    game_context->score = 0;
+
+    GameLevelContext* level_context = context;
+    player_respawn(level_context->player);
+    reset_target(level_context->target);
+
+    FURI_LOG_D(GAME_NAME, "Game level started");
+}
+
+static void
+level_game_stop(Level* level, GameManager* manager, void* context)
+{
+    UNUSED(manager);
+
+    GameLevelContext* level_context = context;
+    FOREACH(item, level_context->enemies)
+    {
+        level_remove_entity(level, *item);
+    }
+    EntityList_clear(level_context->enemies);
+
+    resume_game(level);
+}
+
+void
+pause_game(Level* level)
+{
+    GameLevelContext* level_context = level_context_get(level);
+    if (level_context->is_paused) {
+        return;
+    }
+
+    level_context->is_paused = true;
+    level_context->pause_menu =
+      level_add_entity(level, &context_menu_description);
+    context_menu_back_callback_set(
+      level_context->pause_menu, (ContextMenuBackCallback)resume_game, level);
+}
+
+void
+resume_game(Level* level)
+{
+    GameLevelContext* level_context = level_context_get(level);
+    if (!level_context->is_paused) {
+        return;
+    }
+
+    level_context->is_paused = false;
+    level_remove_entity(level, level_context->pause_menu);
+    level_context->pause_menu = NULL;
+}
+
+const LevelBehaviour level_game = {
+    .alloc = level_game_alloc,
+    .free = NULL,
+    .start = level_game_start,
+    .stop = level_game_stop,
+    .context_size = sizeof(GameLevelContext),
+};

+ 28 - 0
src/levels/level_game/level_game.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <m-list.h>
+
+#include "../../engine/level.h"
+
+LIST_DEF(EntityList, Entity*, M_POD_OPLIST);
+#define M_OPL_EntityList_t() LIST_OPLIST(EntityList)
+#define FOREACH(name, list) for                                                \
+    M_EACH(name, list, EntityList_t)
+
+typedef struct
+{
+    Entity* player;
+    Entity* target;
+    EntityList_t enemies;
+
+    bool is_paused;
+    Entity* pause_menu;
+
+} GameLevelContext;
+
+void
+pause_game(Level* level);
+void
+resume_game(Level* level);
+
+extern const LevelBehaviour level_game;

+ 122 - 0
src/levels/level_game/player_entity.c

@@ -0,0 +1,122 @@
+#include "player_entity.h"
+
+#include "../../engine/game_manager.h"
+#include "../../game.h"
+
+#include "level_game.h"
+
+#define PLAYER_SIZE 6
+#define PLAYER_SPEED 1
+#define PLAYER_INITIAL_X 64
+#define PLAYER_INITIAL_Y 32
+#define PLAYER_ANIMATION_DURATION 15.0f
+
+Entity*
+player_spawn(Level* level, GameManager* manager)
+{
+    Entity* player = level_add_entity(level, &player_description);
+
+    // Set position and collider rect
+    entity_pos_set(player, (Vector){ PLAYER_INITIAL_X, PLAYER_INITIAL_Y });
+    entity_collider_add_rect(player, PLAYER_SIZE, PLAYER_SIZE);
+
+    // Load player sprite
+    PlayerContext* player_context = entity_context_get(player);
+    player_context->sprite = game_manager_sprite_load(manager, "player.fxbm");
+
+    return player;
+}
+
+void
+player_respawn(Entity* player)
+{
+    // Set player position.
+    entity_pos_set(player, (Vector){ PLAYER_INITIAL_X, PLAYER_INITIAL_Y });
+
+    // Reset animation
+    PlayerContext* player_context = entity_context_get(player);
+    player_context->time = 0.0f;
+}
+
+static void
+player_update(Entity* self, GameManager* manager, void* _entity_context)
+{
+    // Check pause
+    Level* level = game_manager_current_level_get(manager);
+    GameLevelContext* level_context = level_context_get(level);
+    if (level_context->is_paused) {
+        return;
+    }
+
+    // Start level animation
+    PlayerContext* player = _entity_context;
+    if (player->time < PLAYER_ANIMATION_DURATION) {
+        player->time += 1.0f;
+        return;
+    }
+
+    InputState input = game_manager_input_get(manager);
+    Vector pos = entity_pos_get(self);
+
+    // Control player movement
+    if (input.held & GameKeyUp)
+        pos.y -= PLAYER_SPEED;
+    if (input.held & GameKeyDown)
+        pos.y += PLAYER_SPEED;
+    if (input.held & GameKeyLeft)
+        pos.x -= PLAYER_SPEED;
+    if (input.held & GameKeyRight)
+        pos.x += PLAYER_SPEED;
+
+    // Clamp player position to screen bounds, and set it
+    pos.x = CLAMP(pos.x, 125, 3);
+    pos.y = CLAMP(pos.y, 61, 3);
+    entity_pos_set(self, pos);
+
+    // Control game pause
+    if (input.pressed & GameKeyBack) {
+        pause_game(level);
+    }
+}
+
+static void
+player_render(Entity* self,
+              GameManager* manager,
+              Canvas* canvas,
+              void* _entity_context)
+{
+    PlayerContext* player = _entity_context;
+
+    // Draw initial animation
+    if (player->time < PLAYER_ANIMATION_DURATION) {
+        float step = 61 / PLAYER_ANIMATION_DURATION;
+        float left_x = player->time * step;
+        float right_x = 122 - left_x;
+        left_x = CLAMP(left_x, 61, 0);
+        right_x = CLAMP(right_x, 122, 61);
+
+        canvas_draw_sprite(canvas, player->sprite, left_x, 29);
+        canvas_draw_sprite(canvas, player->sprite, right_x, 29);
+
+        return;
+    }
+
+    // Draw player sprite
+    Vector pos = entity_pos_get(self);
+    canvas_draw_sprite(canvas, player->sprite, pos.x - 3, pos.y - 3);
+
+    // Draw score
+    GameContext* game_context = game_manager_game_context_get(manager);
+    canvas_printf_aligned(
+      canvas, 64, 0, AlignCenter, AlignTop, "%lu", game_context->score);
+}
+
+const EntityDescription player_description = {
+    .start = NULL,
+    .stop = NULL,
+    .update = player_update,
+    .render = player_render,
+    .collision = NULL,
+    .event = NULL,
+    .context_size = sizeof(PlayerContext),
+};

+ 18 - 0
src/levels/level_game/player_entity.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include "../../engine/entity.h"
+
+typedef struct Sprite Sprite;
+
+typedef struct
+{
+    Sprite* sprite;
+    float time;
+} PlayerContext;
+
+Entity*
+player_spawn(Level* level, GameManager* manager);
+void
+player_respawn(Entity* player);
+
+extern const EntityDescription player_description;