jetpack.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. #include <stdlib.h>
  2. #include <jetpack_joyride_icons.h>
  3. #include <furi.h>
  4. #include <gui/gui.h>
  5. #include <gui/icon_animation.h>
  6. #include <input/input.h>
  7. #define TAG "Jetpack Joyride"
  8. #define GRAVITY_BOOST -0.3
  9. #define GRAVITY_TICK 0.15
  10. #define PARTIVLE_VELOCITY 2
  11. #define SCIENTISTS_MAX 6
  12. #define COINS_MAX 25
  13. #define PARTICLES_MAX 50
  14. typedef struct {
  15. int x;
  16. int y;
  17. } POINT;
  18. typedef struct {
  19. float gravity;
  20. POINT point;
  21. IconAnimation* sprite;
  22. bool isBoosting;
  23. } BARRY;
  24. typedef struct {
  25. float gravity;
  26. POINT point;
  27. } COIN;
  28. typedef struct {
  29. POINT point;
  30. } PARTICLE;
  31. typedef struct {
  32. float gravity;
  33. POINT point;
  34. IconAnimation* sprite;
  35. } SCIENTIST;
  36. typedef enum {
  37. GameStateLife,
  38. GameStateGameOver,
  39. } State;
  40. typedef struct {
  41. int points;
  42. int distance;
  43. BARRY barry;
  44. SCIENTIST scientists[SCIENTISTS_MAX];
  45. COIN coins[COINS_MAX];
  46. PARTICLE particles[PARTICLES_MAX];
  47. State state;
  48. FuriMutex* mutex;
  49. } GameState;
  50. typedef enum {
  51. EventTypeTick,
  52. EventTypeKey,
  53. } EventType;
  54. typedef struct {
  55. EventType type;
  56. InputEvent input;
  57. } GameEvent;
  58. static void jetpack_game_random_coins(GameState* const game_state) {
  59. // Check for an available slot for a new coin
  60. for(int i = 0; i < COINS_MAX; ++i) {
  61. if(game_state->coins[i].point.x <= 0 && (rand() % 1000) < 1) {
  62. game_state->coins[i].point.x = 127;
  63. game_state->coins[i].point.y = rand() % 64;
  64. break;
  65. }
  66. }
  67. }
  68. static void jetpack_game_spawn_particles(GameState* const game_state) {
  69. for(int i = 0; i < PARTICLES_MAX; i++) {
  70. if(game_state->particles[i].point.y <= 0) {
  71. game_state->particles[i].point.x = game_state->barry.point.x + (rand() % 7) - 3;
  72. game_state->particles[i].point.y = game_state->barry.point.y;
  73. break;
  74. }
  75. }
  76. }
  77. static void jetpack_game_state_init(GameState* const game_state) {
  78. UNUSED(game_state);
  79. BARRY barry;
  80. barry.gravity = 0;
  81. barry.point.x = 64;
  82. barry.point.y = 32;
  83. barry.sprite = icon_animation_alloc(&A_barry);
  84. barry.isBoosting = false;
  85. icon_animation_start(barry.sprite);
  86. game_state->barry = barry;
  87. game_state->points = 0;
  88. game_state->distance = 0;
  89. game_state->state = GameStateLife;
  90. memset(game_state->scientists, 0, sizeof(game_state->scientists));
  91. memset(game_state->coins, 0, sizeof(game_state->coins));
  92. memset(game_state->particles, 0, sizeof(game_state->particles));
  93. }
  94. static void jetpack_game_state_free(GameState* const game_state) {
  95. icon_animation_free(game_state->barry.sprite);
  96. free(game_state);
  97. }
  98. static void jetpack_game_tick(GameState* const game_state) {
  99. // Do jetpack things
  100. game_state->barry.gravity += GRAVITY_TICK;
  101. game_state->barry.point.y += game_state->barry.gravity;
  102. // Increment distance
  103. game_state->distance++;
  104. // Move coins towards the player
  105. for(int i = 0; i < COINS_MAX; i++) {
  106. if(game_state->coins[i].point.x > 0) {
  107. if(!(game_state->barry.point.x >
  108. game_state->coins[i].point.x + 7 || // Barry is to the right of the coin
  109. game_state->barry.point.x + 11 <
  110. game_state->coins[i].point.x || // Barry is to the left of the coin
  111. game_state->barry.point.y >
  112. game_state->coins[i].point.y + 7 || // Barry is below the coin
  113. game_state->barry.point.y + 15 <
  114. game_state->coins[i].point.y)) { // Barry is above the coin
  115. game_state->coins[i].point.x = 0; // Remove the coin
  116. game_state->points++; // Increase the score
  117. }
  118. game_state->coins[i].point.x -= 1; // move left by 1 unit
  119. if(game_state->coins[i].point.x < -16) { // if the coin is out of screen
  120. game_state->coins[i].point.x =
  121. 0; // set coin x coordinate to 0 to mark it as "inactive"
  122. }
  123. }
  124. }
  125. // Move particles
  126. for(int i = 0; i < PARTICLES_MAX; i++) {
  127. if(game_state->particles[i].point.y > 0) {
  128. game_state->particles[i].point.y += PARTIVLE_VELOCITY;
  129. if(game_state->particles[i].point.x < 0 || game_state->particles[i].point.x > 128 ||
  130. game_state->particles[i].point.y < 0 || game_state->particles[i].point.y > 64) {
  131. game_state->particles[i].point.y = 0;
  132. }
  133. }
  134. }
  135. // Spawn scientists and coins...
  136. jetpack_game_random_coins(game_state);
  137. // Sprite height of Barry
  138. int sprite_height = 15;
  139. // Constrain barry's height within sprite_height and 64 - sprite_height
  140. if(game_state->barry.point.y > (64 - sprite_height)) {
  141. game_state->barry.point.y = 64 - sprite_height;
  142. game_state->barry.gravity = 0; // stop upward momentum
  143. } else if(game_state->barry.point.y < 0) {
  144. game_state->barry.point.y = 0;
  145. game_state->barry.gravity = 0; // stop downward momentum
  146. }
  147. // spawn scientists and coins...
  148. jetpack_game_random_coins(game_state);
  149. if(game_state->barry.isBoosting) {
  150. game_state->barry.gravity += GRAVITY_BOOST;
  151. jetpack_game_spawn_particles(game_state);
  152. }
  153. }
  154. static void jetpack_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. // Draw scene
  161. // Draw coins
  162. for(int i = 0; i < COINS_MAX; ++i) {
  163. if(game_state->coins[i].point.x > 0) {
  164. canvas_draw_icon(
  165. canvas, game_state->coins[i].point.x, game_state->coins[i].point.y, &I_coin);
  166. }
  167. }
  168. // Draw particles
  169. for(int i = 0; i < PARTICLES_MAX; i++) {
  170. if(game_state->particles[i].point.x > 0) {
  171. canvas_draw_line(
  172. canvas,
  173. game_state->particles[i].point.x,
  174. game_state->particles[i].point.y,
  175. game_state->particles[i].point.x,
  176. game_state->particles[i].point.y + 3);
  177. }
  178. }
  179. // Draw barry
  180. canvas_draw_icon_animation(
  181. canvas, game_state->barry.point.x, game_state->barry.point.y, game_state->barry.sprite);
  182. canvas_set_font(canvas, FontSecondary);
  183. char buffer[12];
  184. snprintf(buffer, sizeof(buffer), "Dist: %u", game_state->distance);
  185. canvas_draw_str_aligned(canvas, 123, 12, AlignRight, AlignBottom, buffer);
  186. snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
  187. canvas_draw_str_aligned(canvas, 5, 12, AlignLeft, AlignBottom, buffer);
  188. }
  189. if(game_state->state == GameStateGameOver) {
  190. // Show highscore
  191. }
  192. furi_mutex_release(game_state->mutex);
  193. }
  194. static void jetpack_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  195. furi_assert(event_queue);
  196. GameEvent event = {.type = EventTypeKey, .input = *input_event};
  197. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  198. }
  199. static void jetpack_game_update_timer_callback(FuriMessageQueue* event_queue) {
  200. furi_assert(event_queue);
  201. GameEvent event = {.type = EventTypeTick};
  202. furi_message_queue_put(event_queue, &event, 0);
  203. }
  204. int32_t jetpack_game_app(void* p) {
  205. UNUSED(p);
  206. int32_t return_code = 0;
  207. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
  208. GameState* game_state = malloc(sizeof(GameState));
  209. jetpack_game_state_init(game_state);
  210. game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  211. if(!game_state->mutex) {
  212. FURI_LOG_E(TAG, "cannot create mutex\r\n");
  213. return_code = 255;
  214. goto free_and_exit;
  215. }
  216. // Set system callbacks
  217. ViewPort* view_port = view_port_alloc();
  218. view_port_draw_callback_set(view_port, jetpack_game_render_callback, game_state);
  219. view_port_input_callback_set(view_port, jetpack_game_input_callback, event_queue);
  220. FuriTimer* timer =
  221. furi_timer_alloc(jetpack_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
  222. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
  223. // Open GUI and register view_port
  224. Gui* gui = furi_record_open(RECORD_GUI);
  225. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  226. GameEvent event;
  227. for(bool processing = true; processing;) {
  228. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  229. furi_mutex_acquire(game_state->mutex, FuriWaitForever);
  230. if(event_status == FuriStatusOk) {
  231. // press events
  232. if(event.type == EventTypeKey) {
  233. if(event.input.type == InputTypeRelease && event.input.key == InputKeyOk) {
  234. game_state->barry.isBoosting = false;
  235. }
  236. if(event.input.type == InputTypePress) {
  237. switch(event.input.key) {
  238. case InputKeyUp:
  239. break;
  240. case InputKeyDown:
  241. break;
  242. case InputKeyRight:
  243. break;
  244. case InputKeyLeft:
  245. break;
  246. case InputKeyOk:
  247. if(game_state->state == GameStateGameOver) {
  248. jetpack_game_state_init(game_state);
  249. }
  250. if(game_state->state == GameStateLife) {
  251. // Do something
  252. game_state->barry.isBoosting = true;
  253. }
  254. break;
  255. case InputKeyBack:
  256. processing = false;
  257. break;
  258. default:
  259. break;
  260. }
  261. }
  262. } else if(event.type == EventTypeTick) {
  263. jetpack_game_tick(game_state);
  264. }
  265. }
  266. view_port_update(view_port);
  267. furi_mutex_release(game_state->mutex);
  268. }
  269. furi_timer_free(timer);
  270. view_port_enabled_set(view_port, false);
  271. gui_remove_view_port(gui, view_port);
  272. furi_record_close(RECORD_GUI);
  273. view_port_free(view_port);
  274. furi_mutex_free(game_state->mutex);
  275. free_and_exit:
  276. jetpack_game_state_free(game_state);
  277. furi_message_queue_free(event_queue);
  278. return return_code;
  279. }