|
@@ -8,12 +8,21 @@
|
|
|
#include <yapinvaders_icons.h>
|
|
#include <yapinvaders_icons.h>
|
|
|
|
|
|
|
|
#define PROJECTILES_MAX 10
|
|
#define PROJECTILES_MAX 10
|
|
|
-#define ENEMIES_MAX 10
|
|
|
|
|
|
|
+#define ENEMIES_MAX 12
|
|
|
|
|
+#define BARRIERS_MAX 5
|
|
|
|
|
+#define BARRIER_WIDTH 10
|
|
|
|
|
+#define BARRIER_HEIGHT 3
|
|
|
|
|
+#define PROJECTILE_WIDTH 4
|
|
|
|
|
+#define PROJECTILE_HEIGHT 8
|
|
|
|
|
+#define ENEMY_PROJECTILE_SPEED .5
|
|
|
|
|
+#define ENEMY_PROJECTILES_MAX 10
|
|
|
|
|
|
|
|
int enemy_movement_direction = 1;
|
|
int enemy_movement_direction = 1;
|
|
|
int move_down_step = 0;
|
|
int move_down_step = 0;
|
|
|
int enemy_move_counter = 0;
|
|
int enemy_move_counter = 0;
|
|
|
int enemy_move_threshold = 20;
|
|
int enemy_move_threshold = 20;
|
|
|
|
|
+bool leftKeyPressed = false;
|
|
|
|
|
+bool rightKeyPressed = false;
|
|
|
|
|
|
|
|
typedef struct { int x, y; } Point;
|
|
typedef struct { int x, y; } Point;
|
|
|
|
|
|
|
@@ -31,6 +40,11 @@ typedef struct {
|
|
|
bool active;
|
|
bool active;
|
|
|
} Enemy;
|
|
} Enemy;
|
|
|
|
|
|
|
|
|
|
+typedef struct {
|
|
|
|
|
+ Point position;
|
|
|
|
|
+ bool active;
|
|
|
|
|
+} Barrier;
|
|
|
|
|
+
|
|
|
typedef struct {
|
|
typedef struct {
|
|
|
Player player;
|
|
Player player;
|
|
|
Projectile projectiles[PROJECTILES_MAX];
|
|
Projectile projectiles[PROJECTILES_MAX];
|
|
@@ -41,18 +55,36 @@ typedef struct {
|
|
|
bool waiting_for_restart;
|
|
bool waiting_for_restart;
|
|
|
bool running;
|
|
bool running;
|
|
|
int score;
|
|
int score;
|
|
|
|
|
+ Barrier barriers[BARRIERS_MAX];
|
|
|
|
|
+ Projectile enemy_projectiles[ENEMY_PROJECTILES_MAX];
|
|
|
|
|
+ int enemy_projectiles_count;
|
|
|
} GameState;
|
|
} GameState;
|
|
|
|
|
|
|
|
GameState game_state;
|
|
GameState game_state;
|
|
|
|
|
+void deactivate_all_enemies(GameState* state);
|
|
|
|
|
|
|
|
void game_state_init(GameState* state) {
|
|
void game_state_init(GameState* state) {
|
|
|
- state->player.position = (Point){64, 54};
|
|
|
|
|
|
|
+ state->player.position = (Point){64, 55};
|
|
|
state->projectiles_count = 0;
|
|
state->projectiles_count = 0;
|
|
|
for(int i = 0; i < PROJECTILES_MAX; ++i) {
|
|
for(int i = 0; i < PROJECTILES_MAX; ++i) {
|
|
|
state->projectiles[i].active = false;
|
|
state->projectiles[i].active = false;
|
|
|
}
|
|
}
|
|
|
- state->running = true;
|
|
|
|
|
- state->game_over = false;
|
|
|
|
|
|
|
+ for(int i = 0; i < BARRIERS_MAX; ++i) {
|
|
|
|
|
+ state->barriers[i].position.x = 7 + i * (128 / BARRIERS_MAX);
|
|
|
|
|
+ state->barriers[i].position.y = 50;
|
|
|
|
|
+ state->barriers[i].active = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ for(int p = 0; p < ENEMY_PROJECTILES_MAX; ++p) {
|
|
|
|
|
+ if(state->enemy_projectiles[p].active) {
|
|
|
|
|
+ state->enemy_projectiles[p].position.y += ENEMY_PROJECTILE_SPEED;
|
|
|
|
|
+ if(state->enemy_projectiles[p].position.y > 128) {
|
|
|
|
|
+ state->enemy_projectiles[p].active = false;
|
|
|
|
|
+ state->enemy_projectiles_count--;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ state->running = true;
|
|
|
|
|
+ state->game_over = false;
|
|
|
state->current_wave = 0;
|
|
state->current_wave = 0;
|
|
|
state->waiting_for_restart = false;
|
|
state->waiting_for_restart = false;
|
|
|
state->score = 0;
|
|
state->score = 0;
|
|
@@ -91,9 +123,8 @@ void handle_input(GameState* state, InputEvent* input_event) {
|
|
|
void initialize_enemies(GameState* state) {
|
|
void initialize_enemies(GameState* state) {
|
|
|
int rows = 2;
|
|
int rows = 2;
|
|
|
int cols = ENEMIES_MAX / rows;
|
|
int cols = ENEMIES_MAX / rows;
|
|
|
-
|
|
|
|
|
- int horizontalSpacing = 128 / cols;
|
|
|
|
|
- int verticalSpacing = 10;
|
|
|
|
|
|
|
+ int horizontalSpacing = 128 / cols;
|
|
|
|
|
+ int verticalSpacing = 10;
|
|
|
|
|
|
|
|
for(int i = 0; i < ENEMIES_MAX; ++i) {
|
|
for(int i = 0; i < ENEMIES_MAX; ++i) {
|
|
|
state->enemies[i].position.x = (i % cols) * horizontalSpacing + (horizontalSpacing / 2);
|
|
state->enemies[i].position.x = (i % cols) * horizontalSpacing + (horizontalSpacing / 2);
|
|
@@ -115,7 +146,7 @@ void update_enemy_positions(GameState* state) {
|
|
|
if ((state->enemies[i].position.x <= 0 && enemy_movement_direction < 0) ||
|
|
if ((state->enemies[i].position.x <= 0 && enemy_movement_direction < 0) ||
|
|
|
(state->enemies[i].position.x >= 124 && enemy_movement_direction > 0)) {
|
|
(state->enemies[i].position.x >= 124 && enemy_movement_direction > 0)) {
|
|
|
changeDirection = true;
|
|
changeDirection = true;
|
|
|
- newDirection *= -1;
|
|
|
|
|
|
|
+ newDirection *= -1;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -143,13 +174,35 @@ void update_enemy_positions(GameState* state) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void enemy_shoot(GameState* state) {
|
|
|
|
|
+ int activeEnemies[ENEMIES_MAX];
|
|
|
|
|
+ int activeCount = 0;
|
|
|
|
|
+ for(int e = 0; e < ENEMIES_MAX; ++e) {
|
|
|
|
|
+ if(state->enemies[e].active) {
|
|
|
|
|
+ activeEnemies[activeCount++] = e;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if(activeCount > 0) {
|
|
|
|
|
+ int chosenOne = activeEnemies[rand() % activeCount];
|
|
|
|
|
+ for(int p = 0; p < ENEMY_PROJECTILES_MAX; ++p) {
|
|
|
|
|
+ if(!state->enemy_projectiles[p].active) {
|
|
|
|
|
+ state->enemy_projectiles[p].position = (Point){state->enemies[chosenOne].position.x + 5, state->enemies[chosenOne].position.y + 10}; // Assuming enemy size for adjustment
|
|
|
|
|
+ state->enemy_projectiles[p].active = true;
|
|
|
|
|
+ state->enemy_projectiles_count++;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
void handle_collisions(GameState* state) {
|
|
void handle_collisions(GameState* state) {
|
|
|
|
|
+ int playerHitboxMargin = 2;
|
|
|
for(int p = 0; p < PROJECTILES_MAX; ++p) {
|
|
for(int p = 0; p < PROJECTILES_MAX; ++p) {
|
|
|
if(state->projectiles[p].active) {
|
|
if(state->projectiles[p].active) {
|
|
|
for(int e = 0; e < ENEMIES_MAX; ++e) {
|
|
for(int e = 0; e < ENEMIES_MAX; ++e) {
|
|
|
if(state->enemies[e].active) {
|
|
if(state->enemies[e].active) {
|
|
|
- if(abs(state->projectiles[p].position.x - state->enemies[e].position.x) < 5 &&
|
|
|
|
|
- abs(state->projectiles[p].position.y - state->enemies[e].position.y) < 5) {
|
|
|
|
|
|
|
+ if(abs(state->projectiles[p].position.x - state->enemies[e].position.x) < 10 / 2 + 1 &&
|
|
|
|
|
+ abs(state->projectiles[p].position.y - state->enemies[e].position.y) < 10 / 2 + 1) {
|
|
|
state->projectiles[p].active = false;
|
|
state->projectiles[p].active = false;
|
|
|
state->enemies[e].active = false;
|
|
state->enemies[e].active = false;
|
|
|
state->projectiles_count--;
|
|
state->projectiles_count--;
|
|
@@ -160,7 +213,61 @@ void handle_collisions(GameState* state) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
+ for(int p = 0; p < PROJECTILES_MAX; ++p) {
|
|
|
|
|
+ if(state->projectiles[p].active) {
|
|
|
|
|
+ for(int b = 0; b < BARRIERS_MAX; ++b) {
|
|
|
|
|
+ if(state->barriers[b].active &&
|
|
|
|
|
+ abs(state->projectiles[p].position.x - state->barriers[b].position.x) < 3 &&
|
|
|
|
|
+ abs(state->projectiles[p].position.y - state->barriers[b].position.y) < 3) {
|
|
|
|
|
+ state->projectiles[p].active = false;
|
|
|
|
|
+ state->barriers[b].active = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for(int e = 0; e < ENEMIES_MAX; ++e) {
|
|
|
|
|
+ if(state->enemies[e].active) {
|
|
|
|
|
+ for(int b = 0; b < BARRIERS_MAX; ++b) {
|
|
|
|
|
+ if(state->barriers[b].active &&
|
|
|
|
|
+ abs(state->enemies[e].position.x - state->barriers[b].position.x) < 10 &&
|
|
|
|
|
+ abs(state->enemies[e].position.y - state->barriers[b].position.y) < 10) {
|
|
|
|
|
+ state->enemies[e].active = false;
|
|
|
|
|
+ state->barriers[b].active = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for(int p = 0; p < ENEMY_PROJECTILES_MAX; ++p) {
|
|
|
|
|
+ if(state->enemy_projectiles[p].active) {
|
|
|
|
|
+ int projectileCenterX = state->enemy_projectiles[p].position.x + 2;
|
|
|
|
|
+ int projectileCenterY = state->enemy_projectiles[p].position.y + 4;
|
|
|
|
|
+ int playerCenterX = state->player.position.x + 5;
|
|
|
|
|
+ int playerCenterY = state->player.position.y + 5;
|
|
|
|
|
+
|
|
|
|
|
+ if(abs(projectileCenterX - playerCenterX) < (5 - playerHitboxMargin) &&
|
|
|
|
|
+ abs(projectileCenterY - playerCenterY) < (5 - playerHitboxMargin)) {
|
|
|
|
|
+ deactivate_all_enemies(&game_state);
|
|
|
|
|
+ state->game_over = true;
|
|
|
|
|
+ state->enemy_projectiles[p].active = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for(int p = 0; p < ENEMY_PROJECTILES_MAX; ++p) {
|
|
|
|
|
+ if(state->enemy_projectiles[p].active) {
|
|
|
|
|
+ for(int b = 0; b < BARRIERS_MAX; ++b) {
|
|
|
|
|
+ if(state->barriers[b].active &&
|
|
|
|
|
+ abs(state->enemy_projectiles[p].position.x - state->barriers[b].position.x) < (BARRIER_WIDTH / 2 + PROJECTILE_WIDTH / 2) &&
|
|
|
|
|
+ (state->enemy_projectiles[p].position.y + PROJECTILE_HEIGHT >= state->barriers[b].position.y) &&
|
|
|
|
|
+ (state->enemy_projectiles[p].position.y <= state->barriers[b].position.y + BARRIER_HEIGHT)) {
|
|
|
|
|
+ state->enemy_projectiles[p].active = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
bool all_enemies_destroyed(GameState* state) {
|
|
bool all_enemies_destroyed(GameState* state) {
|
|
|
for(int i = 0; i < ENEMIES_MAX; ++i) {
|
|
for(int i = 0; i < ENEMIES_MAX; ++i) {
|
|
@@ -183,7 +290,14 @@ void render_game_over_screen(Canvas* canvas, GameState* state) {
|
|
|
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "Press OK to restart");
|
|
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "Press OK to restart");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void deactivate_all_enemies(GameState* state) {
|
|
|
|
|
+ for(int i = 0; i < ENEMIES_MAX; ++i) {
|
|
|
|
|
+ state->enemies[i].active = false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
void update_game_state(GameState* state) {
|
|
void update_game_state(GameState* state) {
|
|
|
|
|
+ static int shoot_counter = 0;
|
|
|
for(int i = 0; i < PROJECTILES_MAX; ++i) {
|
|
for(int i = 0; i < PROJECTILES_MAX; ++i) {
|
|
|
if(state->projectiles[i].active) {
|
|
if(state->projectiles[i].active) {
|
|
|
state->projectiles[i].position.y -= 2;
|
|
state->projectiles[i].position.y -= 2;
|
|
@@ -194,10 +308,29 @@ void update_game_state(GameState* state) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if(++shoot_counter >= 30) {
|
|
|
|
|
+ enemy_shoot(state);
|
|
|
|
|
+ shoot_counter = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
for(int i = 0; i < ENEMIES_MAX; ++i) {
|
|
for(int i = 0; i < ENEMIES_MAX; ++i) {
|
|
|
- if(state->enemies[i].active && state->enemies[i].position.y >= state->player.position.y) {
|
|
|
|
|
- state->game_over = true;
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ if(state->enemies[i].active) {
|
|
|
|
|
+ if(abs(state->enemies[i].position.x - state->player.position.x) < 10 &&
|
|
|
|
|
+ abs(state->enemies[i].position.y - state->player.position.y) < 10) {
|
|
|
|
|
+ deactivate_all_enemies(state);
|
|
|
|
|
+ state->game_over = true;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for(int p = 0; p < ENEMY_PROJECTILES_MAX; ++p) {
|
|
|
|
|
+ if(state->enemy_projectiles[p].active) {
|
|
|
|
|
+ state->enemy_projectiles[p].position.y += 2;
|
|
|
|
|
+ if(state->enemy_projectiles[p].position.y > 128) {
|
|
|
|
|
+ state->enemy_projectiles[p].active = false;
|
|
|
|
|
+ state->enemy_projectiles_count--;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -205,15 +338,15 @@ void update_game_state(GameState* state) {
|
|
|
initialize_enemies(state);
|
|
initialize_enemies(state);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- update_enemy_positions(state);
|
|
|
|
|
- handle_collisions(state);
|
|
|
|
|
|
|
+ update_enemy_positions(state);
|
|
|
|
|
+ handle_collisions(state);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void render_callback(Canvas* const canvas, void* ctx) {
|
|
void render_callback(Canvas* const canvas, void* ctx) {
|
|
|
GameState* state = (GameState*)ctx;
|
|
GameState* state = (GameState*)ctx;
|
|
|
if (!canvas || !state) return;
|
|
if (!canvas || !state) return;
|
|
|
if(state->game_over) {
|
|
if(state->game_over) {
|
|
|
- render_game_over_screen(canvas, state);
|
|
|
|
|
|
|
+ render_game_over_screen(canvas, state);
|
|
|
} else {
|
|
} else {
|
|
|
canvas_clear(canvas);
|
|
canvas_clear(canvas);
|
|
|
char score_text[30];
|
|
char score_text[30];
|
|
@@ -232,7 +365,17 @@ void render_callback(Canvas* const canvas, void* ctx) {
|
|
|
canvas_draw_icon(canvas, state->enemies[i].position.x, state->enemies[i].position.y, &I_yap);
|
|
canvas_draw_icon(canvas, state->enemies[i].position.x, state->enemies[i].position.y, &I_yap);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
+ for(int i = 0; i < BARRIERS_MAX; ++i) {
|
|
|
|
|
+ if(state->barriers[i].active) {
|
|
|
|
|
+ canvas_draw_rbox(canvas, state->barriers[i].position.x, state->barriers[i].position.y, 10, 3, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for(int p = 0; p < ENEMY_PROJECTILES_MAX; ++p) {
|
|
|
|
|
+ if(state->enemy_projectiles[p].active) {
|
|
|
|
|
+ canvas_draw_rbox(canvas, state->enemy_projectiles[p].position.x - 1, state->enemy_projectiles[p].position.y - 4, 4, 8, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
static void input_callback(InputEvent* input_event, void* ctx) {
|
|
static void input_callback(InputEvent* input_event, void* ctx) {
|
|
|
GameState* state = (GameState*)ctx;
|
|
GameState* state = (GameState*)ctx;
|
|
@@ -247,11 +390,45 @@ static void input_callback(InputEvent* input_event, void* ctx) {
|
|
|
if (input_event->key == InputKeyOk && input_event->type == InputTypeShort) {
|
|
if (input_event->key == InputKeyOk && input_event->type == InputTypeShort) {
|
|
|
game_state_init(state);
|
|
game_state_init(state);
|
|
|
initialize_enemies(state);
|
|
initialize_enemies(state);
|
|
|
- state->game_over = false;
|
|
|
|
|
|
|
+ state->game_over = false;
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
handle_input(state, input_event);
|
|
handle_input(state, input_event);
|
|
|
}
|
|
}
|
|
|
|
|
+ if (input_event->type == InputTypePress) {
|
|
|
|
|
+ switch(input_event->key) {
|
|
|
|
|
+ case InputKeyLeft:
|
|
|
|
|
+ leftKeyPressed = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyRight:
|
|
|
|
|
+ rightKeyPressed = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (input_event->type == InputTypeRelease) {
|
|
|
|
|
+ switch(input_event->key) {
|
|
|
|
|
+ case InputKeyLeft:
|
|
|
|
|
+ leftKeyPressed = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case InputKeyRight:
|
|
|
|
|
+ rightKeyPressed = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void update_player_position(GameState* state) {
|
|
|
|
|
+ if(leftKeyPressed) {
|
|
|
|
|
+ state->player.position.x = (state->player.position.x - 1) > 0 ? state->player.position.x - 1 : 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ if(rightKeyPressed) {
|
|
|
|
|
+ state->player.position.x = (state->player.position.x + 1) < 124 ? state->player.position.x + 1 : 124;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int32_t yapinvaders_app(void) {
|
|
int32_t yapinvaders_app(void) {
|
|
@@ -272,6 +449,7 @@ int32_t yapinvaders_app(void) {
|
|
|
game_state.waiting_for_restart = false;
|
|
game_state.waiting_for_restart = false;
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ update_player_position(&game_state);
|
|
|
update_game_state(&game_state);
|
|
update_game_state(&game_state);
|
|
|
view_port_update(view_port);
|
|
view_port_update(view_port);
|
|
|
}
|
|
}
|