flappy_bird.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. #include <stdlib.h>
  2. #include <flappy_bird_icons.h>
  3. #include <furi.h>
  4. #include <gui/gui.h>
  5. #include <input/input.h>
  6. #include <dolphin/dolphin.h>
  7. #include <storage/storage.h>
  8. #include <furi_hal.h>
  9. #define TAG "Flappy"
  10. #define DEBUG false
  11. #define FLAPPY_BIRD_HEIGHT 15
  12. #define FLAPPY_BIRD_WIDTH 10
  13. #define FLAPPY_PILAR_MAX 6
  14. #define FLAPPY_PILAR_DIST 35
  15. #define FLAPPY_GAB_HEIGHT 24
  16. #define YAPPER_GAB_HEIGHT 32
  17. #define YAPPYGHOST_GAB_HEIGHT 29
  18. #define FLAPPY_GAB_WIDTH 10
  19. #define YAPPER_HEIGHT 22
  20. #define YAPPER_WIDTH 16
  21. #define FLAPPY_GRAVITY_JUMP -1.1
  22. #define FLAPPY_GRAVITY_TICK 0.15
  23. #define FLIPPER_LCD_WIDTH 128
  24. #define FLIPPER_LCD_HEIGHT 64
  25. static const char* FLAPPY_SAVE_PATH = APP_DATA_PATH("flappy_high.save");
  26. typedef enum {
  27. BirdState0 = 0,
  28. BirdState1,
  29. BirdState2,
  30. BirdStateMAX
  31. } BirdState;
  32. typedef enum {
  33. BirdTypeDefault = 0,
  34. BirdTypeYapper,
  35. BirdTypeYappyGhost,
  36. BirdTypeMAX
  37. } BirdType;
  38. typedef struct {
  39. int width;
  40. int height;
  41. int hitbox_x_offset;
  42. int hitbox_y_offset;
  43. int hitbox_width;
  44. int hitbox_height;
  45. } CharacterDimensions;
  46. static const CharacterDimensions character_dimensions[] = {
  47. {FLAPPY_BIRD_WIDTH,
  48. FLAPPY_BIRD_HEIGHT,
  49. 2,
  50. 2,
  51. FLAPPY_BIRD_WIDTH - 4,
  52. FLAPPY_BIRD_HEIGHT - 4}, // Default bird
  53. {YAPPER_WIDTH,
  54. YAPPER_HEIGHT,
  55. 4,
  56. 4,
  57. YAPPER_WIDTH - 8,
  58. YAPPER_HEIGHT - 8}, // Yapper with larger offset
  59. {18, 19, 4, 4, 10, 11} // YappyGhost with custom hitbox
  60. };
  61. const Icon* bird_sets[BirdTypeMAX][BirdStateMAX] = {
  62. {&I_bird_01, &I_bird_02, &I_bird_03},
  63. {&I_yapper_01, &I_yapper_02, &I_yapper_03},
  64. {&I_yappyghost_01, &I_yappyghost_02, &I_yappyghost_03}};
  65. typedef enum {
  66. EventTypeTick,
  67. EventTypeKey,
  68. } EventType;
  69. typedef struct {
  70. int x;
  71. int y;
  72. } POINT;
  73. typedef struct {
  74. float gravity;
  75. POINT point;
  76. } BIRD;
  77. typedef struct {
  78. POINT point;
  79. int height;
  80. int visible;
  81. bool passed;
  82. } PILAR;
  83. typedef enum {
  84. GameStateStart,
  85. GameStateLife,
  86. GameStateGameOver,
  87. } State;
  88. typedef struct {
  89. BIRD bird;
  90. int points;
  91. int high_score;
  92. int pilars_count;
  93. PILAR pilars[FLAPPY_PILAR_MAX];
  94. bool debug;
  95. State state;
  96. FuriMutex* mutex;
  97. uint8_t collision_frame;
  98. BirdType selected_bird;
  99. bool in_bird_select;
  100. } GameState;
  101. typedef struct {
  102. EventType type;
  103. InputEvent input;
  104. } GameEvent;
  105. typedef enum {
  106. DirectionUp,
  107. DirectionRight,
  108. DirectionDown,
  109. DirectionLeft,
  110. } Direction;
  111. static void flappy_game_save_score(int score) {
  112. Storage* storage = furi_record_open(RECORD_STORAGE);
  113. File* file = storage_file_alloc(storage);
  114. if(storage_file_open(file, FLAPPY_SAVE_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  115. storage_file_write(file, &score, sizeof(int));
  116. }
  117. storage_file_close(file);
  118. storage_file_free(file);
  119. furi_record_close(RECORD_STORAGE);
  120. }
  121. static int flappy_game_load_score() {
  122. Storage* storage = furi_record_open(RECORD_STORAGE);
  123. File* file = storage_file_alloc(storage);
  124. int score = 0;
  125. if(storage_file_open(file, FLAPPY_SAVE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
  126. storage_file_read(file, &score, sizeof(int));
  127. }
  128. storage_file_close(file);
  129. storage_file_free(file);
  130. furi_record_close(RECORD_STORAGE);
  131. return score;
  132. }
  133. static inline int get_gap_height(BirdType bird_type) {
  134. switch(bird_type) {
  135. case BirdTypeYapper:
  136. return YAPPER_GAB_HEIGHT;
  137. case BirdTypeYappyGhost:
  138. return YAPPYGHOST_GAB_HEIGHT; // Now uses its own gap height
  139. default:
  140. return FLAPPY_GAB_HEIGHT;
  141. }
  142. }
  143. static void flappy_game_random_pilar(GameState* const game_state) {
  144. PILAR pilar;
  145. int gap_height = get_gap_height(game_state->selected_bird);
  146. pilar.passed = false;
  147. pilar.visible = 1;
  148. pilar.height = random() % (FLIPPER_LCD_HEIGHT - gap_height) + 1;
  149. pilar.point.y = 0;
  150. pilar.point.x = FLIPPER_LCD_WIDTH + FLAPPY_GAB_WIDTH + 1;
  151. game_state->pilars_count++;
  152. game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX] = pilar;
  153. }
  154. static void flappy_game_state_init(GameState* const game_state) {
  155. BIRD bird;
  156. bird.gravity = 0.0f;
  157. bird.point.x = 15;
  158. bird.point.y = 32;
  159. game_state->debug = DEBUG;
  160. game_state->bird = bird;
  161. game_state->pilars_count = 0;
  162. game_state->points = 0;
  163. game_state->state = GameStateStart;
  164. game_state->collision_frame = 0;
  165. game_state->in_bird_select = false;
  166. // Keep the selected bird between games
  167. if(game_state->selected_bird >= BirdTypeMAX) {
  168. game_state->selected_bird = BirdTypeDefault;
  169. }
  170. // Only load high score if it's not already loaded
  171. if(game_state->high_score == 0) {
  172. game_state->high_score = flappy_game_load_score();
  173. }
  174. memset(game_state->pilars, 0, sizeof(game_state->pilars));
  175. flappy_game_random_pilar(game_state);
  176. }
  177. static void flappy_game_state_free(GameState* const game_state) {
  178. free(game_state);
  179. }
  180. static bool check_collision(
  181. const GameState* game_state,
  182. const PILAR* pilar,
  183. CharacterDimensions dims,
  184. int gap_height) {
  185. // Calculate character's hitbox with offsets
  186. int char_left = game_state->bird.point.x + dims.hitbox_x_offset;
  187. int char_right = char_left + dims.hitbox_width;
  188. int char_top = game_state->bird.point.y + dims.hitbox_y_offset;
  189. int char_bottom = char_top + dims.hitbox_height;
  190. // Calculate pipe hitboxes
  191. int pipe_left = pilar->point.x + 1; // Small offset for visual alignment
  192. int pipe_right = pilar->point.x + FLAPPY_GAB_WIDTH - 1;
  193. int top_pipe_bottom = pilar->height;
  194. int bottom_pipe_top = pilar->height + gap_height;
  195. // No collision if we're not within the horizontal bounds of the pipes
  196. if(char_right < pipe_left || char_left > pipe_right) {
  197. return false;
  198. }
  199. // Extra forgiving collision for Yapper/YappyGhost
  200. if(game_state->selected_bird == BirdTypeYapper ||
  201. game_state->selected_bird == BirdTypeYappyGhost) {
  202. // Add small grace area for top and bottom pipes
  203. top_pipe_bottom += 2;
  204. bottom_pipe_top -= 2;
  205. }
  206. // Collision with top pipe
  207. if(char_top < top_pipe_bottom) {
  208. return true;
  209. }
  210. // Collision with bottom pipe
  211. if(char_bottom > bottom_pipe_top) {
  212. return true;
  213. }
  214. return false;
  215. }
  216. static void flappy_game_tick(GameState* const game_state) {
  217. if(game_state->collision_frame > 0) {
  218. game_state->collision_frame--;
  219. }
  220. if(game_state->state == GameStateLife) {
  221. if(!game_state->debug) {
  222. game_state->bird.gravity += FLAPPY_GRAVITY_TICK;
  223. game_state->bird.point.y += game_state->bird.gravity;
  224. }
  225. CharacterDimensions dims = character_dimensions[game_state->selected_bird];
  226. int gap_height = get_gap_height(game_state->selected_bird);
  227. if(game_state->bird.point.y + game_state->bird.gravity <= 0) {
  228. // Hit the ceiling - game over
  229. game_state->state = GameStateGameOver;
  230. game_state->collision_frame = 4;
  231. if(game_state->points > game_state->high_score) {
  232. game_state->high_score = game_state->points;
  233. flappy_game_save_score(game_state->high_score);
  234. }
  235. return;
  236. }
  237. float max_y = FLIPPER_LCD_HEIGHT - dims.height;
  238. if(game_state->bird.point.y > max_y) {
  239. game_state->state = GameStateGameOver;
  240. game_state->collision_frame = 4;
  241. if(game_state->points > game_state->high_score) {
  242. game_state->high_score = game_state->points;
  243. flappy_game_save_score(game_state->high_score);
  244. }
  245. return;
  246. }
  247. // Check pilar spawning
  248. PILAR* last_pilar = &game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX];
  249. if(last_pilar->point.x == (FLIPPER_LCD_WIDTH - FLAPPY_PILAR_DIST)) {
  250. flappy_game_random_pilar(game_state);
  251. }
  252. // Process all pilars
  253. for(int i = 0; i < FLAPPY_PILAR_MAX; i++) {
  254. PILAR* pilar = &game_state->pilars[i];
  255. if(pilar->visible) {
  256. pilar->point.x--;
  257. if(!pilar->passed &&
  258. game_state->bird.point.x >= pilar->point.x + FLAPPY_GAB_WIDTH) {
  259. pilar->passed = true;
  260. game_state->points++;
  261. }
  262. if(pilar->point.x < -FLAPPY_GAB_WIDTH) {
  263. pilar->visible = 0;
  264. }
  265. if(check_collision(game_state, pilar, dims, gap_height)) {
  266. game_state->state = GameStateGameOver;
  267. game_state->collision_frame = 4;
  268. if(game_state->points > game_state->high_score) {
  269. game_state->high_score = game_state->points;
  270. flappy_game_save_score(game_state->high_score);
  271. }
  272. break;
  273. }
  274. }
  275. }
  276. }
  277. }
  278. static void flappy_game_flap(GameState* const game_state) {
  279. game_state->bird.gravity = FLAPPY_GRAVITY_JUMP;
  280. }
  281. static void flappy_game_render_callback(Canvas* const canvas, void* ctx) {
  282. furi_assert(ctx);
  283. const GameState* game_state = ctx;
  284. furi_mutex_acquire(game_state->mutex, FuriWaitForever);
  285. canvas_draw_frame(canvas, 0, 0, 128, 64);
  286. if(game_state->state == GameStateStart) {
  287. if(!game_state->in_bird_select) {
  288. canvas_set_color(canvas, ColorWhite);
  289. canvas_draw_box(canvas, 14, 8, 100, 48);
  290. canvas_set_color(canvas, ColorBlack);
  291. canvas_draw_frame(canvas, 14, 8, 100, 48);
  292. canvas_set_font(canvas, FontPrimary);
  293. // Change title based on selected character
  294. if(game_state->selected_bird == BirdTypeYapper) {
  295. canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignBottom, "Yappy Bird");
  296. } else if(game_state->selected_bird == BirdTypeYappyGhost) {
  297. canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignBottom, "Yappy Ghost");
  298. } else {
  299. canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignBottom, "Flappy Bird");
  300. }
  301. canvas_set_font(canvas, FontSecondary);
  302. canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignBottom, "Press OK to start");
  303. canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignBottom, "^ to select char");
  304. if(game_state->high_score > 0) {
  305. char hi_buffer[24];
  306. snprintf(hi_buffer, sizeof(hi_buffer), "Best: %d", game_state->high_score);
  307. canvas_draw_str_aligned(canvas, 64, 52, AlignCenter, AlignBottom, hi_buffer);
  308. }
  309. } else {
  310. // Character selection menu with larger box
  311. canvas_set_color(canvas, ColorWhite);
  312. canvas_draw_box(canvas, 16, 4, 96, 56); // Much bigger box
  313. canvas_set_color(canvas, ColorBlack);
  314. canvas_draw_frame(canvas, 16, 4, 96, 56);
  315. // Title more space from top
  316. canvas_set_font(canvas, FontPrimary);
  317. canvas_draw_str_aligned(canvas, 64, 14, AlignCenter, AlignBottom, "Select Character");
  318. // Get current character dimensions
  319. CharacterDimensions dims = character_dimensions[game_state->selected_bird];
  320. // Centered position for preview with more vertical space
  321. int preview_x = 64 - (dims.width / 2);
  322. int preview_y = 32 - (dims.height / 2); // Center vertically
  323. // Draw character preview
  324. canvas_draw_icon(
  325. canvas, preview_x, preview_y, bird_sets[game_state->selected_bird][BirdState1]);
  326. // Draw selection arrows with more spacing
  327. canvas_draw_str_aligned(canvas, 26, 34, AlignCenter, AlignBottom, "<");
  328. canvas_draw_str_aligned(canvas, 102, 34, AlignCenter, AlignBottom, ">");
  329. canvas_set_font(canvas, FontSecondary);
  330. // Instructions pushed lower with more spacing
  331. canvas_draw_str_aligned(canvas, 64, 48, AlignCenter, AlignBottom, "</> to choose");
  332. canvas_draw_str_aligned(canvas, 64, 56, AlignCenter, AlignBottom, "OK to confirm");
  333. }
  334. }
  335. if(game_state->state == GameStateLife) {
  336. // Get current gap height for rendering
  337. int gap_height = get_gap_height(game_state->selected_bird);
  338. // Pilars
  339. for(int i = 0; i < FLAPPY_PILAR_MAX; i++) {
  340. const PILAR* pilar = &game_state->pilars[i];
  341. if(pilar != NULL && pilar->visible == 1) {
  342. canvas_draw_frame(
  343. canvas, pilar->point.x, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height);
  344. canvas_draw_frame(
  345. canvas, pilar->point.x + 1, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height);
  346. canvas_draw_frame(
  347. canvas,
  348. pilar->point.x + 2,
  349. pilar->point.y,
  350. FLAPPY_GAB_WIDTH - 1,
  351. pilar->height);
  352. canvas_draw_frame(
  353. canvas,
  354. pilar->point.x,
  355. pilar->point.y + pilar->height + gap_height,
  356. FLAPPY_GAB_WIDTH,
  357. FLIPPER_LCD_HEIGHT - pilar->height - gap_height);
  358. canvas_draw_frame(
  359. canvas,
  360. pilar->point.x + 1,
  361. pilar->point.y + pilar->height + gap_height,
  362. FLAPPY_GAB_WIDTH - 1,
  363. FLIPPER_LCD_HEIGHT - pilar->height - gap_height);
  364. canvas_draw_frame(
  365. canvas,
  366. pilar->point.x + 2,
  367. pilar->point.y + pilar->height + gap_height,
  368. FLAPPY_GAB_WIDTH - 1,
  369. FLIPPER_LCD_HEIGHT - pilar->height - gap_height);
  370. }
  371. }
  372. // Switch animation - Using character directly from bird_sets
  373. int bird_state = BirdState1;
  374. if(game_state->bird.gravity < -0.5)
  375. bird_state = BirdState0;
  376. else if(game_state->bird.gravity > 0.5)
  377. bird_state = BirdState2;
  378. // Get current character dimensions
  379. CharacterDimensions dims = character_dimensions[game_state->selected_bird];
  380. // Adjust Y position to keep character in bounds
  381. int adjusted_y = game_state->bird.point.y;
  382. if(adjusted_y < 0) adjusted_y = 0;
  383. if(adjusted_y > FLIPPER_LCD_HEIGHT - dims.height) {
  384. adjusted_y = FLIPPER_LCD_HEIGHT - dims.height;
  385. }
  386. // Draw the character with adjusted position
  387. canvas_draw_icon(
  388. canvas,
  389. game_state->bird.point.x,
  390. adjusted_y,
  391. bird_sets[game_state->selected_bird][bird_state]);
  392. // Score display
  393. canvas_set_font(canvas, FontSecondary);
  394. char buffer[12];
  395. snprintf(buffer, sizeof(buffer), "%u", game_state->points);
  396. canvas_draw_str_aligned(canvas, 64, 12, AlignCenter, AlignBottom, buffer);
  397. if(game_state->debug) {
  398. char coordinates[20];
  399. snprintf(coordinates, sizeof(coordinates), "Y: %u", adjusted_y);
  400. canvas_draw_str_aligned(canvas, 1, 12, AlignCenter, AlignBottom, coordinates);
  401. }
  402. }
  403. if(game_state->state == GameStateGameOver) {
  404. // Adjusted box height for exactly 3 lines of text
  405. canvas_set_color(canvas, ColorWhite);
  406. canvas_draw_box(canvas, 24, 12, 82, 36); // Reduced height from 42 to 36
  407. canvas_set_color(canvas, ColorBlack);
  408. canvas_draw_frame(canvas, 24, 12, 82, 36);
  409. // Game Over text
  410. canvas_set_font(canvas, FontPrimary);
  411. canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignBottom, "Game Over");
  412. // Current score
  413. canvas_set_font(canvas, FontSecondary);
  414. char buffer[12];
  415. snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
  416. canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignBottom, buffer);
  417. // High score
  418. char hi_buffer[16];
  419. snprintf(hi_buffer, sizeof(hi_buffer), "Best: %u", game_state->high_score);
  420. canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignBottom, hi_buffer);
  421. // New Best! message (shown outside the box)
  422. if(game_state->points > game_state->high_score) {
  423. canvas_draw_str_aligned(canvas, 64, 52, AlignCenter, AlignBottom, "New Best!");
  424. }
  425. // Collision effect
  426. if(game_state->collision_frame > 0) {
  427. canvas_invert_color(canvas);
  428. }
  429. }
  430. furi_mutex_release(game_state->mutex);
  431. }
  432. static void flappy_game_input_callback(InputEvent* input_event, void* ctx) {
  433. furi_assert(ctx);
  434. FuriMessageQueue* event_queue = ctx;
  435. GameEvent event = {.type = EventTypeKey, .input = *input_event};
  436. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  437. }
  438. static void flappy_game_update_timer_callback(void* ctx) {
  439. furi_assert(ctx);
  440. FuriMessageQueue* event_queue = ctx;
  441. GameEvent event = {.type = EventTypeTick};
  442. furi_message_queue_put(event_queue, &event, 0);
  443. }
  444. int32_t flappy_game_app(void* p) {
  445. UNUSED(p);
  446. int32_t return_code = 0;
  447. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
  448. GameState* game_state = malloc(sizeof(GameState));
  449. flappy_game_state_init(game_state);
  450. game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  451. if(!game_state->mutex) {
  452. FURI_LOG_E(TAG, "cannot create mutex\r\n");
  453. return_code = 255;
  454. goto free_and_exit;
  455. }
  456. // Set system callbacks
  457. ViewPort* view_port = view_port_alloc();
  458. view_port_draw_callback_set(view_port, flappy_game_render_callback, game_state);
  459. view_port_input_callback_set(view_port, flappy_game_input_callback, event_queue);
  460. FuriTimer* timer =
  461. furi_timer_alloc(flappy_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
  462. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
  463. // Open GUI and register view_port
  464. Gui* gui = furi_record_open(RECORD_GUI);
  465. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  466. // Call dolphin deed on game start
  467. dolphin_deed(DolphinDeedPluginGameStart);
  468. GameEvent event;
  469. for(bool processing = true; processing;) {
  470. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  471. furi_mutex_acquire(game_state->mutex, FuriWaitForever);
  472. if(event_status == FuriStatusOk) {
  473. // press events
  474. if(event.type == EventTypeKey) {
  475. if(event.input.type == InputTypePress) {
  476. switch(event.input.key) {
  477. case InputKeyUp:
  478. if(game_state->state == GameStateStart && !game_state->in_bird_select) {
  479. game_state->in_bird_select = true;
  480. } else if(game_state->state == GameStateLife) {
  481. flappy_game_flap(game_state);
  482. }
  483. break;
  484. case InputKeyDown:
  485. if(game_state->state == GameStateStart && game_state->in_bird_select) {
  486. game_state->in_bird_select = false;
  487. }
  488. break;
  489. case InputKeyLeft:
  490. if(game_state->state == GameStateStart && game_state->in_bird_select) {
  491. if(game_state->selected_bird == 0) {
  492. game_state->selected_bird = BirdTypeMAX - 1;
  493. } else {
  494. game_state->selected_bird--;
  495. }
  496. }
  497. break;
  498. case InputKeyRight:
  499. if(game_state->state == GameStateStart && game_state->in_bird_select) {
  500. game_state->selected_bird =
  501. (game_state->selected_bird + 1) % BirdTypeMAX;
  502. }
  503. break;
  504. case InputKeyOk:
  505. if(game_state->state == GameStateStart) {
  506. if(game_state->in_bird_select) {
  507. game_state->in_bird_select = false;
  508. } else {
  509. game_state->state = GameStateLife;
  510. }
  511. } else if(game_state->state == GameStateGameOver) {
  512. flappy_game_state_init(game_state);
  513. } else if(game_state->state == GameStateLife) {
  514. flappy_game_flap(game_state);
  515. }
  516. break;
  517. case InputKeyBack:
  518. if(game_state->state == GameStateStart && game_state->in_bird_select) {
  519. game_state->in_bird_select = false;
  520. } else {
  521. processing = false;
  522. }
  523. break;
  524. default:
  525. break;
  526. }
  527. }
  528. } else if(event.type == EventTypeTick) {
  529. flappy_game_tick(game_state);
  530. }
  531. }
  532. furi_mutex_release(game_state->mutex);
  533. view_port_update(view_port);
  534. }
  535. furi_timer_free(timer);
  536. view_port_enabled_set(view_port, false);
  537. gui_remove_view_port(gui, view_port);
  538. furi_record_close(RECORD_GUI);
  539. view_port_free(view_port);
  540. furi_mutex_free(game_state->mutex);
  541. free_and_exit:
  542. flappy_game_state_free(game_state);
  543. furi_message_queue_free(event_queue);
  544. return return_code;
  545. }