heap_defence.c 17 KB

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