flappy_bird.c 12 KB

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