game.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. #include "game.h"
  2. #include "flip_world.h"
  3. #include "flip_world_icons.h"
  4. Wall walls[] = {
  5. WALL(true, 12, 0, 3),
  6. WALL(false, 3, 3, 17),
  7. WALL(false, 23, 3, 6),
  8. WALL(true, 3, 4, 57),
  9. WALL(true, 28, 4, 56),
  10. WALL(false, 4, 7, 5),
  11. WALL(false, 12, 7, 13),
  12. WALL(true, 8, 8, 34),
  13. WALL(true, 12, 8, 42),
  14. WALL(true, 24, 8, 8),
  15. WALL(true, 16, 11, 8),
  16. WALL(false, 17, 11, 4),
  17. WALL(true, 20, 12, 22),
  18. WALL(false, 6, 17, 2),
  19. WALL(true, 24, 19, 15),
  20. WALL(true, 16, 22, 16),
  21. WALL(false, 4, 24, 1),
  22. WALL(false, 21, 28, 2),
  23. WALL(false, 6, 33, 2),
  24. WALL(false, 13, 34, 3),
  25. WALL(false, 17, 37, 11),
  26. WALL(true, 16, 41, 14),
  27. WALL(false, 20, 41, 5),
  28. WALL(true, 20, 45, 12),
  29. WALL(true, 24, 45, 12),
  30. WALL(false, 4, 46, 2),
  31. WALL(false, 9, 46, 3),
  32. WALL(false, 6, 50, 3),
  33. WALL(true, 12, 53, 7),
  34. WALL(true, 8, 54, 6),
  35. WALL(false, 4, 60, 19),
  36. WALL(false, 26, 60, 6),
  37. };
  38. // Global variables to store camera position
  39. static int camera_x = 0;
  40. static int camera_y = 0;
  41. // Draw a line of icons (16 width)
  42. static void draw_icon_line(Canvas *canvas, Vector pos, int amount, bool horizontal, const Icon *icon)
  43. {
  44. for (int i = 0; i < amount; i++)
  45. {
  46. if (horizontal)
  47. {
  48. // check if element is outside the world
  49. if (pos.x + (i * 17) > WORLD_WIDTH)
  50. {
  51. break;
  52. }
  53. canvas_draw_icon(canvas, pos.x + (i * 17) - camera_x, pos.y - camera_y, icon);
  54. }
  55. else
  56. {
  57. // check if element is outside the world
  58. if (pos.y + (i * 17) > WORLD_HEIGHT)
  59. {
  60. break;
  61. }
  62. canvas_draw_icon(canvas, pos.x - camera_x, pos.y + (i * 17) - camera_y, icon);
  63. }
  64. }
  65. }
  66. // Draw a half section of icons (16 width)
  67. void draw_icon_half_world(Canvas *canvas, bool right, const Icon *icon)
  68. {
  69. for (int i = 0; i < 10; i++)
  70. {
  71. if (right)
  72. {
  73. draw_icon_line(canvas, (Vector){WORLD_WIDTH / 2 + 6, i * 19 + 2}, 11, true, icon);
  74. }
  75. else
  76. {
  77. draw_icon_line(canvas, (Vector){0, i * 19 + 2}, 11, true, icon);
  78. }
  79. }
  80. }
  81. // Background rendering function
  82. // TODO: each object needs a collision box so we can detect collisions and prevent movement through walls.
  83. static void background_render(Canvas *canvas, Vector pos)
  84. {
  85. // Clear the canvas
  86. canvas_clear(canvas);
  87. // Calculate camera offset to center the player
  88. camera_x = pos.x - (SCREEN_WIDTH / 2);
  89. camera_y = pos.y - (SCREEN_HEIGHT / 2);
  90. // Clamp camera position to prevent showing areas outside the world
  91. camera_x = CLAMP(camera_x, WORLD_WIDTH - SCREEN_WIDTH, 0);
  92. camera_y = CLAMP(camera_y, WORLD_HEIGHT - SCREEN_HEIGHT, 0);
  93. // Draw other elements adjusted by camera offset
  94. // Static Dot at (72, 40)
  95. canvas_draw_dot(canvas, 72 - camera_x, 40 - camera_y);
  96. // Static Circle at (16, 16) with radius 4
  97. canvas_draw_circle(canvas, 16 - camera_x, 16 - camera_y, 4);
  98. // Static 8x8 Rectangle Frame at (96, 48)
  99. canvas_draw_frame(canvas, 96 - camera_x, 48 - camera_y, 8, 8);
  100. // Static earth icon at (112, 56)
  101. canvas_draw_icon(canvas, 112 - camera_x, 56 - camera_y, &I_icon_earth_15x16);
  102. // static home icon at (128, 24)
  103. canvas_draw_icon(canvas, 128 - camera_x, 24 - camera_y, &I_icon_home_15x16);
  104. // static menu icon at (144, 24)
  105. canvas_draw_icon(canvas, 144 - camera_x, 24 - camera_y, &I_icon_info_15x16);
  106. // static man icon at (160, 56)
  107. canvas_draw_icon(canvas, 160 - camera_x, 56 - camera_y, &I_icon_man_7x16);
  108. // static woman icon at (208, 56)
  109. canvas_draw_icon(canvas, 168 - camera_x, 56 - camera_y, &I_icon_woman_9x16);
  110. // static plant icon at (168, 32)
  111. canvas_draw_icon(canvas, 168 - camera_x, 32 - camera_y, &I_icon_plant_16x16);
  112. // tree world
  113. draw_icon_half_world(canvas, true, &I_icon_tree_16x16);
  114. // Draw the outer bounds adjusted by camera offset
  115. // we draw this last to ensure users can see the bounds
  116. canvas_draw_frame(canvas, -camera_x, -camera_y, WORLD_WIDTH, WORLD_HEIGHT);
  117. }
  118. /****** Entities: Player ******/
  119. typedef struct
  120. {
  121. Vector trajectory; // Direction player would like to move.
  122. float radius; // collision radius
  123. int8_t dx; // x direction
  124. int8_t dy; // y direction
  125. Sprite *sprite; // player sprite
  126. } PlayerContext;
  127. // Forward declaration of player_desc, because it's used in player_spawn function.
  128. static const EntityDescription player_desc;
  129. static void player_spawn(Level *level, GameManager *manager)
  130. {
  131. Entity *player = level_add_entity(level, &player_desc);
  132. // Set player position.
  133. // Depends on your game logic, it can be done in start entity function, but also can be done here.
  134. entity_pos_set(player, (Vector){WORLD_WIDTH / 2, WORLD_HEIGHT / 2});
  135. // Add collision box to player entity
  136. // Box is centered in player x and y, and it's size is 10x10
  137. entity_collider_add_rect(player, 10, 10);
  138. // Get player context
  139. PlayerContext *player_context = entity_context_get(player);
  140. // Load player sprite
  141. player_context->sprite = game_manager_sprite_load(manager, "player.fxbm");
  142. }
  143. static void player_update(Entity *self, GameManager *manager, void *context)
  144. {
  145. UNUSED(context);
  146. // Get game input
  147. InputState input = game_manager_input_get(manager);
  148. // Get player position
  149. Vector pos = entity_pos_get(self);
  150. // Control player movement
  151. if (input.held & GameKeyUp)
  152. pos.y -= 2;
  153. if (input.held & GameKeyDown)
  154. pos.y += 2;
  155. if (input.held & GameKeyLeft)
  156. pos.x -= 2;
  157. if (input.held & GameKeyRight)
  158. pos.x += 2;
  159. // Clamp player position to screen bounds, considering player sprite size (10x10)
  160. pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
  161. pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
  162. // Set new player position
  163. entity_pos_set(self, pos);
  164. // Control game exit
  165. if (input.pressed & GameKeyBack)
  166. {
  167. game_manager_game_stop(manager);
  168. }
  169. }
  170. static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
  171. {
  172. // Get player context
  173. PlayerContext *player = context;
  174. // Get player position
  175. Vector pos = entity_pos_get(self);
  176. // Draw background (updates camera_x and camera_y)
  177. background_render(canvas, pos);
  178. // Draw player sprite relative to camera
  179. canvas_draw_sprite(canvas, player->sprite, pos.x - camera_x - 5, pos.y - camera_y - 5);
  180. // Get game context
  181. GameContext *game_context = game_manager_game_context_get(manager);
  182. // Draw score (optional)
  183. UNUSED(game_context);
  184. // canvas_printf(canvas, 0, 7, "Score: %lu", game_context->score);
  185. }
  186. static const EntityDescription player_desc = {
  187. .start = NULL, // called when entity is added to the level
  188. .stop = NULL, // called when entity is removed from the level
  189. .update = player_update, // called every frame
  190. .render = player_render, // called every frame, after update
  191. .collision = NULL, // called when entity collides with another entity
  192. .event = NULL, // called when entity receives an event
  193. .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
  194. };
  195. /****** Entities: Target ******/
  196. static Vector random_pos(void)
  197. {
  198. return (Vector){rand() % (SCREEN_WIDTH - 8) + 4, rand() % (SCREEN_HEIGHT - 8) + 4};
  199. }
  200. static void target_start(Entity *self, GameManager *manager, void *context)
  201. {
  202. UNUSED(context);
  203. UNUSED(manager);
  204. // Set target position
  205. entity_pos_set(self, random_pos());
  206. // Add collision circle to target entity
  207. // Circle is centered in target x and y, and it's radius is 3
  208. entity_collider_add_circle(self, 3);
  209. }
  210. static void target_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
  211. {
  212. UNUSED(context);
  213. UNUSED(manager);
  214. // Get target position
  215. Vector pos = entity_pos_get(self);
  216. // Draw target relative to the camera
  217. canvas_draw_disc(canvas, pos.x - camera_x, pos.y - camera_y, 3);
  218. }
  219. static void target_collision(Entity *self, Entity *other, GameManager *manager, void *context)
  220. {
  221. UNUSED(context);
  222. // Check if target collided with player
  223. if (entity_description_get(other) == &player_desc)
  224. {
  225. // Increase score
  226. GameContext *game_context = game_manager_game_context_get(manager);
  227. game_context->score++;
  228. // Move target to new random position
  229. entity_pos_set(self, random_pos());
  230. }
  231. }
  232. static const EntityDescription target_desc = {
  233. .start = target_start, // called when entity is added to the level
  234. .stop = NULL, // called when entity is removed from the level
  235. .update = NULL, // called every frame
  236. .render = target_render, // called every frame, after update
  237. .collision = target_collision, // called when entity collides with another entity
  238. .event = NULL, // called when entity receives an event
  239. .context_size = 0, // size of entity context, will be automatically allocated and freed
  240. };
  241. /****** Entities: Wall ******/
  242. static uint8_t wall_index;
  243. static void wall_start(Entity *self, GameManager *manager, void *context);
  244. typedef struct
  245. {
  246. float width;
  247. float height;
  248. } WallContext;
  249. static void wall_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
  250. {
  251. UNUSED(manager);
  252. UNUSED(self);
  253. UNUSED(canvas);
  254. UNUSED(context);
  255. // WallContext *wall = context;
  256. // Vector pos = entity_pos_get(self);
  257. // Draw the wall relative to the camera
  258. // canvas_draw_box(
  259. // canvas,
  260. // pos.x - camera_x - (wall->width / 2),
  261. // pos.y - camera_y - (wall->height / 2),
  262. // wall->width,
  263. // wall->height);
  264. }
  265. static void wall_collision(Entity *self, Entity *other, GameManager *manager, void *context)
  266. {
  267. WallContext *wall = context;
  268. // Check if wall collided with player
  269. if (entity_description_get(other) == &player_desc)
  270. {
  271. // Increase score
  272. GameContext *game_context = game_manager_game_context_get(manager);
  273. game_context->score++;
  274. PlayerContext *player = (PlayerContext *)entity_context_get(other);
  275. if (player)
  276. {
  277. if (player->dx || player->dy)
  278. {
  279. Vector pos = entity_pos_get(other);
  280. // TODO: Based on where we collided, we should still slide across/down the wall.
  281. UNUSED(wall);
  282. if (player->dx)
  283. {
  284. FURI_LOG_D(
  285. "Player",
  286. "Player collided with wall, dx: %d. center:%f,%f",
  287. player->dx,
  288. (double)pos.x,
  289. (double)pos.y);
  290. pos.x -= player->dx;
  291. player->dx = 0;
  292. }
  293. if (player->dy)
  294. {
  295. FURI_LOG_D(
  296. "Player",
  297. "Player collided with wall, dy: %d. center:%f,%f",
  298. player->dy,
  299. (double)pos.x,
  300. (double)pos.y);
  301. pos.y -= player->dy;
  302. player->dy = 0;
  303. }
  304. entity_pos_set(other, pos);
  305. FURI_LOG_D("Player", "Set to center:%f,%f", (double)pos.x, (double)pos.y);
  306. }
  307. }
  308. else
  309. {
  310. FURI_LOG_D("Player", "Player collided with wall, but context null.");
  311. }
  312. }
  313. else
  314. {
  315. // HACK: Wall touching other items destroys each other (to help find collider issues)
  316. Level *level = game_manager_current_level_get(manager);
  317. level_remove_entity(level, self);
  318. level_remove_entity(level, other);
  319. }
  320. }
  321. static const EntityDescription wall_desc = {
  322. .start = wall_start, // called when entity is added to the level
  323. .stop = NULL, // called when entity is removed from the level
  324. .update = NULL, // called every frame
  325. .render = wall_render, // called every frame, after update
  326. .collision = wall_collision, // called when entity collides with another entity
  327. .event = NULL, // called when entity receives an event
  328. .context_size =
  329. sizeof(WallContext), // size of entity context, will be automatically allocated and freed
  330. };
  331. static void wall_start(Entity *self, GameManager *manager, void *context)
  332. {
  333. UNUSED(manager);
  334. WallContext *wall = context;
  335. // TODO: We can get the current number of items from the level (instead of wall_index).
  336. if (wall_index < COUNT_OF(walls))
  337. {
  338. if (walls[wall_index].horizontal)
  339. {
  340. wall->width = walls[wall_index].length * 2;
  341. wall->height = 1 * 2;
  342. }
  343. else
  344. {
  345. wall->width = 1 * 2;
  346. wall->height = walls[wall_index].length * 2;
  347. }
  348. entity_pos_set(
  349. self,
  350. (Vector){
  351. walls[wall_index].x + wall->width / 2, walls[wall_index].y + wall->height / 2});
  352. entity_collider_add_rect(self, wall->width, wall->height);
  353. wall_index++;
  354. }
  355. }
  356. /****** Level ******/
  357. static void level_alloc(Level *level, GameManager *manager, void *context)
  358. {
  359. UNUSED(manager);
  360. UNUSED(context);
  361. // Add player entity to the level
  362. player_spawn(level, manager);
  363. // Add first target entity to the level
  364. level_add_entity(level, &target_desc);
  365. // Add wall entities to the level
  366. wall_index = 0;
  367. for (size_t i = 0; i < COUNT_OF(walls); i++)
  368. {
  369. level_add_entity(level, &wall_desc);
  370. }
  371. }
  372. static const LevelBehaviour level = {
  373. .alloc = level_alloc, // called once, when level allocated
  374. .free = NULL, // called once, when level freed
  375. .start = NULL, // called when level is changed to this level
  376. .stop = NULL, // called when level is changed from this level
  377. .context_size = 0, // size of level context, will be automatically allocated and freed
  378. };
  379. /****** Game ******/
  380. /*
  381. Write here the start code for your game, for example: creating a level and so on.
  382. Game context is allocated (game.context_size) and passed to this function, you can use it to store your game data.
  383. */
  384. static void game_start(GameManager *game_manager, void *ctx)
  385. {
  386. UNUSED(game_manager);
  387. // Do some initialization here, for example you can load score from storage.
  388. // For simplicity, we will just set it to 0.
  389. GameContext *game_context = ctx;
  390. game_context->score = 0;
  391. // Add level to the game
  392. game_manager_add_level(game_manager, &level);
  393. }
  394. /*
  395. Write here the stop code for your game, for example, freeing memory, if it was allocated.
  396. You don't need to free level, sprites or entities, it will be done automatically.
  397. Also, you don't need to free game_context, it will be done automatically, after this function.
  398. */
  399. static void game_stop(void *ctx)
  400. {
  401. UNUSED(ctx);
  402. // GameContext *game_context = ctx;
  403. // Do some deinitialization here, for example you can save score to storage.
  404. // For simplicity, we will just print it.
  405. // FURI_LOG_I("Game", "Your score: %lu", game_context->score);
  406. }
  407. /*
  408. Your game configuration, do not rename this variable, but you can change its content here.
  409. */
  410. const Game game = {
  411. .target_fps = 30, // target fps, game will try to keep this value
  412. .show_fps = false, // show fps counter on the screen
  413. .always_backlight = true, // keep display backlight always on
  414. .start = game_start, // will be called once, when game starts
  415. .stop = game_stop, // will be called once, when game stops
  416. .context_size = sizeof(GameContext), // size of game context
  417. };