Преглед изворни кода

Merge pull request #4 from jblanked/dev_0.1

Add collision support (few more updates left until 0.1 is ready)
JBlanked пре 1 година
родитељ
комит
a3838fe98e

+ 0 - 0
assets/icon_earth.png → assets/icon_earth_15x16.png


+ 0 - 0
assets/icon_flower.png → assets/icon_flower_16x16.png


+ 0 - 0
assets/icon_home.png → assets/icon_home_15x16.png


+ 0 - 0
assets/icon_info.png → assets/icon_info_15x16.png


+ 0 - 0
assets/icon_man.png → assets/icon_man_7x16.png


+ 0 - 0
assets/icon_plant.png → assets/icon_plant_16x16.png


+ 0 - 0
assets/icon_tree.png → assets/icon_tree_16x16.png


+ 0 - 0
assets/icon_woman.png → assets/icon_woman_9x16.png


+ 19 - 4
callback/callback.c

@@ -166,6 +166,19 @@ static bool alloc_text_input_view(void *context, char *title)
         {
             return false;
         }
+        char ssid[64];
+        char pass[64];
+        if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
+        {
+            if (strcmp(title, "SSID") == 0)
+            {
+                strncpy(app->text_input_temp_buffer, ssid, app->text_input_buffer_size);
+            }
+            else
+            {
+                strncpy(app->text_input_temp_buffer, pass, app->text_input_buffer_size);
+            }
+        }
     }
     return true;
 }
@@ -201,6 +214,8 @@ static bool alloc_variable_item_list(void *context)
         {
             variable_item_set_current_value_text(app->variable_item_ssid, ssid);
             // variable_item_set_current_value_text(app->variable_item_pass, pass);
+            save_char("WiFi-SSID", ssid);
+            save_char("WiFi-Password", pass);
         }
     }
     return true;
@@ -502,6 +517,8 @@ static void settings_item_selected(void *context, uint32_t index)
         FURI_LOG_E(TAG, "FlipWorldApp is NULL");
         return;
     }
+    char ssid[64];
+    char pass[64];
     switch (index)
     {
     case 0: // Input SSID
@@ -512,8 +529,7 @@ static void settings_item_selected(void *context, uint32_t index)
             return;
         }
         // load SSID
-        char ssid[64];
-        if (load_char("WiFi-SSID", ssid, sizeof(ssid)))
+        if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
         {
             strncpy(app->text_input_temp_buffer, ssid, app->text_input_buffer_size - 1);
             app->text_input_temp_buffer[app->text_input_buffer_size - 1] = '\0';
@@ -528,8 +544,7 @@ static void settings_item_selected(void *context, uint32_t index)
             return;
         }
         // load password
