|
@@ -0,0 +1,357 @@
|
|
|
|
|
+#include "level_game.h"
|
|
|
|
|
+#include "level_message.h"
|
|
|
|
|
+
|
|
|
|
|
+const NotificationSequence sequence_sound_ball_collide = {
|
|
|
|
|
+ &message_note_c7,
|
|
|
|
|
+ &message_delay_50,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ NULL,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const NotificationSequence sequence_sound_ball_paddle_collide = {
|
|
|
|
|
+ &message_note_d6,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ NULL,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const NotificationSequence sequence_sound_ball_lost = {
|
|
|
|
|
+ &message_vibro_on,
|
|
|
|
|
+
|
|
|
|
|
+ &message_note_ds4,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+
|
|
|
|
|
+ &message_note_ds4,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+
|
|
|
|
|
+ &message_note_ds4,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+ &message_sound_off,
|
|
|
|
|
+ &message_delay_10,
|
|
|
|
|
+
|
|
|
|
|
+ &message_vibro_off,
|
|
|
|
|
+ NULL,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+typedef enum {
|
|
|
|
|
+ GameEventBallLost,
|
|
|
|
|
+} GameEvent;
|
|
|
|
|
+
|
|
|
|
|
+/****** Ball ******/
|
|
|
|
|
+
|
|
|
|
|
+static const EntityDescription paddle_desc;
|
|
|
|
|
+
|
|
|
|
|
+typedef struct {
|
|
|
|
|
+ Vector speed;
|
|
|
|
|
+ float radius;
|
|
|
|
|
+ float max_speed;
|
|
|
|
|
+} Ball;
|
|
|
|
|
+
|
|
|
|
|
+static void ball_reset(Ball* ball) {
|
|
|
|
|
+ ball->max_speed = 2;
|
|
|
|
|
+ ball->speed = (Vector){0, 0};
|
|
|
|
|
+ ball->radius = 2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void ball_start(Entity* self, GameManager* manager, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ Ball* ball = context;
|
|
|
|
|
+ ball_reset(ball);
|
|
|
|
|
+ entity_collider_add_circle(self, ball->radius);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void ball_set_angle(Ball* ball, float angle) {
|
|
|
|
|
+ ball->speed.x = cosf(angle * (M_PI / 180.0f)) * ball->max_speed;
|
|
|
|
|
+ ball->speed.y = sinf(angle * (M_PI / 180.0f)) * ball->max_speed;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void ball_update(Entity* entity, GameManager* manager, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ Ball* ball = context;
|
|
|
|
|
+ Vector pos = entity_pos_get(entity);
|
|
|
|
|
+ pos = vector_add(pos, ball->speed);
|
|
|
|
|
+
|
|
|
|
|
+ const Vector screen = {128, 64};
|
|
|
|
|
+
|
|
|
|
|
+ // prevent to go out of screen
|
|
|
|
|
+ if(pos.x - ball->radius < 0) {
|
|
|
|
|
+ pos.x = ball->radius;
|
|
|
|
|
+ ball->speed.x = -ball->speed.x;
|
|
|
|
|
+ } else if(pos.x + ball->radius > screen.x) {
|
|
|
|
|
+ pos.x = screen.x - ball->radius;
|
|
|
|
|
+ ball->speed.x = -ball->speed.x;
|
|
|
|
|
+ } else if(pos.y - ball->radius < 0) {
|
|
|
|
|
+ pos.y = ball->radius;
|
|
|
|
|
+ ball->speed.y = -ball->speed.y;
|
|
|
|
|
+ } else if(pos.y - ball->radius > screen.y) {
|
|
|
|
|
+ Level* level = game_manager_current_level_get(manager);
|
|
|
|
|
+ level_send_event(level, entity, &paddle_desc, GameEventBallLost, (EntityEventValue){0});
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ entity_pos_set(entity, pos);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void ball_render(Entity* entity, GameManager* manager, Canvas* canvas, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ Ball* ball = context;
|
|
|
|
|
+ Vector pos = entity_pos_get(entity);
|
|
|
|
|
+ canvas_draw_disc(canvas, pos.x, pos.y, ball->radius);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static const EntityDescription ball_desc = {
|
|
|
|
|
+ .start = ball_start,
|
|
|
|
|
+ .stop = NULL,
|
|
|
|
|
+ .update = ball_update,
|
|
|
|
|
+ .render = ball_render,
|
|
|
|
|
+ .collision = NULL,
|
|
|
|
|
+ .event = NULL,
|
|
|
|
|
+ .context_size = sizeof(Ball),
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/****** Block ******/
|
|
|
|
|
+
|
|
|
|
|
+static const EntityDescription block_desc;
|
|
|
|
|
+
|
|
|
|
|
+typedef struct {
|
|
|
|
|
+ Vector size;
|
|
|
|
|
+} Block;
|
|
|
|
|
+
|
|
|
|
|
+static void block_spawn(Level* level, Vector pos, Vector size) {
|
|
|
|
|
+ Entity* block = level_add_entity(level, &block_desc);
|
|
|
|
|
+ entity_collider_add_rect(block, size.x, size.y);
|
|
|
|
|
+ entity_pos_set(block, pos);
|
|
|
|
|
+ Block* block_context = entity_context_get(block);
|
|
|
|
|
+ block_context->size = size;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void block_render(Entity* entity, GameManager* manager, Canvas* canvas, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ Block* block = context;
|
|
|
|
|
+ Vector pos = entity_pos_get(entity);
|
|
|
|
|
+ canvas_draw_box(
|
|
|
|
|
+ canvas, pos.x - block->size.x / 2, pos.y - block->size.y / 2, block->size.x, block->size.y);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void block_collision(Entity* self, Entity* other, GameManager* manager, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+
|
|
|
|
|
+ if(entity_description_get(other) == &ball_desc) {
|
|
|
|
|
+ Ball* ball = entity_context_get(other);
|
|
|
|
|
+ Block* block = context;
|
|
|
|
|
+ Vector ball_pos = entity_pos_get(other);
|
|
|
|
|
+ Vector block_pos = entity_pos_get(self);
|
|
|
|
|
+
|
|
|
|
|
+ Vector closest = {
|
|
|
|
|
+ CLAMP(ball_pos.x, block_pos.x + block->size.x / 2, block_pos.x - block->size.x / 2),
|
|
|
|
|
+ CLAMP(ball_pos.y, block_pos.y + block->size.y / 2, block_pos.y - block->size.y / 2),
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // change the ball speed based on the collision
|
|
|
|
|
+ Vector distance = vector_sub(ball_pos, closest);
|
|
|
|
|
+ if(fabsf(distance.x) < fabsf(distance.y)) {
|
|
|
|
|
+ ball->speed.y = -ball->speed.y;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ball->speed.x = -ball->speed.x;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Level* level = game_manager_current_level_get(manager);
|
|
|
|
|
+ level_remove_entity(level, self);
|
|
|
|
|
+
|
|
|
|
|
+ GameContext* game = game_manager_game_context_get(manager);
|
|
|
|
|
+ game_sound_play(game, &sequence_sound_ball_collide);
|
|
|
|
|
+
|
|
|
|
|
+ if(level_entity_count(level, &block_desc) == 0) {
|
|
|
|
|
+ LevelMessageContext* message_context = level_context_get(game->levels.message);
|
|
|
|
|
+ furi_string_set(message_context->message, "You win!");
|
|
|
|
|
+ game_manager_next_level_set(manager, game->levels.message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static const EntityDescription block_desc = {
|
|
|
|
|
+ .start = NULL,
|
|
|
|
|
+ .stop = NULL,
|
|
|
|
|
+ .update = NULL,
|
|
|
|
|
+ .render = block_render,
|
|
|
|
|
+ .collision = block_collision,
|
|
|
|
|
+ .event = NULL,
|
|
|
|
|
+ .context_size = sizeof(Block),
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/****** Paddle ******/
|
|
|
|
|
+
|
|
|
|
|
+static const Vector paddle_start_size = {30, 3};
|
|
|
|
|
+
|
|
|
|
|
+typedef struct {
|
|
|
|
|
+ Vector size;
|
|
|
|
|
+ bool ball_launched;
|
|
|
|
|
+ Entity* ball;
|
|
|
|
|
+} Paddle;
|
|
|
|
|
+
|
|
|
|
|
+static void paddle_start(Entity* self, GameManager* manager, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ Paddle* paddle = context;
|
|
|
|
|
+ paddle->size = paddle_start_size;
|
|
|
|
|
+ paddle->ball_launched = false;
|
|
|
|
|
+ entity_pos_set(self, (Vector){64, 61});
|
|
|
|
|
+ entity_collider_add_rect(self, paddle->size.x, paddle->size.y);
|
|
|
|
|
+
|
|
|
|
|
+ Level* level = game_manager_current_level_get(manager);
|
|
|
|
|
+ paddle->ball = level_add_entity(level, &ball_desc);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void paddle_stop(Entity* entity, GameManager* manager, void* context) {
|
|
|
|
|
+ UNUSED(entity);
|
|
|
|
|
+ Paddle* paddle = context;
|
|
|
|
|
+
|
|
|
|
|
+ Level* level = game_manager_current_level_get(manager);
|
|
|
|
|
+ level_remove_entity(level, paddle->ball);
|
|
|
|
|
+ paddle->ball = NULL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static float paddle_x_from_angle(float angle) {
|
|
|
|
|
+ const float min_angle = -45.0f;
|
|
|
|
|
+ const float max_angle = 45.0f;
|
|
|
|
|
+
|
|
|
|
|
+ return 128.0f * (angle - min_angle) / (max_angle - min_angle);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void paddle_update(Entity* entity, GameManager* manager, void* context) {
|
|
|
|
|
+ Paddle* paddle = context;
|
|
|
|
|
+ InputState input = game_manager_input_get(manager);
|
|
|
|
|
+ GameContext* game_context = game_manager_game_context_get(manager);
|
|
|
|
|
+
|
|
|
|
|
+ Vector pos = entity_pos_get(entity);
|
|
|
|
|
+ float paddle_half = paddle->size.x / 2;
|
|
|
|
|
+ if(game_context->imu_present) {
|
|
|
|
|
+ pos.x = paddle_x_from_angle(-imu_pitch_get(game_context->imu));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if(input.held & GameKeyLeft) {
|
|
|
|
|
+ pos.x -= 2;
|
|
|
|
|
+ }
|
|
|
|
|
+ if(input.held & GameKeyRight) {
|
|
|
|
|
+ pos.x += 2;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ pos.x = CLAMP(pos.x, 128 - paddle_half, paddle_half);
|
|
|
|
|
+ entity_pos_set(entity, pos);
|
|
|
|
|
+
|
|
|
|
|
+ if(input.pressed & GameKeyBack) {
|
|
|
|
|
+ game_manager_next_level_set(manager, game_context->levels.menu);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(input.pressed & GameKeyOk) {
|
|
|
|
|
+ if(!paddle->ball_launched) {
|
|
|
|
|
+ paddle->ball_launched = true;
|
|
|
|
|
+
|
|
|
|
|
+ Ball* ball = entity_context_get(paddle->ball);
|
|
|
|
|
+ ball_set_angle(ball, 270.0f);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(!paddle->ball_launched) {
|
|
|
|
|
+ Vector ball_pos = entity_pos_get(paddle->ball);
|
|
|
|
|
+ Ball* ball = entity_context_get(paddle->ball);
|
|
|
|
|
+ ball_pos.x = pos.x;
|
|
|
|
|
+ ball_pos.y = pos.y - paddle->size.y / 2 - ball->radius;
|
|
|
|
|
+ entity_pos_set(paddle->ball, ball_pos);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void paddle_render(Entity* entity, GameManager* manager, Canvas* canvas, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ Paddle* paddle = context;
|
|
|
|
|
+ Vector pos = entity_pos_get(entity);
|
|
|
|
|
+ float paddle_half = paddle->size.x / 2;
|
|
|
|
|
+ canvas_draw_box(canvas, pos.x - paddle_half, pos.y, paddle->size.x, paddle->size.y);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void paddle_collision(Entity* self, Entity* other, GameManager* manager, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+
|
|
|
|
|
+ if(entity_description_get(other) == &ball_desc) {
|
|
|
|
|
+ Ball* ball = entity_context_get(other);
|
|
|
|
|
+ Paddle* paddle = context;
|
|
|
|
|
+ Vector ball_pos = entity_pos_get(other);
|
|
|
|
|
+ Vector paddle_pos = entity_pos_get(self);
|
|
|
|
|
+
|
|
|
|
|
+ float paddle_half = paddle->size.x / 2;
|
|
|
|
|
+ float paddle_center = paddle_pos.x;
|
|
|
|
|
+ float paddle_edge = paddle_center - paddle_half;
|
|
|
|
|
+ float paddle_edge_distance = ball_pos.x - paddle_edge;
|
|
|
|
|
+ float paddle_edge_distance_normalized = paddle_edge_distance / paddle->size.x;
|
|
|
|
|
+
|
|
|
|
|
+ // lerp the angle based on the distance from the paddle center
|
|
|
|
|
+ float angle = 270.0f - 45.0f + 90.0f * paddle_edge_distance_normalized;
|
|
|
|
|
+ ball_set_angle(ball, angle);
|
|
|
|
|
+
|
|
|
|
|
+ GameContext* game = game_manager_game_context_get(manager);
|
|
|
|
|
+ game_sound_play(game, &sequence_sound_ball_paddle_collide);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void paddle_event(Entity* self, GameManager* manager, EntityEvent event, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ UNUSED(self);
|
|
|
|
|
+ if(event.type == GameEventBallLost) {
|
|
|
|
|
+ Paddle* paddle = context;
|
|
|
|
|
+ paddle->ball_launched = false;
|
|
|
|
|
+ Ball* ball = entity_context_get(paddle->ball);
|
|
|
|
|
+ ball_reset(ball);
|
|
|
|
|
+ GameContext* game = game_manager_game_context_get(manager);
|
|
|
|
|
+ game_sound_play(game, &sequence_sound_ball_lost);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static const EntityDescription paddle_desc = {
|
|
|
|
|
+ .start = paddle_start,
|
|
|
|
|
+ .stop = paddle_stop,
|
|
|
|
|
+ .update = paddle_update,
|
|
|
|
|
+ .render = paddle_render,
|
|
|
|
|
+ .collision = paddle_collision,
|
|
|
|
|
+ .event = paddle_event,
|
|
|
|
|
+ .context_size = sizeof(Paddle),
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static void level_1_spawn(Level* level) {
|
|
|
|
|
+ level_add_entity(level, &paddle_desc);
|
|
|
|
|
+ const Vector block_size = {13, 5};
|
|
|
|
|
+ const Vector screen = {128, 64};
|
|
|
|
|
+ const int block_count_x = screen.x / block_size.x;
|
|
|
|
|
+ const int block_count_y = 6;
|
|
|
|
|
+ size_t block_spacing = 1;
|
|
|
|
|
+
|
|
|
|
|
+ for(int y = 0; y < block_count_y; y++) {
|
|
|
|
|
+ for(int x = 0; x < block_count_x; x++) {
|
|
|
|
|
+ Vector pos = {
|
|
|
|
|
+ (x) * (block_size.x + block_spacing) + block_size.x / 2,
|
|
|
|
|
+ (y) * (block_size.y + block_spacing) + block_size.y / 2,
|
|
|
|
|
+ };
|
|
|
|
|
+ block_spawn(level, pos, block_size);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void level_game_start(Level* level, GameManager* manager, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ UNUSED(context);
|
|
|
|
|
+ level_1_spawn(level);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void level_game_stop(Level* level, GameManager* manager, void* context) {
|
|
|
|
|
+ UNUSED(manager);
|
|
|
|
|
+ UNUSED(context);
|
|
|
|
|
+ level_clear(level);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const LevelBehaviour level_game = {
|
|
|
|
|
+ .alloc = NULL,
|
|
|
|
|
+ .free = NULL,
|
|
|
|
|
+ .start = level_game_start,
|
|
|
|
|
+ .stop = level_game_stop,
|
|
|
|
|
+ .context_size = 0,
|
|
|
|
|
+};
|