flappy_bird.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. #define TAG "Flappy"
  8. #define DEBUG false
  9. #define FLAPPY_BIRD_HEIGHT 15
  10. #define FLAPPY_BIRD_WIDTH 10
  11. #define FLAPPY_PILAR_MAX 6
  12. #define FLAPPY_PILAR_DIST 35
  13. #define FLAPPY_GAB_HEIGHT 25
  14. #define FLAPPY_GAB_WIDTH 10
  15. #define FLAPPY_GRAVITY_JUMP -1.1
  16. #define FLAPPY_GRAVITY_TICK 0.15
  17. #define FLIPPER_LCD_WIDTH 128
  18. #define FLIPPER_LCD_HEIGHT 64
  19. typedef enum {
  20. EventTypeTick,
  21. EventTypeKey,
  22. } EventType;
  23. typedef enum {
  24. BirdState0 = 0,
  25. BirdState1,
  26. BirdState2,
  27. BirdStateMAX
  28. } BirdState;
  29. const Icon* bird_states[BirdStateMAX] = {
  30. &I_bird_01,
  31. &I_bird_02,
  32. &I_bird_03,
  33. };
  34. typedef struct {
  35. int x;
  36. int y;
  37. } POINT;
  38. typedef struct {
  39. float gravity;
  40. POINT point;
  41. } BIRD;
  42. typedef struct {
  43. POINT point;
  44. int height;
  45. int visible;
  46. bool passed;
  47. } PILAR;
  48. typedef enum {
  49. GameStateLife,
  50. GameStateGameOver,
  51. } State;
  52. typedef struct {
  53. BIRD bird;
  54. int points;
  55. int pilars_count;
  56. PILAR pilars[FLAPPY_PILAR_MAX];
  57. bool debug;
  58. State state;
  59. FuriMutex* mutex;
  60. } GameState;
  61. typedef struct {
  62. EventType type;
  63. InputEvent input;
  64. } GameEvent;
  65. typedef enum {
  66. DirectionUp,
  67. DirectionRight,
  68. DirectionDown,
  69. DirectionLeft,
  70. } Direction;
  71. static void flappy_game_random_pilar(GameState* const game_state) {
  72. PILAR pilar;
  73. pilar.passed = false;
  74. pilar.visible = 1;
  75. pilar.height = random() % (FLIPPER_LCD_HEIGHT - FLAPPY_GAB_HEIGHT) + 1;
  76. pilar.point.y = 0;
  77. pilar.point.x = FLIPPER_LCD_WIDTH + FLAPPY_GAB_WIDTH + 1;
  78. game_state->pilars_count++;
  79. game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX] = pilar;
  80. }
  81. static void flappy_game_state_init(GameState* const game_state) {
  82. BIRD bird;
  83. bird.gravity = 0.0f;
  84. bird.point.x = 15;
  85. bird.point.y = 32;
  86. game_state->debug = DEBUG;
  87. game_state->bird = bird;
  88. game_state->pilars_count = 0;
  89. game_state->points = 0;
  90. game_state->state = GameStateLife;
  91. memset(game_state->pilars, 0, sizeof(game_state->pilars));
  92. flappy_game_random_pilar(game_state);
  93. }
  94. static void flappy_game_state_free(GameState* const game_state) {
  95. free(game_state);
  96. }
  97. static void flappy_game_tick(GameState* const game_state) {
  98. if(game_state->state == GameStateLife) {
  99. if(!game_state->debug) {
  100. game_state->bird.gravity += FLAPPY_GRAVITY_TICK;
  101. game_state->bird.point.y += game_state->bird.gravity;
  102. }
  103. // Checking the location of the last respawned pilar.
  104. PILAR* pilar = &game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX];
  105. if(pilar->point.x == (FLIPPER_LCD_WIDTH - FLAPPY_PILAR_DIST))
  106. flappy_game_random_pilar(game_state);
  107. // Updating the position/status of the pilars (visiblity, posotion, game points)
  108. // | | | | |
  109. // | | | | |
  110. // |__| | |__|
  111. // _____X | X_____
  112. // | | | | | // [Pos + Width of pilar] >= [Bird Pos]
  113. // |_____| | |_____|
  114. // X <----> | X <->
  115. // Bird Pos + Lenght of the bird] >= [Pilar]
  116. for(int i = 0; i < FLAPPY_PILAR_MAX; i++) {
  117. PILAR* pilar = &game_state->pilars[i];
  118. if(pilar != NULL && pilar->visible && game_state->state == GameStateLife) {
  119. pilar->point.x--;
  120. if(game_state->bird.point.x >= pilar->point.x + FLAPPY_GAB_WIDTH &&
  121. pilar->passed == false) {
  122. pilar->passed = true;
  123. game_state->points++;
  124. }
  125. if(pilar->point.x < -FLAPPY_GAB_WIDTH) pilar->visible = 0;
  126. if(game_state->bird.point.y <= 0 - FLAPPY_BIRD_WIDTH) {
  127. game_state->bird.point.y = 64;
  128. }
  129. if(game_state->bird.point.y > 64 - FLAPPY_BIRD_WIDTH) {
  130. game_state->bird.point.y = FLIPPER_LCD_HEIGHT - FLAPPY_BIRD_WIDTH;
  131. }
  132. // Bird inbetween pipes
  133. if((game_state->bird.point.x + FLAPPY_BIRD_HEIGHT >= pilar->point.x) &&
  134. (game_state->bird.point.x <= pilar->point.x + FLAPPY_GAB_WIDTH)) {
  135. // Bird below Bottom Pipe
  136. if(game_state->bird.point.y + FLAPPY_BIRD_WIDTH - 2 >=
  137. pilar->height + FLAPPY_GAB_HEIGHT) {
  138. game_state->state = GameStateGameOver;
  139. break;
  140. }
  141. // Bird above Upper Pipe
  142. if(game_state->bird.point.y < pilar->height) {
  143. game_state->state = GameStateGameOver;
  144. break;
  145. }
  146. }
  147. }
  148. }
  149. }
  150. }
  151. static void flappy_game_flap(GameState* const game_state) {
  152. game_state->bird.gravity = FLAPPY_GRAVITY_JUMP;
  153. }
  154. static void flappy_game_render_callback(Canvas* const canvas, void* ctx) {
  155. furi_assert(ctx);
  156. const GameState* game_state = ctx;
  157. furi_mutex_acquire(game_state->mutex, FuriWaitForever);
  158. canvas_draw_frame(canvas, 0, 0, 128, 64);
  159. if(game_state->state == GameStateLife) {
  160. // Pilars
  161. for(int i = 0; i < FLAPPY_PILAR_MAX; i++) {
  162. const PILAR* pilar = &game_state->pilars[i];
  163. if(pilar != NULL && pilar->visible == 1) {
  164. canvas_draw_frame(
  165. canvas, pilar->point.x, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height);
  166. canvas_draw_frame(
  167. canvas, pilar->point.x + 1, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height);
  168. canvas_draw_frame(
  169. canvas,
  170. pilar->point.x + 2,
  171. pilar->point.y,
  172. FLAPPY_GAB_WIDTH - 1,
  173. pilar->height);
  174. canvas_draw_frame(
  175. canvas,
  176. pilar->point.x,
  177. pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT,
  178. FLAPPY_GAB_WIDTH,
  179. FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT);
  180. canvas_draw_frame(
  181. canvas,
  182. pilar->point.x + 1,
  183. pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT,
  184. FLAPPY_GAB_WIDTH - 1,
  185. FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT);
  186. canvas_draw_frame(
  187. canvas,
  188. pilar->point.x + 2,
  189. pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT,
  190. FLAPPY_GAB_WIDTH - 1,
  191. FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT);
  192. }
  193. }
  194. // Switch animation
  195. BirdState bird_state = BirdState1;
  196. if(game_state->bird.gravity < -0.5)
  197. bird_state = BirdState0;
  198. else if(game_state->bird.gravity > 0.5)
  199. bird_state = BirdState2;
  200. canvas_draw_icon(
  201. canvas, game_state->bird.point.x, game_state->bird.point.y, bird_states[bird_state]);
  202. canvas_set_font(canvas, FontSecondary);
  203. char buffer[12];
  204. snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
  205. canvas_draw_str_aligned(canvas, 100, 12, AlignCenter, AlignBottom, buffer);
  206. if(game_state->debug) {
  207. char coordinates[20];
  208. snprintf(coordinates, sizeof(coordinates), "Y: %u", game_state->bird.point.y);
  209. canvas_draw_str_aligned(canvas, 1, 12, AlignCenter, AlignBottom, coordinates);
  210. }
  211. }
  212. if(game_state->state == GameStateGameOver) {
  213. // Screen is 128x64 px
  214. canvas_set_color(canvas, ColorWhite);
  215. canvas_draw_box(canvas, 34, 20, 62, 24);
  216. canvas_set_color(canvas, ColorBlack);
  217. canvas_draw_frame(canvas, 34, 20, 62, 24);
  218. canvas_set_font(canvas, FontPrimary);
  219. canvas_draw_str(canvas, 37, 31, "Game Over");
  220. canvas_set_font(canvas, FontSecondary);
  221. char buffer[12];
  222. snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
  223. canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
  224. }
  225. furi_mutex_release(game_state->mutex);
  226. }
  227. static void flappy_game_input_callback(InputEvent* input_event, void* ctx) {
  228. furi_assert(ctx);
  229. FuriMessageQueue* event_queue = ctx;
  230. GameEvent event = {.type = EventTypeKey, .input = *input_event};
  231. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  232. }
  233. static void flappy_game_update_timer_callback(void* ctx) {
  234. furi_assert(ctx);
  235. FuriMessageQueue* event_queue = ctx;
  236. GameEvent event = {.type = EventTypeTick};
  237. furi_message_queue_put(event_queue, &event, 0);
  238. }
  239. int32_t flappy_game_app(void* p) {
  240. UNUSED(p);
  241. int32_t return_code = 0;
  242. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
  243. GameState* game_state = malloc(sizeof(GameState));
  244. flappy_game_state_init(game_state);
  245. game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  246. if(!game_state->mutex) {
  247. FURI_LOG_E(TAG, "cannot create mutex\r\n");
  248. return_code = 255;
  249. goto free_and_exit;
  250. }
  251. // Set system callbacks
  252. ViewPort* view_port = view_port_alloc();
  253. view_port_draw_callback_set(view_port, flappy_game_render_callback, game_state);
  254. view_port_input_callback_set(view_port, flappy_game_input_callback, event_queue);
  255. FuriTimer* timer =
  256. furi_timer_alloc(flappy_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
  257. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
  258. // Open GUI and register view_port
  259. Gui* gui = furi_record_open(RECORD_GUI);
  260. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  261. // Call dolphin deed on game start
  262. dolphin_deed(DolphinDeedPluginGameStart);
  263. GameEvent event;
  264. for(bool processing = true; processing;) {
  265. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  266. furi_mutex_acquire(game_state->mutex, FuriWaitForever);
  267. if(event_status == FuriStatusOk) {
  268. // press events
  269. if(event.type == EventTypeKey) {
  270. if(event.input.type == InputTypePress) {
  271. switch(event.input.key) {
  272. case InputKeyUp:
  273. if(game_state->state == GameStateLife) {
  274. flappy_game_flap(game_state);
  275. }
  276. break;
  277. case InputKeyDown:
  278. break;
  279. case InputKeyRight:
  280. break;
  281. case InputKeyLeft:
  282. break;
  283. case InputKeyOk:
  284. if(game_state->state == GameStateGameOver) {
  285. flappy_game_state_init(game_state);
  286. }
  287. if(game_state->state == GameStateLife) {
  288. flappy_game_flap(game_state);
  289. }
  290. break;
  291. case InputKeyBack:
  292. processing = false;
  293. break;
  294. default:
  295. break;
  296. }
  297. }
  298. } else if(event.type == EventTypeTick) {
  299. flappy_game_tick(game_state);
  300. }
  301. }
  302. furi_mutex_release(game_state->mutex);
  303. view_port_update(view_port);
  304. }
  305. furi_timer_free(timer);
  306. view_port_enabled_set(view_port, false);
  307. gui_remove_view_port(gui, view_port);
  308. furi_record_close(RECORD_GUI);
  309. view_port_free(view_port);
  310. furi_mutex_free(game_state->mutex);
  311. free_and_exit:
  312. flappy_game_state_free(game_state);
  313. furi_message_queue_free(event_queue);
  314. return return_code;
  315. }