-        char pass[64];
-        if (load_char("WiFi-Password", pass, sizeof(pass)))
+        if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
         {
             strncpy(app->text_input_temp_buffer, pass, app->text_input_buffer_size - 1);
             app->text_input_temp_buffer[app->text_input_buffer_size - 1] = '\0';

+ 48 - 0
draw/draw.c

@@ -0,0 +1,48 @@
+#include <draw/draw.h>
+
+// Global variables to store camera position
+int camera_x = 0;
+int camera_y = 0;
+
+// Draw a line of icons (16 width)
+void draw_icon_line(Canvas *canvas, Vector pos, int amount, bool horizontal, const Icon *icon)
+{
+    for (int i = 0; i < amount; i++)
+    {
+        if (horizontal)
+        {
+            // check if element is outside the world
+            if (pos.x + (i * 17) > WORLD_WIDTH)
+            {
+                break;
+            }
+
+            canvas_draw_icon(canvas, pos.x + (i * 17) - camera_x, pos.y - camera_y, icon);
+        }
+        else
+        {
+            // check if element is outside the world
+            if (pos.y + (i * 17) > WORLD_HEIGHT)
+            {
+                break;
+            }
+
+            canvas_draw_icon(canvas, pos.x - camera_x, pos.y + (i * 17) - camera_y, icon);
+        }
+    }
+}
+// Draw a half section of icons (16 width)
+void draw_icon_half_world(Canvas *canvas, bool right, const Icon *icon)
+{
+    for (int i = 0; i < 10; i++)
+    {
+        if (right)
+        {
+            draw_icon_line(canvas, (Vector){WORLD_WIDTH / 2 + 6, i * 19 + 2}, 11, true, icon);
+        }
+        else
+        {
+            draw_icon_line(canvas, (Vector){0, i * 19 + 2}, 11, true, icon);
+        }
+    }
+}

+ 32 - 0
draw/draw.h

@@ -0,0 +1,32 @@
+#pragma once
+#include "engine/engine.h"
+#include "flip_world.h"
+#include "flip_world_icons.h"
+
+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;
+
+// Global variables to store camera position
+extern int camera_x;
+extern int camera_y;
+
+void draw_icon_line(Canvas *canvas, Vector pos, int amount, bool horizontal, const Icon *icon);
+void draw_icon_half_world(Canvas *canvas, bool right, const Icon *icon);
+
+// create custom icons at https://lopaka.app/sandbox

+ 139 - 0
draw/world.c

@@ -0,0 +1,139 @@
+#include <draw/world.h>
+
+void draw_bounds(Canvas *canvas)
+{
+    // Draw the outer bounds adjusted by camera offset
+    // we draw this last to ensure users can see the bounds
+    canvas_draw_frame(canvas, -camera_x, -camera_y, WORLD_WIDTH, WORLD_HEIGHT);
+}
+
+void draw_example_world(Level *level)
+{
+    spawn_icon(level, &I_icon_earth, 112, 56);
+    spawn_icon(level, &I_icon_home, 128, 24);
+    spawn_icon(level, &I_icon_info, 144, 24);
+    spawn_icon(level, &I_icon_man, 160, 56);
+    spawn_icon(level, &I_icon_woman, 168, 56);
+    spawn_icon(level, &I_icon_plant, 168, 32);
+}
+
+void draw_tree_world(Level *level)
+{
+    // Spawn two full left/up tree lines
+    for (int i = 0; i < 2; i++)
+    {
+        for (int j = 0; j < 22; j++)
+        {
+            spawn_icon(level, &I_icon_tree, 5 + j * 17, 2 + i * 17); // Horizontal lines
+        }
+        for (int j = 0; j < 11; j++)
+        {
+            spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + j * 17); // Vertical lines
+        }
+    }
+
+    // Spawn two full down tree lines
+    for (int i = 9; i < 11; i++)
+    {
+        for (int j = 0; j < 22; j++)
+        {
+            spawn_icon(level, &I_icon_tree, 5 + j * 17, 2 + i * 17); // Horizontal lines
+        }
+    }
+
+    // Spawn two full right tree lines
+    for (int i = 20; i < 22; i++)
+    {
+        for (int j = 0; j < 8; j++)
+        {
+            spawn_icon(level, &I_icon_tree, 5 + i * 17, 50 + j * 17); // Vertical lines
+        }
+    }
+
+    // Spawn labyrinth lines
+    // Third line (14 left, 3 middle, 0 right) - exit line
+    for (int i = 0; i < 14; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 2 * 17);
+    }
+    for (int i = 0; i < 3; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 16 * 17 + i * 17, 2 + 2 * 17);
+    }
+
+    // Fourth line (3 left, 6 middle, 4 right)
+    for (int i = 0; i < 3; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 3 * 17); // 3 left
+    }
+    for (int i = 0; i < 6; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 7 * 17 + i * 17, 2 + 3 * 17); // 6 middle
+    }
+    for (int i = 0; i < 4; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 15 * 17 + i * 17, 2 + 3 * 17); // 4 right
+    }
+
+    // Fifth line (6 left, 7 middle, 0 right)
+    for (int i = 0; i < 6; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 4 * 17); // 6 left
+    }
+    for (int i = 0; i < 7; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 7 * 17 + i * 17, 2 + 4 * 17); // 7 middle
+    }
+
+    // Sixth line (5 left, 6 middle, 7 right)
+    for (int i = 0; i < 5; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 5 * 17); // 5 left
+    }
+    for (int i = 0; i < 3; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 7 * 17 + i * 17, 2 + 5 * 17); // 3 middle
+    }
+    for (int i = 0; i < 7; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 15 * 17 + i * 17, 2 + 5 * 17); // 7 right
+    }
+
+    // Seventh line (0 left, 7 middle, 4 right)
+    for (int i = 0; i < 7; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 6 * 17 + i * 17, 2 + 6 * 17); // 7 middle
+    }
+    for (int i = 0; i < 4; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 14 * 17 + i * 17, 2 + 6 * 17); // 4 right
+    }
+
+    // Eighth line (4 left, 3 middle, 4 right)
+    for (int i = 0; i < 4; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 7 * 17); // 4 left
+    }
+    for (int i = 0; i < 3; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 7 * 17 + i * 17, 2 + 7 * 17); // 3 middle
+    }
+    for (int i = 0; i < 4; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 15 * 17 + i * 17, 2 + 7 * 17); // 4 right
+    }
+
+    // Ninth line (3 left, 2 middle, 3 right)
+    for (int i = 0; i < 3; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + i * 17, 2 + 8 * 17); // 3 left
+    }
+    for (int i = 0; i < 1; i++) // 2 middle
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 5 * 17 + i * 17, 2 + 8 * 17);
+    }
+    for (int i = 0; i < 3; i++)
+    {
+        spawn_icon(level, &I_icon_tree, 5 + 11 * 17 + i * 17, 2 + 8 * 17); // 3 right
+    }
+}

