Просмотр исходного кода

Merge pull request #3 from jblanked/dev_0.1

Merge working version
JBlanked 1 год назад
Родитель
Сommit
744b9c1eb2

+ 0 - 2
alloc/alloc.c

@@ -25,8 +25,6 @@ FlipWorldApp *flip_world_app_alloc()
         return NULL;
     }
 
-    view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_world_custom_event_callback);
-
     // Submenu
     if (!easy_flipper_set_submenu(&app->submenu, FlipWorldViewSubmenu, VERSION_TAG, callback_exit_app, &app->view_dispatcher))
     {

+ 1 - 1
application.fam

@@ -6,7 +6,7 @@ App(
     stack_size=4 * 1024,
     fap_icon="app.png",
     fap_category="GPIO",
-    fap_description="Open World Multiplayer game, best played with the VGM."
+    fap_description="Open World Multiplayer game, best played with the VGM.",
     fap_icon_assets="assets",
     fap_file_assets="assets", # Do not touch this and the next line, it is needed to generate sprites
     fap_extbuild=(

BIN
assets/icon_earth.png


BIN
assets/icon_flower.png


BIN
assets/icon_home.png


BIN
assets/icon_info.png


BIN
assets/icon_man.png


BIN
assets/icon_plant.png


BIN
assets/icon_tree.png


BIN
assets/icon_woman.png


+ 36 - 61
callback/callback.c

@@ -64,7 +64,6 @@ int32_t game_app(void *p)
 }
 
 static bool alloc_about_view(void *context);
-static bool alloc_main_view(void *context);
 static bool alloc_text_input_view(void *context, char *title);
 static bool alloc_variable_item_list(void *context);
 //
@@ -83,14 +82,6 @@ static uint32_t callback_to_wifi_settings(void *context)
     return FlipWorldViewSettings;
 }
 
-// Callback for drawing the main screen
-static void flip_world_view_game_draw_callback(Canvas *canvas, void *model)
-{
-    UNUSED(model);
-    canvas_clear(canvas);
-    canvas_set_font_custom(canvas, FONT_SIZE_XLARGE);
-    canvas_draw_str(canvas, 0, 10, "Game");
-}
 static void flip_world_view_about_draw_callback(Canvas *canvas, void *model)
 {
     UNUSED(model);
@@ -128,27 +119,6 @@ static bool alloc_about_view(void *context)
     return true;
 }
 
-static bool alloc_main_view(void *context)
-{
-    FlipWorldApp *app = (FlipWorldApp *)context;
-    if (!app)
-    {
-        FURI_LOG_E(TAG, "FlipWorldApp is NULL");
-        return false;
-    }
-    if (!app->view_main)
-    {
-        if (!easy_flipper_set_view(&app->view_main, FlipWorldViewMain, flip_world_view_game_draw_callback, NULL, callback_to_submenu, &app->view_dispatcher, app))
-        {
-            return false;
-        }
-        if (!app->view_main)
-        {
-            return false;
-        }
-    }
-    return true;
-}
 static bool alloc_text_input_view(void *context, char *title)
 {
     FlipWorldApp *app = (FlipWorldApp *)context;
@@ -317,6 +287,8 @@ static void free_variable_item_list(void *context)
         app->variable_item_pass = NULL;
     }
 }
+static FuriThreadId thread_id;
+static bool game_thread_running = false;
 void free_all_views(void *context, bool should_free_variable_item_list)
 {
     FlipWorldApp *app = (FlipWorldApp *)context;
@@ -332,36 +304,18 @@ void free_all_views(void *context, bool should_free_variable_item_list)
     free_about_view(app);
     free_main_view(app);
     free_text_input_view(app);
-}
-
-static void flip_world_loader_process_callback(void *context)
-{
-    FlipWorldApp *app = (FlipWorldApp *)context;
-    if (!app)
-    {
-        FURI_LOG_E(TAG, "FlipWorldApp is NULL");
-        return;
-    }
-
-    // load game
-    game_app(NULL);
-}
-
-bool flip_world_custom_event_callback(void *context, uint32_t index)
-{
-    if (!context)
+    if (app->view_main)
     {
-        FURI_LOG_E(TAG, "context is NULL");
-        return false;
+        view_free(app->view_main);
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewMain);
+        app->view_main = NULL;
     }
