zombiez.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. #include <furi.h>
  2. #include <gui/gui.h>
  3. #include <input/input.h>
  4. #include <stdlib.h>
  5. #include <dolphin/dolphin.h>
  6. //ORIGINAL REPO: https://github.com/Dooskington/flipperzero-zombiez
  7. //AUTHORS: https://github.com/Dooskington | https://github.com/DevMilanIan
  8. #include "zombiez.h"
  9. #define ZOMBIES_MAX 3
  10. #define ZOMBIES_WIDTH 5
  11. #define ZOMBIES_HEIGHT 8
  12. #define PROJECTILES_MAX 10
  13. #define MIN_Y 5
  14. #define MAX_Y 58
  15. #define WALL_X 16
  16. #define PLAYER_START_X 8
  17. #define PLAYER_START_Y (MAX_Y - MIN_Y) / 2
  18. typedef enum {
  19. EventTypeTick,
  20. EventTypeKey,
  21. } EventType;
  22. typedef struct {
  23. EventType type;
  24. InputEvent input;
  25. } PluginEvent;
  26. typedef enum {
  27. GameStatePlaying,
  28. GameStateGameOver
  29. } GameState;
  30. typedef struct {
  31. int x;
  32. int y;
  33. } Point;
  34. typedef struct {
  35. Point position;
  36. int hp;
  37. } Player;
  38. typedef struct {
  39. Point position;
  40. int hp;
  41. } Zombie;
  42. typedef struct {
  43. Point position;
  44. } Projectile;
  45. typedef struct {
  46. FuriMutex* mutex;
  47. GameState game_state;
  48. Player player;
  49. size_t zombies_count;
  50. Zombie* zombies[ZOMBIES_MAX];
  51. size_t projectiles_count;
  52. Projectile* projectiles[PROJECTILES_MAX];
  53. uint16_t score;
  54. bool input_shoot;
  55. } PluginState;
  56. static void render_callback(Canvas* const canvas, void* ctx) {
  57. furi_assert(ctx);
  58. const PluginState* plugin_state = ctx;
  59. furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
  60. canvas_draw_frame(canvas, 0, 0, 128, 64);
  61. canvas_set_font(canvas, FontPrimary);
  62. canvas_draw_str_aligned(
  63. canvas,
  64. plugin_state->player.position.x,
  65. plugin_state->player.position.y,
  66. AlignCenter,
  67. AlignCenter,
  68. "@");
  69. canvas_draw_line(canvas, WALL_X, 0, WALL_X, 64);
  70. canvas_draw_line(canvas, WALL_X + 2, 4, WALL_X + 2, 59);
  71. for(int i = 0; i < PROJECTILES_MAX; ++i) {
  72. Projectile* p = plugin_state->projectiles[i];
  73. if(p != NULL) {
  74. canvas_draw_disc(canvas, p->position.x, p->position.y, 3);
  75. }
  76. }
  77. for(int i = 0; i < ZOMBIES_MAX; ++i) {
  78. Zombie* z = plugin_state->zombies[i];
  79. if(z != NULL) {
  80. for(int h = 0; h < ZOMBIES_HEIGHT; h++) {
  81. for(int w = 0; w < ZOMBIES_WIDTH; w++) {
  82. // Switch animation
  83. int zIdx = 0;
  84. if(z->position.x % 2 == 0) {
  85. zIdx = 1;
  86. }
  87. // Draw zombie pixels
  88. if(zombie_array[zIdx][h][w] == 1) {
  89. int x = z->position.x + w;
  90. int y = z->position.y + h;
  91. canvas_draw_dot(canvas, x, y);
  92. }
  93. }
  94. }
  95. }
  96. }
  97. int heart;
  98. if((plugin_state->player.hp - 10) > 5) { // 16, 17, 18, 19, 20
  99. heart = 0;
  100. } else if((plugin_state->player.hp - 5) > 5) { // 11, 12, 13, 14, 15
  101. heart = 1;
  102. } else if((plugin_state->player.hp - 3) > 2) { // 6, 7, 8, 9, 10
  103. heart = 2;
  104. } else if(plugin_state->player.hp > 0) { // 1, 2, 3, 4, 5
  105. heart = 3;
  106. } else { // 0
  107. heart = 4;
  108. }
  109. // visual representation of health
  110. for(int h = 0; h < 5; h++) {
  111. for(int w = 0; w < 5; w++) {
  112. if(heart_array[heart][h][w] == 1) {
  113. int x = 124 - w;
  114. int y = 56 + h;
  115. canvas_draw_dot(canvas, x, y);
  116. }
  117. }
  118. }
  119. // buffer hp + score
  120. char hpBuffer[8];
  121. char scoreBuffer[14];
  122. if(plugin_state->game_state == GameStatePlaying) {
  123. // display ammo / reload
  124. if(plugin_state->projectiles_count >= PROJECTILES_MAX) {
  125. canvas_draw_str_aligned(canvas, 24, 10, AlignLeft, AlignCenter, "RELOAD");
  126. } else {
  127. for(uint8_t i = 0; i < (PROJECTILES_MAX - plugin_state->projectiles_count); i++) {
  128. canvas_draw_box(canvas, 24 + (4 * i), 6, 2, 4);
  129. }
  130. }
  131. // display hp + score
  132. snprintf(hpBuffer, sizeof(hpBuffer), "%u", plugin_state->player.hp);
  133. canvas_draw_str_aligned(canvas, 118, 62, AlignRight, AlignBottom, hpBuffer);
  134. snprintf(scoreBuffer, sizeof(scoreBuffer), "%u", plugin_state->score);
  135. canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, scoreBuffer);
  136. }
  137. // Game Over banner
  138. if(plugin_state->game_state == GameStateGameOver) {
  139. // Screen is 128x64 px
  140. canvas_set_color(canvas, ColorWhite);
  141. canvas_draw_box(canvas, 34, 20, 62, 24);
  142. canvas_set_color(canvas, ColorBlack);
  143. canvas_draw_frame(canvas, 34, 20, 62, 24);
  144. canvas_set_font(canvas, FontPrimary);
  145. canvas_draw_str(canvas, 37, 31, "Game Over");
  146. canvas_set_font(canvas, FontSecondary);
  147. snprintf(scoreBuffer, sizeof(scoreBuffer), "Score: %u", plugin_state->score);
  148. canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, scoreBuffer);
  149. }
  150. //char* info = (char*)malloc(16 * sizeof(char));
  151. //asprintf(&info, "%d, %d", plugin_state->x, plugin_state->y);
  152. //canvas_draw_str_aligned(canvas, 32, 16, AlignLeft, AlignBottom, info);
  153. //free(info);
  154. furi_mutex_release(plugin_state->mutex);
  155. }
  156. static void input_callback(InputEvent* input_event, void* ctx) {
  157. furi_assert(ctx);
  158. FuriMessageQueue* event_queue = ctx;
  159. PluginEvent event = {.type = EventTypeKey, .input = *input_event};
  160. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  161. }
  162. static void tick(PluginState* const plugin_state) {
  163. if(plugin_state->input_shoot && (plugin_state->projectiles_count < PROJECTILES_MAX)) {
  164. Projectile* p = (Projectile*)malloc(sizeof(Projectile));
  165. p->position.x = plugin_state->player.position.x;
  166. p->position.y = plugin_state->player.position.y;
  167. size_t idx = plugin_state->projectiles_count;
  168. plugin_state->projectiles[idx] = p;
  169. plugin_state->projectiles_count += 1;
  170. }
  171. for(int i = 0; i < ZOMBIES_MAX; ++i) {
  172. if(!plugin_state->zombies[i]) {
  173. Zombie* z = (Zombie*)malloc(sizeof(Zombie));
  174. //z->hp = 20;
  175. z->position.x = 126;
  176. z->position.y = MIN_Y + (rand() % (MAX_Y - MIN_Y));
  177. plugin_state->zombies[i] = z;
  178. plugin_state->zombies_count += 1;
  179. }
  180. }
  181. for(int i = 0; i < PROJECTILES_MAX; ++i) {
  182. Projectile* p = plugin_state->projectiles[i];
  183. if(p != NULL) {
  184. p->position.x += 2;
  185. for(int i = 0; i < ZOMBIES_MAX; ++i) {
  186. Zombie* z = plugin_state->zombies[i];
  187. if(z != NULL) {
  188. if( // projectile close enough to zombie
  189. (((z->position.x - p->position.x) <= 2) &&
  190. ((z->position.y - p->position.y) <= 4)) &&
  191. (((p->position.x - z->position.x) <= 2) &&
  192. ((p->position.y - z->position.y) <= 6))) {
  193. //z->hp -= 5;
  194. //if(z->hp <= 0) {
  195. plugin_state->zombies_count -= 1;
  196. free(z);
  197. plugin_state->zombies[i] = NULL;
  198. plugin_state->score++;
  199. //if(plugin_state->score % 15 == 0) dolphin_deed(getRandomDeed());
  200. //}
  201. } else if(z->position.x <= WALL_X && z->position.x > 0) { // zombie got to the wall
  202. plugin_state->zombies_count -= 1;
  203. free(z);
  204. plugin_state->zombies[i] = NULL;
  205. if(plugin_state->player.hp > 0) {
  206. plugin_state->player.hp--;
  207. } else {
  208. plugin_state->game_state = GameStateGameOver;
  209. }
  210. } else {
  211. if(furi_get_tick() % 2 == 0) z->position.x--;
  212. }
  213. }
  214. }
  215. if(p->position.x >= 128) {
  216. free(p);
  217. plugin_state->projectiles[i] = NULL;
  218. }
  219. }
  220. }
  221. plugin_state->input_shoot = false;
  222. }
  223. static void timer_callback(void* ctx) {
  224. furi_assert(ctx);
  225. FuriMessageQueue* event_queue = ctx;
  226. PluginEvent event = {.type = EventTypeTick};
  227. furi_message_queue_put(event_queue, &event, 0);
  228. }
  229. static void zombiez_state_init(PluginState* const plugin_state) {
  230. plugin_state->player.position.x = PLAYER_START_X;
  231. plugin_state->player.position.y = PLAYER_START_Y;
  232. plugin_state->player.hp = 20;
  233. plugin_state->projectiles_count = 0;
  234. plugin_state->zombies_count = 0;
  235. plugin_state->score = 0;
  236. for(int i = 0; i < PROJECTILES_MAX; i++) {
  237. plugin_state->projectiles[i] = NULL;
  238. }
  239. for(int i = 0; i < ZOMBIES_MAX; i++) {
  240. plugin_state->zombies[i] = NULL;
  241. }
  242. plugin_state->game_state = GameStatePlaying;
  243. plugin_state->input_shoot = false;
  244. }
  245. int32_t zombiez_game_app(void* p) {
  246. UNUSED(p);
  247. uint32_t return_code = 0;
  248. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
  249. PluginState* plugin_state = malloc(sizeof(PluginState));
  250. zombiez_state_init(plugin_state);
  251. plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  252. if(!plugin_state->mutex) {
  253. FURI_LOG_E("Zombiez", "cannot create mutex\r\n");
  254. return_code = 255;
  255. goto free_and_exit;
  256. }
  257. // Set system callbacks
  258. ViewPort* view_port = view_port_alloc();
  259. view_port_draw_callback_set(view_port, render_callback, plugin_state);
  260. view_port_input_callback_set(view_port, input_callback, event_queue);
  261. FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
  262. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
  263. // Open GUI and register view_port
  264. Gui* gui = furi_record_open(RECORD_GUI);
  265. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  266. // Call dolphin deed on game start
  267. dolphin_deed(DolphinDeedPluginGameStart);
  268. PluginEvent event;
  269. bool isRunning = true;
  270. while(isRunning) {
  271. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  272. furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
  273. if(event_status == FuriStatusOk) {
  274. if(event.type == EventTypeKey) {
  275. if(event.input.type == InputTypePress) {
  276. switch(event.input.key) {
  277. case InputKeyUp:
  278. if(plugin_state->player.position.y > MIN_Y &&
  279. plugin_state->game_state == GameStatePlaying) {
  280. plugin_state->player.position.y--;
  281. }
  282. break;
  283. case InputKeyDown:
  284. if(plugin_state->player.position.y < MAX_Y &&
  285. plugin_state->game_state == GameStatePlaying) {
  286. plugin_state->player.position.y++;
  287. }
  288. break;
  289. case InputKeyOk:
  290. if(plugin_state->projectiles_count < PROJECTILES_MAX &&
  291. plugin_state->game_state == GameStatePlaying) {
  292. plugin_state->input_shoot = true;
  293. }
  294. break;
  295. case InputKeyBack:
  296. break;
  297. default:
  298. break;
  299. }
  300. } else if(
  301. event.input.type == InputTypeRepeat &&
  302. plugin_state->game_state == GameStatePlaying) {
  303. switch(event.input.key) {
  304. case InputKeyUp:
  305. if(plugin_state->player.position.y > (MIN_Y + 1)) {
  306. plugin_state->player.position.y -= 4;
  307. }
  308. break;
  309. case InputKeyDown:
  310. if(plugin_state->player.position.y < (MAX_Y - 1)) {
  311. plugin_state->player.position.y += 4;
  312. }
  313. break;
  314. default:
  315. break;
  316. }
  317. } else if(event.input.type == InputTypeLong) {
  318. if(event.input.key == InputKeyOk) {
  319. if(plugin_state->game_state == GameStateGameOver) {
  320. zombiez_state_init(plugin_state);
  321. } else if(plugin_state->projectiles_count >= PROJECTILES_MAX) {
  322. plugin_state->projectiles_count = 0;
  323. plugin_state->player.hp++;
  324. }
  325. } else if(event.input.key == InputKeyBack) {
  326. isRunning = false;
  327. }
  328. }
  329. } else if(event.type == EventTypeTick) {
  330. tick(plugin_state);
  331. }
  332. }
  333. furi_mutex_release(plugin_state->mutex);
  334. view_port_update(view_port);
  335. }
  336. furi_timer_free(timer);
  337. view_port_enabled_set(view_port, false);
  338. gui_remove_view_port(gui, view_port);
  339. furi_record_close(RECORD_GUI);
  340. view_port_free(view_port);
  341. furi_mutex_free(plugin_state->mutex);
  342. free_and_exit:
  343. free(plugin_state);
  344. furi_message_queue_free(event_queue);
  345. return return_code;
  346. }