+ 6 - 0
draw/world.h

@@ -0,0 +1,6 @@
+#pragma once
+#include <draw/draw.h>
+#include <game.h>
+void draw_bounds(Canvas *canvas);
+void draw_example_world(Level *level);
+void draw_tree_world(Level *level);

+ 10 - 0
flip_world.h

@@ -14,6 +14,14 @@
 #define TAG "FlipWorld"
 #define VERSION_TAG "FlipWorld v0.1"
 
+// Screen size
+#define SCREEN_WIDTH 128
+#define SCREEN_HEIGHT 64
+
+// World size (3x3)
+#define WORLD_WIDTH 384
+#define WORLD_HEIGHT 192
+
 // Define the submenu items for our FlipWorld application
 typedef enum
 {
@@ -55,3 +63,5 @@ typedef struct
     char *text_input_temp_buffer;    // Temporary buffer for the text input
     uint32_t text_input_buffer_size; // Size of the text input buffer
 } FlipWorldApp;
+
+// TODO - Add Download world function and download world pack button

+ 72 - 272
game.c

@@ -2,46 +2,8 @@
 #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
+// TODO: each object needs a collision box so we can detect collisions and prevent movement through walls.
 static void background_render(Canvas *canvas, Vector pos)
 {
     // Clear the canvas
@@ -56,38 +18,7 @@ static void background_render(Canvas *canvas, Vector pos)
     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);
+    draw_bounds(canvas);
 }
 
 /****** Entities: Player ******/
@@ -110,7 +41,7 @@ static void player_spawn(Level *level, GameManager *manager)
 
     // Set player position.
     // Depends on your game logic, it can be done in start entity function, but also can be done here.
-    entity_pos_set(player, (Vector){64, 32});
+    entity_pos_set(player, (Vector){WORLD_WIDTH / 2, WORLD_HEIGHT / 2});
 
     // Add collision box to player entity
     // Box is centered in player x and y, and it's size is 10x10
@@ -123,34 +54,43 @@ static void player_spawn(Level *level, GameManager *manager)
     player_context->sprite = game_manager_sprite_load(manager, "player.fxbm");
 }
 
+// Modify player_update to track direction
 static void player_update(Entity *self, GameManager *manager, void *context)
 {
-    UNUSED(context);
-
-    // Get game input
+    PlayerContext *player = (PlayerContext *)context;
     InputState input = game_manager_input_get(manager);
-
-    // Get player position
     Vector pos = entity_pos_get(self);
 
-    // Control player movement
+    // Reset direction each frame
+    player->dx = 0;
+    player->dy = 0;
+
     if (input.held & GameKeyUp)
+    {
         pos.y -= 2;
+        player->dy = -1;
+    }
     if (input.held & GameKeyDown)
+    {
         pos.y += 2;
+        player->dy = 1;
+    }
     if (input.held & GameKeyLeft)
+    {
         pos.x -= 2;
+        player->dx = -1;
+    }
     if (input.held & GameKeyRight)
+    {
         pos.x += 2;
+        player->dx = 1;
+    }
 
-    // Clamp player position to screen bounds, considering player sprite size (10x10)
     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);
 
