heap_defence.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. //
  2. // Created by moh on 30.11.2021.
  3. //
  4. // Ported to latest firmware by @xMasterX - 18 Oct 2022
  5. //
  6. #include <string.h>
  7. #include "hede_assets.h"
  8. #include "heap_defence_icons.h"
  9. #include <furi.h>
  10. #include <gui/gui.h>
  11. #include <input/input.h>
  12. #include <notification/notification.h>
  13. #include <notification/notification_messages.h>
  14. #include <dolphin/dolphin.h>
  15. #define Y_FIELD_SIZE 6
  16. #define Y_LAST (Y_FIELD_SIZE - 1)
  17. #define X_FIELD_SIZE 12
  18. #define X_LAST (X_FIELD_SIZE - 1)
  19. #define DRAW_X_OFFSET 4
  20. #define TAG "HeDe"
  21. #define BOX_HEIGHT 10
  22. #define BOX_WIDTH 10
  23. #define TIMER_UPDATE_FREQ 8
  24. #define BOX_GENERATION_RATE 15
  25. static IconAnimation* BOX_DESTROYED;
  26. static const Icon* boxes[] = {
  27. (Icon*)&A_HD_BoxDestroyed_10x10,
  28. &I_Box1_10x10,
  29. &I_Box2_10x10,
  30. &I_Box3_10x10,
  31. &I_Box4_10x10,
  32. &I_Box5_10x10};
  33. static uint8_t BOX_TEXTURE_COUNT = sizeof(boxes) / sizeof(Icon*);
  34. typedef enum {
  35. AnimationGameOver = 0,
  36. AnimationPause,
  37. AnimationLeft,
  38. AnimationRight,
  39. } Animations;
  40. static IconAnimation* animations[4];
  41. typedef uint8_t byte;
  42. typedef enum {
  43. GameStatusVibro = 1 << 0,
  44. GameStatusInProgress = 1 << 1,
  45. } GameStatuses;
  46. typedef struct {
  47. uint8_t x;
  48. uint8_t y;
  49. } Position;
  50. typedef enum {
  51. PlayerRising = 1,
  52. PlayerFalling = -1,
  53. PlayerNothing = 0
  54. } PlayerStates;
  55. typedef struct {
  56. Position p;
  57. int8_t x_direction;
  58. int8_t j_tick;
  59. int8_t h_tick;
  60. int8_t states;
  61. bool right_frame;
  62. } Person;
  63. typedef struct {
  64. uint8_t offset : 4;
  65. uint8_t box_id : 3;
  66. uint8_t exists : 1;
  67. } Box;
  68. static const uint8_t ROW_BYTE_SIZE = sizeof(Box) * X_FIELD_SIZE;
  69. typedef struct {
  70. Box** field;
  71. Person* person;
  72. Animations animation;
  73. GameStatuses game_status;
  74. FuriMutex* mutex;
  75. } GameState;
  76. typedef Box** Field;
  77. typedef enum {
  78. EventGameTick,
  79. EventKeyPress
  80. } EventType;
  81. typedef struct {
  82. EventType type;
  83. InputEvent input;
  84. } GameEvent;
  85. /**
  86. * #Construct / Destroy
  87. */
  88. static void game_reset_field_and_player(GameState* game) {
  89. ///Reset field
  90. bzero(game->field[0], X_FIELD_SIZE * Y_FIELD_SIZE * sizeof(Box));
  91. ///Reset person
  92. bzero(game->person, sizeof(Person));
  93. game->person->p.x = X_FIELD_SIZE / 2;
  94. game->person->p.y = Y_LAST;
  95. }
  96. static GameState* allocGameState() {
  97. GameState* game = malloc(sizeof(GameState));
  98. game->person = malloc(sizeof(Person));
  99. game->field = malloc(Y_FIELD_SIZE * sizeof(Box*));
  100. game->field[0] = malloc(X_FIELD_SIZE * Y_FIELD_SIZE * sizeof(Box));
  101. for(int y = 1; y < Y_FIELD_SIZE; ++y) {
  102. game->field[y] = game->field[0] + (y * X_FIELD_SIZE);
  103. }
  104. game_reset_field_and_player(game);
  105. game->game_status = GameStatusInProgress;
  106. return game;
  107. }
  108. static void game_destroy(GameState* game) {
  109. furi_assert(game);
  110. free(game->field[0]);
  111. free(game->field);
  112. free(game);
  113. }
  114. static void assets_load() {
  115. /// Init animations
  116. animations[AnimationPause] = icon_animation_alloc(&A_HD_start_128x64);
  117. animations[AnimationGameOver] = icon_animation_alloc(&A_HD_game_over_128x64);
  118. animations[AnimationLeft] = icon_animation_alloc(&A_HD_person_left_10x20);
  119. animations[AnimationRight] = icon_animation_alloc(&A_HD_person_right_10x20);
  120. BOX_DESTROYED = icon_animation_alloc(&A_HD_BoxDestroyed_10x10);
  121. icon_animation_start(animations[AnimationLeft]);
  122. icon_animation_start(animations[AnimationRight]);
  123. }
  124. static void assets_clear() {
  125. for(int i = 0; i < 4; ++i) {
  126. icon_animation_stop(animations[i]);
  127. icon_animation_free(animations[i]);
  128. }
  129. icon_animation_free(BOX_DESTROYED);
  130. }
  131. /**
  132. * Box utils
  133. */
  134. static inline bool is_empty(Box* box) {
  135. return !box->exists;
  136. }
  137. static inline bool has_dropped(Box* box) {
  138. return box->offset == 0;
  139. }
  140. static Box* get_upper_box(Field field, Position current) {
  141. return (&field[current.y - 1][current.x]);
  142. }
  143. static Box* get_lower_box(Field field, Position current) {
  144. return (&field[current.y + 1][current.x]);
  145. }
  146. static Box* get_next_box(Field field, Position current, int x_direction) {
  147. return (&field[current.y][current.x + x_direction]);
  148. }
  149. static inline void decrement_y_offset_to_zero(Box* n) {
  150. if(n->offset) --n->offset;
  151. }
  152. static inline void heap_swap(Box* first, Box* second) {
  153. Box temp = *first;
  154. *first = *second;
  155. *second = temp;
  156. }
  157. /**
  158. * #Box logic
  159. */
  160. static void generate_box(GameState const* game) {
  161. furi_assert(game);
  162. static byte tick_count = BOX_GENERATION_RATE;
  163. if(tick_count++ != BOX_GENERATION_RATE) {
  164. return;
  165. }
  166. tick_count = 0;
  167. int x_offset = rand() % X_FIELD_SIZE;
  168. while(game->field[1][x_offset].exists) {
  169. x_offset = rand() % X_FIELD_SIZE;
  170. }
  171. game->field[1][x_offset].exists = true;
  172. game->field[1][x_offset].offset = BOX_HEIGHT;
  173. game->field[1][x_offset].box_id = (rand() % (BOX_TEXTURE_COUNT - 1)) + 1;
  174. }
  175. static void drop_box(GameState* game) {
  176. furi_assert(game);
  177. for(int y = Y_LAST; y > 0; y--) {
  178. for(int x = 0; x < X_FIELD_SIZE; x++) {
  179. Box* current_box = game->field[y] + x;
  180. Box* upper_box = game->field[y - 1] + x;
  181. if(y == Y_LAST) {
  182. decrement_y_offset_to_zero(current_box);
  183. }
  184. decrement_y_offset_to_zero(upper_box);
  185. if(is_empty(current_box) && !is_empty(upper_box) && has_dropped(upper_box)) {
  186. upper_box->offset = BOX_HEIGHT;
  187. heap_swap(current_box, upper_box);
  188. }
  189. }
  190. }
  191. }
  192. static bool clear_rows(Box** field) {
  193. for(int x = 0; x < X_FIELD_SIZE; ++x) {
  194. if(is_empty(field[Y_LAST] + x) || !has_dropped(field[Y_LAST] + x)) {
  195. return false;
  196. }
  197. }
  198. memset(field[Y_LAST], 128, ROW_BYTE_SIZE);
  199. return true;
  200. }
  201. /**
  202. * Input Handling
  203. */
  204. static inline bool on_ground(Person* person, Field field) {
  205. return person->p.y == Y_LAST || field[person->p.y + 1][person->p.x].exists;
  206. }
  207. static void handle_key_presses(Person* person, InputEvent* input, GameState* game) {
  208. switch(input->key) {
  209. case InputKeyUp:
  210. if(person->states == PlayerNothing && on_ground(person, game->field)) {
  211. person->states = PlayerRising;
  212. person->j_tick = 0;
  213. }
  214. break;
  215. case InputKeyLeft:
  216. person->right_frame = false;
  217. if(person->h_tick == 0) {
  218. person->h_tick = 1;
  219. person->x_direction = -1;
  220. }
  221. break;
  222. case InputKeyRight:
  223. person->right_frame = true;
  224. if(person->h_tick == 0) {
  225. person->h_tick = 1;
  226. person->x_direction = 1;
  227. }
  228. break;
  229. case InputKeyOk:
  230. game->game_status &= ~GameStatusInProgress;
  231. game->animation = AnimationPause;
  232. icon_animation_start(animations[AnimationPause]);
  233. default:
  234. break;
  235. }
  236. }
  237. /**
  238. * #Person logic
  239. */
  240. static inline bool ground_box_check(Field field, Position new_position) {
  241. Box* lower_box = get_lower_box(field, new_position);
  242. bool ground_box_dropped =
  243. (new_position.y == Y_LAST || //Eсли мы и так в самом низу
  244. is_empty(lower_box) || // Ecли снизу пустота
  245. has_dropped(lower_box)); //Eсли бокс снизу допадал
  246. return ground_box_dropped;
  247. }
  248. static inline bool is_movable(Field field, Position box_pos, int x_direction) {
  249. //TODO::Moжет и не двух, предположение
  250. bool out_of_bounds = box_pos.x == 0 || box_pos.x == X_LAST;
  251. if(out_of_bounds) return false;
  252. bool box_on_top = box_pos.y < 1 || get_upper_box(field, box_pos)->exists;
  253. if(box_on_top) return false;
  254. bool has_next_box = get_next_box(field, box_pos, x_direction)->exists;
  255. if(has_next_box) return false;
  256. return true;
  257. }
  258. static bool horizontal_move(Person* person, Field field) {
  259. Position new_position = person->p;
  260. if(!person->x_direction) return false;
  261. new_position.x += person->x_direction;
  262. bool on_edge_column = new_position.x > X_LAST;
  263. if(on_edge_column) return false;
  264. if(is_empty(&field[new_position.y][new_position.x])) {
  265. bool ground_box_dropped = ground_box_check(field, new_position);
  266. if(ground_box_dropped) {
  267. person->p = new_position;
  268. return true;
  269. }
  270. } else if(is_movable(field, new_position, person->x_direction)) {
  271. *get_next_box(field, new_position, person->x_direction) =
  272. field[new_position.y][new_position.x];
  273. field[new_position.y][new_position.x] = (Box){0};
  274. person->p = new_position;
  275. return true;
  276. }
  277. return false;
  278. }
  279. void hd_person_set_state(Person* person, PlayerStates state) {
  280. person->states = state;
  281. person->j_tick = 0;
  282. }
  283. static void person_move(Person* person, Field field) {
  284. /// Left-right logic
  285. FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__);
  286. if(person->states == PlayerNothing) {
  287. if(!on_ground(person, field)) {
  288. hd_person_set_state(person, PlayerFalling);
  289. }
  290. } else if(person->states == PlayerRising) {
  291. if(person->j_tick++ == 0) {
  292. person->p.y--;
  293. } else if(person->j_tick == 6) {
  294. hd_person_set_state(person, PlayerNothing);
  295. }
  296. /// Destroy upper box
  297. get_upper_box(field, person->p)->box_id = 0;
  298. field[person->p.y][person->p.x].box_id = 0;
  299. } else if(person->states == PlayerFalling) {
  300. if(person->j_tick++ == 0) {
  301. if(on_ground(person, field)) { // TODO: Test the bugfix
  302. hd_person_set_state(person, PlayerNothing);
  303. } else {
  304. person->p.y++;
  305. }
  306. } else if(person->j_tick == 5) {
  307. if(on_ground(person, field)) {
  308. hd_person_set_state(person, PlayerNothing);
  309. } else {
  310. hd_person_set_state(person, PlayerFalling);
  311. }
  312. }
  313. }
  314. switch(person->h_tick) {
  315. case 0:
  316. break;
  317. case 1:
  318. person->h_tick++;
  319. FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__);
  320. bool moved = horizontal_move(person, field);
  321. if(!moved) {
  322. person->h_tick = 0;
  323. person->x_direction = 0;
  324. }
  325. break;
  326. case 5:
  327. FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__);
  328. person->h_tick = 0;
  329. person->x_direction = 0;
  330. break;
  331. default:
  332. FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__);
  333. person->h_tick++;
  334. }
  335. }
  336. static inline bool is_person_dead(Person* person, Box** field) {
  337. return get_upper_box(field, person->p)->box_id != 0;
  338. }
  339. /**
  340. * #Callback
  341. */
  342. static void draw_box(Canvas* canvas, Box* box, int x, int y) {
  343. if(is_empty(box)) {
  344. return;
  345. }
  346. byte y_screen = y * BOX_HEIGHT - box->offset;
  347. byte x_screen = x * BOX_WIDTH + DRAW_X_OFFSET;
  348. if(box->box_id == 0) {
  349. canvas_set_bitmap_mode(canvas, true);
  350. icon_animation_start(BOX_DESTROYED);
  351. canvas_draw_icon_animation(canvas, x_screen, y_screen, BOX_DESTROYED);
  352. if(icon_animation_is_last_frame(BOX_DESTROYED)) {
  353. *box = (Box){0};
  354. icon_animation_stop(BOX_DESTROYED);
  355. }
  356. canvas_set_bitmap_mode(canvas, false);
  357. } else {
  358. canvas_draw_icon(canvas, x_screen, y_screen, boxes[box->box_id]);
  359. }
  360. }
  361. static void heap_defense_render_callback(Canvas* const canvas, void* mutex) {
  362. furi_assert(mutex);
  363. const GameState* game = mutex;
  364. furi_mutex_acquire(game->mutex, FuriWaitForever);
  365. ///Draw GameOver or Pause
  366. if(!(game->game_status & GameStatusInProgress)) {
  367. FURI_LOG_W(TAG, "[DAED_DRAW]func: [%s] line: %d ", __FUNCTION__, __LINE__);
  368. canvas_draw_icon_animation(canvas, 0, 0, animations[game->animation]);
  369. furi_mutex_release(game->mutex);
  370. return;
  371. }
  372. ///Draw field
  373. canvas_draw_icon(canvas, 0, 0, &I_Background_128x64);
  374. ///Draw Person
  375. const Person* person = game->person;
  376. IconAnimation* player_animation = person->right_frame ? animations[AnimationRight] :
  377. animations[AnimationLeft];
  378. uint8_t x_screen = person->p.x * BOX_WIDTH + DRAW_X_OFFSET;
  379. if(person->h_tick && person->h_tick != 1) {
  380. if(person->right_frame) {
  381. x_screen += (person->h_tick) * 2 - BOX_WIDTH;
  382. } else {
  383. x_screen -= (person->h_tick) * 2 - BOX_WIDTH;
  384. }
  385. }
  386. uint8_t y_screen = (person->p.y - 1) * BOX_HEIGHT;
  387. if(person->j_tick) {
  388. if(person->states == PlayerRising) {
  389. y_screen += BOX_HEIGHT - (person->j_tick) * 2;
  390. } else if(person->states == PlayerFalling) {
  391. y_screen -= BOX_HEIGHT - (person->j_tick) * 2;
  392. }
  393. }
  394. canvas_draw_icon_animation(canvas, x_screen, y_screen, player_animation);
  395. ///Draw Boxes
  396. canvas_set_color(canvas, ColorBlack);
  397. for(int y = 1; y < Y_FIELD_SIZE; ++y) {
  398. for(int x = 0; x < X_FIELD_SIZE; ++x) {
  399. draw_box(canvas, &(game->field[y][x]), x, y);
  400. }
  401. }
  402. furi_mutex_release(game->mutex);
  403. }
  404. static void heap_defense_input_callback(InputEvent* input_event, void* ctx) {
  405. FuriMessageQueue* event_queue = ctx;
  406. if(input_event->type != InputTypePress && input_event->type != InputTypeLong) return;
  407. furi_assert(event_queue);
  408. GameEvent event = {.type = EventKeyPress, .input = *input_event};
  409. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  410. }
  411. static void heap_defense_timer_callback(void* ctx) {
  412. FuriMessageQueue* event_queue = ctx;
  413. furi_assert(event_queue);
  414. GameEvent event;
  415. event.type = EventGameTick;
  416. event.input = (InputEvent){0};
  417. furi_message_queue_put(event_queue, &event, 0);
  418. }
  419. int32_t heap_defence_app(void* p) {
  420. UNUSED(p);
  421. //FURI_LOG_W(TAG, "Heap defence start %d", __LINE__);
  422. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
  423. GameState* game = allocGameState();
  424. game->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  425. if(!game->mutex) {
  426. game_destroy(game);
  427. return 1;
  428. }
  429. assets_load();
  430. ViewPort* view_port = view_port_alloc();
  431. view_port_draw_callback_set(view_port, heap_defense_render_callback, game);
  432. view_port_input_callback_set(view_port, heap_defense_input_callback, event_queue);
  433. FuriTimer* timer =
  434. furi_timer_alloc(heap_defense_timer_callback, FuriTimerTypePeriodic, event_queue);
  435. furi_timer_start(timer, furi_kernel_get_tick_frequency() / TIMER_UPDATE_FREQ);
  436. Gui* gui = furi_record_open(RECORD_GUI);
  437. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  438. NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
  439. memset(game->field[Y_LAST], 128, ROW_BYTE_SIZE);
  440. game->person->p.y -= 2;
  441. game->game_status = 0;
  442. game->animation = AnimationPause;
  443. // Call dolphin deed on game start
  444. dolphin_deed(DolphinDeedPluginGameStart);
  445. GameEvent event = {0};
  446. while(event.input.key != InputKeyBack) {
  447. if(furi_message_queue_get(event_queue, &event, 100) != FuriStatusOk) {
  448. continue;
  449. }
  450. furi_mutex_acquire(game->mutex, FuriWaitForever);
  451. //unset vibration
  452. if(game->game_status & GameStatusVibro) {
  453. notification_message(notification, &sequence_reset_vibro);
  454. game->game_status &= ~GameStatusVibro;
  455. icon_animation_stop(BOX_DESTROYED);
  456. memset(game->field[Y_LAST], 0, ROW_BYTE_SIZE);
  457. }
  458. if(!(game->game_status & GameStatusInProgress)) {
  459. if(event.type == EventKeyPress && event.input.key == InputKeyOk) {
  460. game->game_status |= GameStatusInProgress;
  461. icon_animation_stop(animations[game->animation]);
  462. }
  463. } else if(event.type == EventKeyPress) {
  464. handle_key_presses(game->person, &(event.input), game);
  465. } else { // EventGameTick
  466. drop_box(game);
  467. generate_box(game);
  468. if(clear_rows(game->field)) {
  469. notification_message(notification, &sequence_set_vibro_on);
  470. icon_animation_start(BOX_DESTROYED);
  471. game->game_status |= GameStatusVibro;
  472. }
  473. person_move(game->person, game->field);
  474. if(is_person_dead(game->person, game->field)) {
  475. game->game_status &= ~GameStatusInProgress;
  476. game->animation = AnimationGameOver;
  477. icon_animation_start(animations[AnimationGameOver]);
  478. game_reset_field_and_player(game);
  479. notification_message(notification, &sequence_error);
  480. }
  481. }
  482. furi_mutex_release(game->mutex);
  483. view_port_update(view_port);
  484. }
  485. furi_timer_free(timer);
  486. view_port_enabled_set(view_port, false);
  487. gui_remove_view_port(gui, view_port);
  488. view_port_free(view_port);
  489. furi_record_close(RECORD_GUI);
  490. furi_record_close(RECORD_NOTIFICATION);
  491. furi_message_queue_free(event_queue);
  492. assets_clear();
  493. furi_mutex_free(game->mutex);
  494. game_destroy(game);
  495. return 0;
  496. }