-    switch (index)
+    // free game thread
+    if (game_thread_running)
     {
-    case FlipWorldCustomEventPlay:
-        // free_all_views(app, true);
-        flip_world_loader_process_callback(context);
-        return true;
-    default:
-        return false;
+        game_thread_running = false;
+        furi_thread_flags_set(thread_id, WorkerEvtStop);
+        furi_thread_free(thread_id);
     }
 }
 
@@ -376,14 +330,35 @@ void callback_submenu_choices(void *context, uint32_t index)
     switch (index)
     {
     case FlipWorldSubmenuIndexRun:
+        // free game thread
+        if (game_thread_running)
+        {
+            game_thread_running = false;
+            furi_thread_flags_set(thread_id, WorkerEvtStop);
+            furi_thread_free(thread_id);
+        }
         free_all_views(app, true);
-        if (!alloc_main_view(app))
+        if (!app->view_main)
+        {
+            if (!easy_flipper_set_view(&app->view_main, FlipWorldViewMain, NULL, NULL, callback_to_submenu, &app->view_dispatcher, app))
+            {
+                return;
+            }
+            if (!app->view_main)
+            {
+                return;
+            }
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMain);
+        FuriThread *thread = furi_thread_alloc_ex("game", 4096, game_app, app);
+        if (!thread)
         {
-            FURI_LOG_E(TAG, "Failed to allocate main view");
+            FURI_LOG_E(TAG, "Failed to allocate game thread");
             return;
         }
-        // view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMain);
-        view_dispatcher_send_custom_event(app->view_dispatcher, FlipWorldCustomEventPlay);
+        furi_thread_start(thread);
+        thread_id = furi_thread_get_id(thread);
+        game_thread_running = true;
         break;
     case FlipWorldSubmenuIndexAbout:
         free_all_views(app, true);

+ 0 - 1
callback/callback.h

@@ -11,5 +11,4 @@
 #include "engine/entity_i.h"
 
 void free_all_views(void *context, bool free_variable_item_list);
-bool flip_world_custom_event_callback(void *context, uint32_t index);
 void callback_submenu_choices(void *context, uint32_t index);

+ 262 - 28
game.c

@@ -1,10 +1,104 @@
 #include "game.h"
+#include "flip_world.h"
+#include "flip_world_icons.h"
+
+Wall walls[] = {
+    WALL(true, 12, 0, 3),
+    WALL(false, 3, 3, 17),
+    WALL(false, 23, 3, 6),
+    WALL(true, 3, 4, 57),
+    WALL(true, 28, 4, 56),
+    WALL(false, 4, 7, 5),
+    WALL(false, 12, 7, 13),
+    WALL(true, 8, 8, 34),
+    WALL(true, 12, 8, 42),
+    WALL(true, 24, 8, 8),
+    WALL(true, 16, 11, 8),
+    WALL(false, 17, 11, 4),
+    WALL(true, 20, 12, 22),
+    WALL(false, 6, 17, 2),
+    WALL(true, 24, 19, 15),
+    WALL(true, 16, 22, 16),
+    WALL(false, 4, 24, 1),
+    WALL(false, 21, 28, 2),
+    WALL(false, 6, 33, 2),
+    WALL(false, 13, 34, 3),
+    WALL(false, 17, 37, 11),
+    WALL(true, 16, 41, 14),
+    WALL(false, 20, 41, 5),
+    WALL(true, 20, 45, 12),
+    WALL(true, 24, 45, 12),
+    WALL(false, 4, 46, 2),
+    WALL(false, 9, 46, 3),
+    WALL(false, 6, 50, 3),
+    WALL(true, 12, 53, 7),
+    WALL(true, 8, 54, 6),
+    WALL(false, 4, 60, 19),
+    WALL(false, 26, 60, 6),
+};
+
+// Global variables to store camera position
+static int camera_x = 0;
+static int camera_y = 0;
+
+// Background rendering function
+static void background_render(Canvas *canvas, Vector pos)
+{
+    // Clear the canvas
+    canvas_clear(canvas);
+
+    // Calculate camera offset to center the player
+    camera_x = pos.x - (SCREEN_WIDTH / 2);
+    camera_y = pos.y - (SCREEN_HEIGHT / 2);
+
+    // Clamp camera position to prevent showing areas outside the world
+    camera_x = CLAMP(camera_x, WORLD_WIDTH - SCREEN_WIDTH, 0);
+    camera_y = CLAMP(camera_y, WORLD_HEIGHT - SCREEN_HEIGHT, 0);
+
+    // Draw the outer bounds adjusted by camera offset
+    canvas_draw_frame(canvas, -camera_x, -camera_y, WORLD_WIDTH, WORLD_HEIGHT);
+
+    // Draw other elements adjusted by camera offset
+    // Static Dot at (72, 40)
+    canvas_draw_dot(canvas, 72 - camera_x, 40 - camera_y);
+
+    // Static Circle at (16, 16) with radius 4
+    canvas_draw_circle(canvas, 16 - camera_x, 16 - camera_y, 4);
+
+    // Static 8x8 Rectangle Frame at (96, 48)
+    canvas_draw_frame(canvas, 96 - camera_x, 48 - camera_y, 8, 8);
+
+    // Static earth icon at (112, 56)
+    canvas_draw_icon(canvas, 112 - camera_x, 56 - camera_y, &I_icon_earth);
+
+    // static home icon at (128, 24)
+    canvas_draw_icon(canvas, 128 - camera_x, 24 - camera_y, &I_icon_home);
+
+    // static menu icon at (144, 24)
+    canvas_draw_icon(canvas, 144 - camera_x, 24 - camera_y, &I_icon_info);
+
+    // static man icon at (160, 56)
+    canvas_draw_icon(canvas, 160 - camera_x, 56 - camera_y, &I_icon_man);
+
+    // static plant icon at (176, 32)
+    canvas_draw_icon(canvas, 176 - camera_x, 32 - camera_y, &I_icon_plant);
+
+    // static tree icon at (104, 40)
+    canvas_draw_icon(canvas, 192 - camera_x, 40 - camera_y, &I_icon_tree);
+
+    // static woman icon at (208, 32)
+    canvas_draw_icon(canvas, 208 - camera_x, 32 - camera_y, &I_icon_woman);
+}
 
 /****** Entities: Player ******/
 
 typedef struct
 {
-    Sprite *sprite;
+    Vector trajectory; // Direction player would like to move.
+    float radius;      // collision radius
+    int8_t dx;         // x direction
+    int8_t dy;         // y direction
+    Sprite *sprite;    // player sprite
 } PlayerContext;
 
 // Forward declaration of player_desc, because it's used in player_spawn function.
@@ -50,8 +144,8 @@ static void player_update(Entity *self, GameManager *manager, void *context)
         pos.x += 2;
 
     // Clamp player position to screen bounds, considering player sprite size (10x10)
-    pos.x = CLAMP(pos.x, 123, 5);
-    pos.y = CLAMP(pos.y, 59, 5);
+    pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
+    pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
 
     // Set new player position
     entity_pos_set(self, pos);
@@ -71,33 +165,35 @@ static void player_render(Entity *self, GameManager *manager, Canvas *canvas, vo
     // Get player position
     Vector pos = entity_pos_get(self);
 
-    // Draw player sprite
-    // We subtract 5 from x and y, because collision box is 10x10, and we want to center sprite in it.
-    canvas_draw_sprite(canvas, player->sprite, pos.x - 5, pos.y - 5);
+    // Draw background (updates camera_x and camera_y)
+    background_render(canvas, pos);
+
+    // Draw player sprite relative to camera
+    canvas_draw_sprite(canvas, player->sprite, pos.x - camera_x - 5, pos.y - camera_y - 5);
 
     // Get game context
     GameContext *game_context = game_manager_game_context_get(manager);
 
-    // Draw score
-    canvas_printf(canvas, 0, 7, "Score: %lu", game_context->score);
+    // Draw score (optional)
+    UNUSED(game_context);
+    // canvas_printf(canvas, 0, 7, "Score: %lu", game_context->score);
 }
 
 static const EntityDescription player_desc = {
-    .start = NULL,           // called when entity is added to the level
-    .stop = NULL,            // called when entity is removed from the level
-    .update = player_update, // called every frame
-    .render = player_render, // called every frame, after update
-    .collision = NULL,       // called when entity collides with another entity
-    .event = NULL,           // called when entity receives an event
-    .context_size =
-        sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
+    .start = NULL,                         // called when entity is added to the level
+    .stop = NULL,                          // called when entity is removed from the level
+    .update = player_update,               // called every frame
+    .render = player_render,               // called every frame, after update
+    .collision = NULL,                     // called when entity collides with another entity
+    .event = NULL,                         // called when entity receives an event
+    .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
 };
 
 /****** Entities: Target ******/
 
 static Vector random_pos(void)
 {
-    return (Vector){rand() % 120 + 4, rand() % 58 + 4};
+    return (Vector){rand() % (SCREEN_WIDTH - 8) + 4, rand() % (SCREEN_HEIGHT - 8) + 4};
 }
 
 static void target_start(Entity *self, GameManager *manager, void *context)
@@ -119,11 +215,8 @@ static void target_render(Entity *self, GameManager *manager, Canvas *canvas, vo
     // Get target position
     Vector pos = entity_pos_get(self);
 
-    // Draw target
-    canvas_draw_disc(canvas, pos.x, pos.y, 3);
-
-    // Draw background
-    canvas_draw_box(canvas, 0, 20, 40, 20);
+    // Draw target relative to the camera
+    canvas_draw_disc(canvas, pos.x - camera_x, pos.y - camera_y, 3);
 }
 
 static void target_collision(Entity *self, Entity *other, GameManager *manager, void *context)
@@ -151,6 +244,142 @@ static const EntityDescription target_desc = {
     .context_size = 0,             // size of entity context, will be automatically allocated and freed
 };
 
+/****** Entities: Wall ******/
+
+static uint8_t wall_index;
+
+static void wall_start(Entity *self, GameManager *manager, void *context);
+
+typedef struct
+{
+    float width;
+    float height;
+} WallContext;
+
+static void wall_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
+{
+    UNUSED(manager);
+    UNUSED(self);
+    UNUSED(canvas);
+    UNUSED(context);
+
+    // WallContext *wall = context;
+
+    // Vector pos = entity_pos_get(self);
+
+    // Draw the wall relative to the camera
+    // canvas_draw_box(
+    //     canvas,
+    //     pos.x - camera_x - (wall->width / 2),
+    //     pos.y - camera_y - (wall->height / 2),
+    //     wall->width,
+    //     wall->height);
+}
+
+static void wall_collision(Entity *self, Entity *other, GameManager *manager, void *context)
+{
+    WallContext *wall = context;
+
+    // Check if wall collided with player
+    if (entity_description_get(other) == &player_desc)
+    {
+        // Increase score
+        GameContext *game_context = game_manager_game_context_get(manager);
+        game_context->score++;
+
+        PlayerContext *player = (PlayerContext *)entity_context_get(other);
+        if (player)
+        {
+            if (player->dx || player->dy)
+            {
+                Vector pos = entity_pos_get(other);
+
+                // TODO: Based on where we collided, we should still slide across/down the wall.
+                UNUSED(wall);
+
+                if (player->dx)
+                {
+                    FURI_LOG_D(
+                        "Player",
+                        "Player collided with wall, dx: %d.  center:%f,%f",
+                        player->dx,
+                        (double)pos.x,
+                        (double)pos.y);
+                    pos.x -= player->dx;
+                    player->dx = 0;
+                }
+                if (player->dy)
+                {
+                    FURI_LOG_D(
+                        "Player",
+                        "Player collided with wall, dy: %d.  center:%f,%f",
+                        player->dy,
+                        (double)pos.x,
+                        (double)pos.y);
+                    pos.y -= player->dy;
+                    player->dy = 0;
+                }
+                entity_pos_set(other, pos);
+                FURI_LOG_D("Player", "Set to center:%f,%f", (double)pos.x, (double)pos.y);
+            }
+        }
+        else
+        {
+            FURI_LOG_D("Player", "Player collided with wall, but context null.");
+        }
+    }
+    else
+    {
+        // HACK: Wall touching other items destroys each other (to help find collider issues)
+        Level *level = game_manager_current_level_get(manager);
+        level_remove_entity(level, self);
+        level_remove_entity(level, other);
+    }
+}
+
+static const EntityDescription wall_desc = {
+    .start = wall_start,         // called when entity is added to the level
+    .stop = NULL,                // called when entity is removed from the level
+    .update = NULL,              // called every frame
+    .render = wall_render,       // called every frame, after update
+    .collision = wall_collision, // called when entity collides with another entity
+    .event = NULL,               // called when entity receives an event
+    .context_size =
+        sizeof(WallContext), // size of entity context, will be automatically allocated and freed
+};
+
+static void wall_start(Entity *self, GameManager *manager, void *context)
+{
+    UNUSED(manager);
+
+    WallContext *wall = context;
+
+    // TODO: We can get the current number of items from the level (instead of wall_index).
+
+    if (wall_index < COUNT_OF(walls))
+    {
+        if (walls[wall_index].horizontal)
+        {
+            wall->width = walls[wall_index].length * 2;
+            wall->height = 1 * 2;
+        }
+        else
+        {
+            wall->width = 1 * 2;
+            wall->height = walls[wall_index].length * 2;
+        }
+
+        entity_pos_set(
+            self,
+            (Vector){
+                walls[wall_index].x + wall->width / 2, walls[wall_index].y + wall->height / 2});
+
+        entity_collider_add_rect(self, wall->width, wall->height);
+
+        wall_index++;
+    }
+}
+
 /****** Level ******/
 
 static void level_alloc(Level *level, GameManager *manager, void *context)
@@ -164,8 +393,12 @@ static void level_alloc(Level *level, GameManager *manager, void *context)
     // Add first target entity to the level
     level_add_entity(level, &target_desc);
 
-    // Add second target entity to the level
-    level_add_entity(level, &target_desc);
+    // Add wall entities to the level
+    wall_index = 0;
+    for (size_t i = 0; i < COUNT_OF(walls); i++)
+    {
+        level_add_entity(level, &wall_desc);
+    }
 }
 
 static const LevelBehaviour level = {
@@ -202,10 +435,11 @@ static void game_start(GameManager *game_manager, void *ctx)
 */
 static void game_stop(void *ctx)
 {
-    GameContext *game_context = ctx;
-    // Do some deinitialization here, for example you can save score to storage.
-    // For simplicity, we will just print it.
-    FURI_LOG_I("Game", "Your score: %lu", game_context->score);
+    UNUSED(ctx);
+    // GameContext *game_context = ctx;
+    //  Do some deinitialization here, for example you can save score to storage.
+    //  For simplicity, we will just print it.
+    // FURI_LOG_I("Game", "Your score: %lu", game_context->score);
 }
 
 /*

+ 43 - 3
game.h

@@ -1,8 +1,48 @@
 #pragma once
 #include "engine/engine.h"
 
-/* Global game defines go here */
+// Screen size
+#define SCREEN_WIDTH 128
+#define SCREEN_HEIGHT 64
+// World size (3x3)
+#define WORLD_WIDTH 384
+#define WORLD_HEIGHT 192
 
-typedef struct {
+// from https://github.com/jamisonderek/flipper-zero-tutorials/blob/main/vgm/apps/air_labyrinth/walls.h
+typedef struct
+{
+    bool horizontal;
+    int x;
+    int y;
+    int length;
+} Wall;
+
+#define WALL(h, y, x, l)   \
+    (Wall)                 \
+    {                      \
+        h, x * 2, y * 2, l \
+    }
+
+typedef struct
+{
     uint32_t score;
-} GameContext;
+} GameContext;
+
+typedef enum
+{
+    // system draw objects
+    DRAW_DOT,        // canvas_draw_dot
+    DRAW_LINE,       // canvas_draw_line
+    DRAW_BOX,        // canvas_draw_box
+    DRAW_FRAME,      // canvas_draw_frame
+    DRAW_CIRCLE,     // canvas_draw_circle
+    DRAW_XBM,        // canvas_draw_xbm
+                     // custom draw objects
+    DRAW_ICON_EARTH, // 	canvas_draw_icon
+    DRAW_ICON_HOME,  // 	canvas_draw_icon
+    DRAW_ICON_INFO,  // 	canvas_draw_icon
+    DRAW_ICON_MAN,   // 	canvas_draw_man
+    DRAW_ICON_PLANT, // 	canvas_draw_icon
+    DRAW_ICON_TREE,  // 	canvas_draw_icon
+    DRAW_ICON_WOMAN, // 	canvas_draw_icon
+} FlipWorldDrawObjects;