-    // Control game exit
     if (input.pressed & GameKeyBack)
     {
         game_manager_game_stop(manager);
@@ -160,6 +100,7 @@ static void player_update(Entity *self, GameManager *manager, void *context)
 static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
 {
     // Get player context
+    UNUSED(manager);
     PlayerContext *player = context;
 
     // Get player position
@@ -170,13 +111,6 @@ static void player_render(Entity *self, GameManager *manager, Canvas *canvas, vo
 
     // 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 (optional)
-    UNUSED(game_context);
-    // canvas_printf(canvas, 0, 7, "Score: %lu", game_context->score);
 }
 
 static const EntityDescription player_desc = {
@@ -189,226 +123,92 @@ static const EntityDescription player_desc = {
     .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
 };
 
-/****** Entities: Target ******/
-
-static Vector random_pos(void)
-{
-    return (Vector){rand() % (SCREEN_WIDTH - 8) + 4, rand() % (SCREEN_HEIGHT - 8) + 4};
-}
+/****** Level ******/
 
-static void target_start(Entity *self, GameManager *manager, void *context)
+static void level_alloc(Level *level, GameManager *manager, void *context)
 {
-    UNUSED(context);
     UNUSED(manager);
-    // Set target position
-    entity_pos_set(self, random_pos());
-    // Add collision circle to target entity
-    // Circle is centered in target x and y, and it's radius is 3
-    entity_collider_add_circle(self, 3);
-}
-
-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);
 
-    // 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)
-{
-    UNUSED(context);
-    // Check if target collided with player
-    if (entity_description_get(other) == &player_desc)
-    {
-        // Increase score
-        GameContext *game_context = game_manager_game_context_get(manager);
-        game_context->score++;
+    // Add player entity to the level
+    player_spawn(level, manager);
 
-        // Move target to new random position
-        entity_pos_set(self, random_pos());
-    }
+    draw_tree_world(level);
+    // draw_example_world(level);
 }
 
-static const EntityDescription target_desc = {
-    .start = target_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 = target_render,       // called every frame, after update
-    .collision = target_collision, // called when entity collides with another entity
-    .event = NULL,                 // called when entity receives an event
-    .context_size = 0,             // size of entity context, will be automatically allocated and freed
+static const LevelBehaviour level = {
+    .alloc = level_alloc, // called once, when level allocated
+    .free = NULL,         // called once, when level freed
+    .start = NULL,        // called when level is changed to this level
+    .stop = NULL,         // called when level is changed from this level
+    .context_size = 0,    // size of level 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;
+    const Icon *icon;
+} IconContext;
+
+// Forward declaration of icon_desc
+static const EntityDescription icon_desc;
 
-static void wall_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
+static void icon_collision(Entity *self, Entity *other, GameManager *manager, 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
+    IconContext *icon = (IconContext *)context;
+    UNUSED(icon);
     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.");
+            Vector pos = entity_pos_get(other);
+            // Bounce the player back by 3 units opposite their last movement direction
+            pos.x -= player->dx * 3;
+            pos.y -= player->dy * 3;
+            entity_pos_set(other, pos);
         }
     }
-    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)
+static void icon_render(Entity *self, GameManager *manager, Canvas *canvas, 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++;
-    }
+    IconContext *icon_ctx = (IconContext *)context;
+    Vector pos = entity_pos_get(self);
+    canvas_draw_icon(canvas, pos.x - camera_x - 8, pos.y - camera_y - 8, icon_ctx->icon);
 }
 
-/****** Level ******/
-
-static void level_alloc(Level *level, GameManager *manager, void *context)
+static void icon_start(Entity *self, GameManager *manager, void *context)
 {
     UNUSED(manager);
     UNUSED(context);
-
-    // Add player entity to the level
-    player_spawn(level, manager);
-
-    // Add first 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);
-    }
+    // Just add the collision rectangle for 16x16 icon
+    entity_collider_add_rect(self, 16, 16);
 }
 
-static const LevelBehaviour level = {
-    .alloc = level_alloc, // called once, when level allocated
-    .free = NULL,         // called once, when level freed
-    .start = NULL,        // called when level is changed to this level
-    .stop = NULL,         // called when level is changed from this level
-    .context_size = 0,    // size of level context, will be automatically allocated and freed
+static const EntityDescription icon_desc = {
+    .start = icon_start,
+    .stop = NULL,
+    .update = NULL,
+    .render = icon_render,
+    .collision = icon_collision,
+    .event = NULL,
+    .context_size = sizeof(IconContext),
 };
 
+// Helper function to spawn an icon entity at a given position
+void spawn_icon(Level *level, const Icon *icon, float x, float y)
+{
+    Entity *e = level_add_entity(level, &icon_desc);
+    IconContext *icon_ctx = entity_context_get(e);
+    icon_ctx->icon = icon;
+    // Set the entity position to the center of the icon
+    entity_pos_set(e, (Vector){x + 8, y + 8});
+}
+
 /****** Game ******/
 
 /*

+ 2 - 40
game.h

@@ -1,48 +1,10 @@
 #pragma once
 #include "engine/engine.h"
+#include <draw/world.h>
 
-// Screen size
-#define SCREEN_WIDTH 128
-#define SCREEN_HEIGHT 64
-// World size (3x3)
-#define WORLD_WIDTH 384
-#define WORLD_HEIGHT 192
-
-// 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 \
-    }
+void spawn_icon(Level *level, const Icon *icon, float x, float y);
 
 typedef struct
 {
     uint32_t score;
 } 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;