laser_tag_app.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. #include "laser_tag_app.h"
  2. #include "laser_tag_view.h"
  3. #include "infrared_controller.h"
  4. #include "game_state.h"
  5. #include <furi.h>
  6. #include <gui/gui.h>
  7. #include <input/input.h>
  8. #include <notification/notification.h>
  9. #define TAG "LaserTagApp"
  10. struct LaserTagApp {
  11. Gui* gui;
  12. ViewPort* view_port;
  13. LaserTagView* view;
  14. FuriMessageQueue* event_queue;
  15. FuriTimer* timer;
  16. NotificationApp* notifications;
  17. InfraredController* ir_controller;
  18. GameState* game_state;
  19. LaserTagState state;
  20. bool need_redraw;
  21. };
  22. const NotificationSequence sequence_vibro_1 = {&message_vibro_on, &message_vibro_off, NULL};
  23. static void laser_tag_app_timer_callback(void* context) {
  24. furi_assert(context);
  25. LaserTagApp* app = context;
  26. FURI_LOG_D(TAG, "Timer callback triggered");
  27. if(app->state == LaserTagStateSplashScreen) {
  28. if(game_state_get_time(app->game_state) >= 2) {
  29. FURI_LOG_I(TAG, "Splash screen time over, switching to TeamSelect");
  30. app->state = LaserTagStateTeamSelect;
  31. game_state_reset(app->game_state);
  32. FURI_LOG_D(TAG, "Game state reset after splash screen");
  33. } else {
  34. FURI_LOG_D(TAG, "Updating splash screen time");
  35. game_state_update_time(app->game_state, 1);
  36. }
  37. } else if(app->state == LaserTagStateGame) {
  38. FURI_LOG_D(TAG, "Updating game time by 1 second");
  39. game_state_update_time(app->game_state, 1);
  40. }
  41. if(app->view) {
  42. FURI_LOG_D(TAG, "Updating view with the latest game state");
  43. laser_tag_view_update(app->view, app->game_state);
  44. app->need_redraw = true;
  45. }
  46. }
  47. static void laser_tag_app_input_callback(InputEvent* input_event, void* context) {
  48. furi_assert(context);
  49. LaserTagApp* app = context;
  50. FURI_LOG_D(TAG, "Input event received: type=%d, key=%d", input_event->type, input_event->key);
  51. furi_message_queue_put(app->event_queue, input_event, 0);
  52. FURI_LOG_D(TAG, "Input event queued successfully");
  53. }
  54. static void laser_tag_app_draw_callback(Canvas* canvas, void* context) {
  55. furi_assert(context);
  56. LaserTagApp* app = context;
  57. FURI_LOG_D(TAG, "Entering draw callback");
  58. if(app->state == LaserTagStateSplashScreen) {
  59. canvas_clear(canvas);
  60. canvas_set_font(canvas, FontPrimary);
  61. canvas_draw_str(canvas, 5, 20, "Laser Tag!");
  62. canvas_set_font(canvas, FontSecondary);
  63. canvas_draw_str(canvas, 5, 40, "https://github.com/");
  64. canvas_draw_str(canvas, 5, 50, "RocketGod-git/");
  65. canvas_draw_str(canvas, 5, 60, "Flipper-Zero-Laser-Tag");
  66. canvas_draw_frame(canvas, 0, 0, 128, 64);
  67. canvas_draw_line(canvas, 0, 30, 127, 30);
  68. canvas_draw_circle(canvas, 110, 15, 12);
  69. canvas_draw_disc(canvas, 110, 15, 4);
  70. } else if(app->state == LaserTagStateTeamSelect) {
  71. canvas_clear(canvas);
  72. canvas_draw_frame(canvas, 0, 0, 128, 64);
  73. canvas_set_font(canvas, FontPrimary);
  74. canvas_draw_str(canvas, 14, 13, "SELECT TEAM");
  75. canvas_draw_line(canvas, 0, 16, 127, 16);
  76. canvas_set_font(canvas, FontSecondary);
  77. canvas_draw_str(canvas, 5, 30, "LEFT");
  78. canvas_draw_str(canvas, 95, 30, "RIGHT");
  79. canvas_set_font(canvas, FontPrimary);
  80. canvas_draw_str(canvas, 10, 45, "RED");
  81. canvas_draw_str(canvas, 95, 45, "BLUE");
  82. // Gun icon for Red team
  83. canvas_draw_line(canvas, 10, 50, 25, 50);
  84. canvas_draw_line(canvas, 25, 50, 25, 55);
  85. canvas_draw_line(canvas, 10, 55, 25, 55);
  86. canvas_draw_line(canvas, 15, 55, 15, 60);
  87. // Gun icon for Blue team (facing left)
  88. canvas_draw_line(canvas, 95, 50, 110, 50);
  89. canvas_draw_line(canvas, 95, 50, 95, 55);
  90. canvas_draw_line(canvas, 95, 55, 110, 55);
  91. canvas_draw_line(canvas, 105, 55, 105, 60);
  92. // Laser beams
  93. canvas_draw_line(canvas, 25, 52, 60, 32);
  94. canvas_draw_line(canvas, 95, 52, 60, 32);
  95. // Targets where lasers hit
  96. canvas_draw_circle(canvas, 60, 32, 5);
  97. canvas_draw_circle(canvas, 60, 32, 2);
  98. } else if(app->view) {
  99. FURI_LOG_D(TAG, "Drawing game view");
  100. laser_tag_view_draw(laser_tag_view_get_view(app->view), canvas);
  101. }
  102. FURI_LOG_D(TAG, "Exiting draw callback");
  103. }
  104. LaserTagApp* laser_tag_app_alloc() {
  105. FURI_LOG_D(TAG, "Allocating Laser Tag App");
  106. LaserTagApp* app = malloc(sizeof(LaserTagApp));
  107. if(!app) {
  108. FURI_LOG_E(TAG, "Failed to allocate LaserTagApp");
  109. return NULL;
  110. }
  111. FURI_LOG_I(TAG, "LaserTagApp allocated successfully");
  112. memset(app, 0, sizeof(LaserTagApp));
  113. app->gui = furi_record_open(RECORD_GUI);
  114. app->view_port = view_port_alloc();
  115. app->view = laser_tag_view_alloc();
  116. app->notifications = furi_record_open(RECORD_NOTIFICATION);
  117. app->game_state = game_state_alloc();
  118. app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  119. if(!app->gui || !app->view_port || !app->view || !app->notifications || !app->game_state ||
  120. !app->event_queue) {
  121. FURI_LOG_E(TAG, "Failed to allocate resources for LaserTagApp");
  122. laser_tag_app_free(app);
  123. return NULL;
  124. }
  125. app->state = LaserTagStateSplashScreen;
  126. app->need_redraw = true;
  127. FURI_LOG_I(TAG, "Initial state set to SplashScreen");
  128. view_port_draw_callback_set(app->view_port, laser_tag_app_draw_callback, app);
  129. view_port_input_callback_set(app->view_port, laser_tag_app_input_callback, app);
  130. gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
  131. FURI_LOG_D(TAG, "ViewPort callbacks set and added to GUI");
  132. app->timer = furi_timer_alloc(laser_tag_app_timer_callback, FuriTimerTypePeriodic, app);
  133. if(!app->timer) {
  134. FURI_LOG_E(TAG, "Failed to allocate timer");
  135. laser_tag_app_free(app);
  136. return NULL;
  137. }
  138. FURI_LOG_I(TAG, "Timer allocated");
  139. furi_timer_start(app->timer, furi_kernel_get_tick_frequency());
  140. FURI_LOG_D(TAG, "Timer started");
  141. return app;
  142. }
  143. void laser_tag_app_free(LaserTagApp* app) {
  144. FURI_LOG_D(TAG, "Freeing Laser Tag App");
  145. furi_assert(app);
  146. furi_timer_free(app->timer);
  147. view_port_enabled_set(app->view_port, false);
  148. gui_remove_view_port(app->gui, app->view_port);
  149. view_port_free(app->view_port);
  150. laser_tag_view_free(app->view);
  151. furi_message_queue_free(app->event_queue);
  152. if(app->ir_controller) {
  153. infrared_controller_free(app->ir_controller);
  154. }
  155. free(app->game_state);
  156. furi_record_close(RECORD_GUI);
  157. furi_record_close(RECORD_NOTIFICATION);
  158. free(app);
  159. FURI_LOG_I(TAG, "Laser Tag App freed successfully");
  160. }
  161. void laser_tag_app_fire(LaserTagApp* app) {
  162. furi_assert(app);
  163. FURI_LOG_D(TAG, "Firing laser");
  164. if(!app->ir_controller) {
  165. FURI_LOG_E(TAG, "IR controller is NULL in laser_tag_app_fire");
  166. return;
  167. }
  168. infrared_controller_send(app->ir_controller);
  169. FURI_LOG_D(TAG, "Laser fired, decreasing ammo by 1");
  170. game_state_decrease_ammo(app->game_state, 1);
  171. notification_message(app->notifications, &sequence_blink_blue_100);
  172. FURI_LOG_I(TAG, "Notifying user with blink blue");
  173. app->need_redraw = true;
  174. }
  175. void laser_tag_app_handle_hit(LaserTagApp* app) {
  176. furi_assert(app);
  177. FURI_LOG_D(TAG, "Handling hit, decreasing health by 10");
  178. game_state_decrease_health(app->game_state, 10);
  179. notification_message(app->notifications, &sequence_vibro_1);
  180. FURI_LOG_I(TAG, "Notifying user with vibration");
  181. if(game_state_is_game_over(app->game_state)) {
  182. FURI_LOG_I(TAG, "Game over, playing game over sound");
  183. notification_message(app->notifications, &sequence_error);
  184. app->need_redraw = true;
  185. }
  186. }
  187. static bool laser_tag_app_enter_game_state(LaserTagApp* app) {
  188. furi_assert(app);
  189. FURI_LOG_I(TAG, "Entering game state");
  190. app->state = LaserTagStateGame;
  191. game_state_reset(app->game_state);
  192. FURI_LOG_D(TAG, "Game state reset");
  193. laser_tag_view_update(app->view, app->game_state);
  194. FURI_LOG_D(TAG, "View updated with new game state");
  195. app->ir_controller = infrared_controller_alloc();
  196. if(!app->ir_controller) {
  197. FURI_LOG_E(TAG, "Failed to allocate IR controller");
  198. return false;
  199. }
  200. FURI_LOG_I(TAG, "IR controller allocated");
  201. infrared_controller_set_team(app->ir_controller, game_state_get_team(app->game_state));
  202. FURI_LOG_D(TAG, "IR controller team set");
  203. app->need_redraw = true;
  204. return true;
  205. }
  206. int32_t laser_tag_app(void* p) {
  207. UNUSED(p);
  208. FURI_LOG_I(TAG, "Laser Tag app starting");
  209. LaserTagApp* app = laser_tag_app_alloc();
  210. if(!app) {
  211. FURI_LOG_E(TAG, "Failed to allocate application");
  212. return -1;
  213. }
  214. FURI_LOG_D(TAG, "LaserTagApp allocated successfully");
  215. InputEvent event;
  216. bool running = true;
  217. while(running) {
  218. FURI_LOG_D(TAG, "Start of main loop iteration");
  219. FuriStatus status = furi_message_queue_get(app->event_queue, &event, 100);
  220. if(status == FuriStatusOk) {
  221. FURI_LOG_D(TAG, "Received input event: type=%d, key=%d", event.type, event.key);
  222. if(event.type == InputTypePress || event.type == InputTypeRepeat) {
  223. if(app->state == LaserTagStateSplashScreen ||
  224. app->state == LaserTagStateTeamSelect) {
  225. switch(event.key) {
  226. case InputKeyLeft:
  227. FURI_LOG_I(TAG, "Red team selected");
  228. game_state_set_team(app->game_state, TeamRed);
  229. if(!laser_tag_app_enter_game_state(app)) {
  230. running = false;
  231. }
  232. break;
  233. case InputKeyRight:
  234. FURI_LOG_I(TAG, "Blue team selected");
  235. game_state_set_team(app->game_state, TeamBlue);
  236. if(!laser_tag_app_enter_game_state(app)) {
  237. running = false;
  238. }
  239. break;
  240. case InputKeyBack:
  241. FURI_LOG_I(TAG, "Back key pressed, exiting");
  242. running = false;
  243. break;
  244. default:
  245. break;
  246. }
  247. } else {
  248. switch(event.key) {
  249. case InputKeyBack:
  250. FURI_LOG_I(TAG, "Back key pressed, exiting");
  251. running = false;
  252. break;
  253. case InputKeyOk:
  254. FURI_LOG_I(TAG, "OK key pressed, firing laser");
  255. laser_tag_app_fire(app);
  256. break;
  257. default:
  258. break;
  259. }
  260. }
  261. }
  262. } else if(status == FuriStatusErrorTimeout) {
  263. FURI_LOG_D(TAG, "No input event, continuing");
  264. } else {
  265. FURI_LOG_E(TAG, "Failed to get input event, status: %d", status);
  266. }
  267. if(app->state == LaserTagStateGame && app->ir_controller) {
  268. if(infrared_controller_receive(app->ir_controller)) {
  269. FURI_LOG_D(TAG, "Hit received, processing");
  270. laser_tag_app_handle_hit(app);
  271. }
  272. if(game_state_is_game_over(app->game_state)) {
  273. FURI_LOG_I(TAG, "Game over, notifying user with error sequence");
  274. notification_message(app->notifications, &sequence_error);
  275. running = false;
  276. }
  277. }
  278. if(app->need_redraw) {
  279. FURI_LOG_D(TAG, "Updating viewport");
  280. view_port_update(app->view_port);
  281. app->need_redraw = false;
  282. }
  283. FURI_LOG_D(TAG, "End of main loop iteration");
  284. furi_delay_ms(10);
  285. }
  286. FURI_LOG_I(TAG, "Laser Tag app exiting");
  287. laser_tag_app_free(app);
  288. return 0;
  289. }