level_game.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. #include "level_game.h"
  2. #include "level_message.h"
  3. const NotificationSequence sequence_sound_ball_collide = {
  4. &message_note_c7,
  5. &message_delay_50,
  6. &message_sound_off,
  7. NULL,
  8. };
  9. const NotificationSequence sequence_sound_ball_paddle_collide = {
  10. &message_note_d6,
  11. &message_delay_10,
  12. &message_sound_off,
  13. NULL,
  14. };
  15. const NotificationSequence sequence_sound_ball_lost = {
  16. &message_vibro_on,
  17. &message_note_ds4,
  18. &message_delay_10,
  19. &message_sound_off,
  20. &message_delay_10,
  21. &message_note_ds4,
  22. &message_delay_10,
  23. &message_sound_off,
  24. &message_delay_10,
  25. &message_note_ds4,
  26. &message_delay_10,
  27. &message_sound_off,
  28. &message_delay_10,
  29. &message_vibro_off,
  30. NULL,
  31. };
  32. typedef enum {
  33. GameEventBallLost,
  34. } GameEvent;
  35. /****** Ball ******/
  36. static const EntityDescription paddle_desc;
  37. typedef struct {
  38. Vector speed;
  39. float radius;
  40. float max_speed;
  41. } Ball;
  42. static void ball_reset(Ball* ball) {
  43. ball->max_speed = 2;
  44. ball->speed = (Vector){0, 0};
  45. ball->radius = 2;
  46. }
  47. static void ball_start(Entity* self, GameManager* manager, void* context) {
  48. UNUSED(manager);
  49. Ball* ball = context;
  50. ball_reset(ball);
  51. entity_collider_add_circle(self, ball->radius);
  52. }
  53. static void ball_set_angle(Ball* ball, float angle) {
  54. ball->speed.x = cosf(angle * (M_PI / 180.0f)) * ball->max_speed;
  55. ball->speed.y = sinf(angle * (M_PI / 180.0f)) * ball->max_speed;
  56. }
  57. static void ball_update(Entity* entity, GameManager* manager, void* context) {
  58. UNUSED(manager);
  59. Ball* ball = context;
  60. Vector pos = entity_pos_get(entity);
  61. pos = vector_add(pos, ball->speed);
  62. const Vector screen = {128, 64};
  63. // prevent to go out of screen
  64. if(pos.x - ball->radius < 0) {
  65. pos.x = ball->radius;
  66. ball->speed.x = -ball->speed.x;
  67. } else if(pos.x + ball->radius > screen.x) {
  68. pos.x = screen.x - ball->radius;
  69. ball->speed.x = -ball->speed.x;
  70. } else if(pos.y - ball->radius < 0) {
  71. pos.y = ball->radius;
  72. ball->speed.y = -ball->speed.y;
  73. } else if(pos.y - ball->radius > screen.y) {
  74. Level* level = game_manager_current_level_get(manager);
  75. level_send_event(level, entity, &paddle_desc, GameEventBallLost, (EntityEventValue){0});
  76. }
  77. entity_pos_set(entity, pos);
  78. }
  79. static void ball_render(Entity* entity, GameManager* manager, Canvas* canvas, void* context) {
  80. UNUSED(manager);
  81. Ball* ball = context;
  82. Vector pos = entity_pos_get(entity);
  83. canvas_draw_disc(canvas, pos.x, pos.y, ball->radius);
  84. }
  85. static const EntityDescription ball_desc = {
  86. .start = ball_start,
  87. .stop = NULL,
  88. .update = ball_update,
  89. .render = ball_render,
  90. .collision = NULL,
  91. .event = NULL,
  92. .context_size = sizeof(Ball),
  93. };
  94. /****** Block ******/
  95. static const EntityDescription block_desc;
  96. typedef struct {
  97. Vector size;
  98. } Block;
  99. static void block_spawn(Level* level, Vector pos, Vector size) {
  100. Entity* block = level_add_entity(level, &block_desc);
  101. entity_collider_add_rect(block, size.x, size.y);
  102. entity_pos_set(block, pos);
  103. Block* block_context = entity_context_get(block);
  104. block_context->size = size;
  105. }
  106. static void block_render(Entity* entity, GameManager* manager, Canvas* canvas, void* context) {
  107. UNUSED(manager);
  108. Block* block = context;
  109. Vector pos = entity_pos_get(entity);
  110. canvas_draw_box(
  111. canvas, pos.x - block->size.x / 2, pos.y - block->size.y / 2, block->size.x, block->size.y);
  112. }
  113. static void block_collision(Entity* self, Entity* other, GameManager* manager, void* context) {
  114. UNUSED(manager);
  115. if(entity_description_get(other) == &ball_desc) {
  116. Ball* ball = entity_context_get(other);
  117. Block* block = context;
  118. Vector ball_pos = entity_pos_get(other);
  119. Vector block_pos = entity_pos_get(self);
  120. Vector closest = {
  121. CLAMP(ball_pos.x, block_pos.x + block->size.x / 2, block_pos.x - block->size.x / 2),
  122. CLAMP(ball_pos.y, block_pos.y + block->size.y / 2, block_pos.y - block->size.y / 2),
  123. };
  124. // change the ball speed based on the collision
  125. Vector distance = vector_sub(ball_pos, closest);
  126. if(fabsf(distance.x) < fabsf(distance.y)) {
  127. ball->speed.y = -ball->speed.y;
  128. } else {
  129. ball->speed.x = -ball->speed.x;
  130. }
  131. Level* level = game_manager_current_level_get(manager);
  132. level_remove_entity(level, self);
  133. GameContext* game = game_manager_game_context_get(manager);
  134. game_sound_play(game, &sequence_sound_ball_collide);
  135. if(level_entity_count(level, &block_desc) == 0) {
  136. LevelMessageContext* message_context = level_context_get(game->levels.message);
  137. furi_string_set(message_context->message, "You win!");
  138. game_manager_next_level_set(manager, game->levels.message);
  139. }
  140. }
  141. }
  142. static const EntityDescription block_desc = {
  143. .start = NULL,
  144. .stop = NULL,
  145. .update = NULL,
  146. .render = block_render,
  147. .collision = block_collision,
  148. .event = NULL,
  149. .context_size = sizeof(Block),
  150. };
  151. /****** Paddle ******/
  152. static const Vector paddle_start_size = {30, 3};
  153. typedef struct {
  154. Vector size;
  155. bool ball_launched;
  156. Entity* ball;
  157. } Paddle;
  158. static void paddle_start(Entity* self, GameManager* manager, void* context) {
  159. UNUSED(manager);
  160. Paddle* paddle = context;
  161. paddle->size = paddle_start_size;
  162. paddle->ball_launched = false;
  163. entity_pos_set(self, (Vector){64, 61});
  164. entity_collider_add_rect(self, paddle->size.x, paddle->size.y);
  165. Level* level = game_manager_current_level_get(manager);
  166. paddle->ball = level_add_entity(level, &ball_desc);
  167. }
  168. static void paddle_stop(Entity* entity, GameManager* manager, void* context) {
  169. UNUSED(entity);
  170. Paddle* paddle = context;
  171. Level* level = game_manager_current_level_get(manager);
  172. level_remove_entity(level, paddle->ball);
  173. paddle->ball = NULL;
  174. }
  175. static float paddle_x_from_angle(float angle) {
  176. const float min_angle = -45.0f;
  177. const float max_angle = 45.0f;
  178. return 128.0f * (angle - min_angle) / (max_angle - min_angle);
  179. }
  180. static void paddle_update(Entity* entity, GameManager* manager, void* context) {
  181. Paddle* paddle = context;
  182. InputState input = game_manager_input_get(manager);
  183. GameContext* game_context = game_manager_game_context_get(manager);
  184. Vector pos = entity_pos_get(entity);
  185. float paddle_half = paddle->size.x / 2;
  186. if(game_context->imu_present) {
  187. pos.x = paddle_x_from_angle(-imu_pitch_get(game_context->imu));
  188. } else {
  189. if(input.held & GameKeyLeft) {
  190. pos.x -= 2;
  191. }
  192. if(input.held & GameKeyRight) {
  193. pos.x += 2;
  194. }
  195. }
  196. pos.x = CLAMP(pos.x, 128 - paddle_half, paddle_half);
  197. entity_pos_set(entity, pos);
  198. if(input.pressed & GameKeyBack) {
  199. game_manager_next_level_set(manager, game_context->levels.menu);
  200. }
  201. if(input.pressed & GameKeyOk) {
  202. if(!paddle->ball_launched) {
  203. paddle->ball_launched = true;
  204. Ball* ball = entity_context_get(paddle->ball);
  205. ball_set_angle(ball, 270.0f);
  206. }
  207. }
  208. if(!paddle->ball_launched) {
  209. Vector ball_pos = entity_pos_get(paddle->ball);
  210. Ball* ball = entity_context_get(paddle->ball);
  211. ball_pos.x = pos.x;
  212. ball_pos.y = pos.y - paddle->size.y / 2 - ball->radius;
  213. entity_pos_set(paddle->ball, ball_pos);
  214. }
  215. }
  216. static void paddle_render(Entity* entity, GameManager* manager, Canvas* canvas, void* context) {
  217. UNUSED(manager);
  218. Paddle* paddle = context;
  219. Vector pos = entity_pos_get(entity);
  220. float paddle_half = paddle->size.x / 2;
  221. canvas_draw_box(canvas, pos.x - paddle_half, pos.y, paddle->size.x, paddle->size.y);
  222. }
  223. static void paddle_collision(Entity* self, Entity* other, GameManager* manager, void* context) {
  224. UNUSED(manager);
  225. if(entity_description_get(other) == &ball_desc) {
  226. Ball* ball = entity_context_get(other);
  227. Paddle* paddle = context;
  228. Vector ball_pos = entity_pos_get(other);
  229. Vector paddle_pos = entity_pos_get(self);
  230. float paddle_half = paddle->size.x / 2;
  231. float paddle_center = paddle_pos.x;
  232. float paddle_edge = paddle_center - paddle_half;
  233. float paddle_edge_distance = ball_pos.x - paddle_edge;
  234. float paddle_edge_distance_normalized = paddle_edge_distance / paddle->size.x;
  235. // lerp the angle based on the distance from the paddle center
  236. float angle = 270.0f - 45.0f + 90.0f * paddle_edge_distance_normalized;
  237. ball_set_angle(ball, angle);
  238. GameContext* game = game_manager_game_context_get(manager);
  239. game_sound_play(game, &sequence_sound_ball_paddle_collide);
  240. }
  241. }
  242. static void paddle_event(Entity* self, GameManager* manager, EntityEvent event, void* context) {
  243. UNUSED(manager);
  244. UNUSED(self);
  245. if(event.type == GameEventBallLost) {
  246. Paddle* paddle = context;
  247. paddle->ball_launched = false;
  248. Ball* ball = entity_context_get(paddle->ball);
  249. ball_reset(ball);
  250. GameContext* game = game_manager_game_context_get(manager);
  251. game_sound_play(game, &sequence_sound_ball_lost);
  252. }
  253. }
  254. static const EntityDescription paddle_desc = {
  255. .start = paddle_start,
  256. .stop = paddle_stop,
  257. .update = paddle_update,
  258. .render = paddle_render,
  259. .collision = paddle_collision,
  260. .event = paddle_event,
  261. .context_size = sizeof(Paddle),
  262. };
  263. static void level_1_spawn(Level* level) {
  264. level_add_entity(level, &paddle_desc);
  265. const Vector block_size = {13, 5};
  266. const Vector screen = {128, 64};
  267. const int block_count_x = screen.x / block_size.x;
  268. const int block_count_y = 6;
  269. size_t block_spacing = 1;
  270. for(int y = 0; y < block_count_y; y++) {
  271. for(int x = 0; x < block_count_x; x++) {
  272. Vector pos = {
  273. (x) * (block_size.x + block_spacing) + block_size.x / 2,
  274. (y) * (block_size.y + block_spacing) + block_size.y / 2,
  275. };
  276. block_spawn(level, pos, block_size);
  277. }
  278. }
  279. }
  280. static void level_game_start(Level* level, GameManager* manager, void* context) {
  281. UNUSED(manager);
  282. UNUSED(context);
  283. level_1_spawn(level);
  284. }
  285. static void level_game_stop(Level* level, GameManager* manager, void* context) {
  286. UNUSED(manager);
  287. UNUSED(context);
  288. level_clear(level);
  289. }
  290. const LevelBehaviour level_game = {
  291. .alloc = NULL,
  292. .free = NULL,
  293. .start = level_game_start,
  294. .stop = level_game_stop,
  295. .context_size = 0,
  296. };