summaryrefslogtreecommitdiff
path: root/src/ei.c
diff options
context:
space:
mode:
authorRichard Braun <rbraun@sceen.net>2024-03-30 21:58:46 +0100
committerRichard Braun <rbraun@sceen.net>2024-03-30 21:58:46 +0100
commit7fbe749f9aee6c8c0ac74a409fdfd152f554e6a6 (patch)
treeedb434a7cbd8d96ff9df3591b5dd4f7c42482fa5 /src/ei.c
Initial commit
Diffstat (limited to 'src/ei.c')
-rw-r--r--src/ei.c1184
1 files changed, 1184 insertions, 0 deletions
diff --git a/src/ei.c b/src/ei.c
new file mode 100644
index 0000000..da2331e
--- /dev/null
+++ b/src/ei.c
@@ -0,0 +1,1184 @@
+/*
+ * Copyright (c) 2024 Richard Braun.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+ * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ *
+ * Embedded invaders.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "eetg.h"
+#include "ei.h"
+#include "macros.h"
+
+#define EI_NR_LIVES 3
+
+#define EI_ALIEN_STARTING_ROW 3
+
+#define EI_PLAYER_MISSILE_SPEED 25
+#define EI_ALIENS_SPEED 10
+#define EI_ALIEN_MISSILE_SPEED 10
+#define EI_FIRST_ALIEN_MISSILE_DELAY 2
+#define EI_UFO_SPEED 10
+
+#define EI_SCORE_MISSILE 40
+#define EI_SCORE_ALIENS0 30
+#define EI_SCORE_ALIENS12 20
+#define EI_SCORE_ALIENS34 10
+#define EI_SCORE_UFO_BASE 100
+
+#define EI_TITLE_SPRITE \
+" _____ _____ \n" \
+"( ___ )-------------------------------------------------( ___ )\n" \
+" | | | | \n" \
+" | | ____ __ __ __ __ | | \n" \
+" | | / __/_ _ / / ___ ___/ /__/ /__ ___/ / | | \n" \
+" | | / _// ' \\/ _ \\/ -_) _ / _ / -_) _ / | | \n" \
+" | | /___/_/_/_/_.__/\\__/\\_,_/\\_,_/\\__/\\_,_/ | | \n" \
+" | | _ __ | | \n" \
+" | | (_)__ _ _____ ____/ /__ _______ | | \n" \
+" | | / / _ \\ |/ / _ `/ _ / -_) __(_-< | | \n" \
+" | | /_/_//_/___/\\_,_/\\_,_/\\__/_/ /___/ | | \n" \
+" | | | | \n" \
+" |___| |___| \n" \
+"(_____)-------------------------------------------------(_____)\n"
+
+#define EI_HELP_SPRITE \
+" s = left \n" \
+" f = right \n" \
+" space = shoot \n"
+
+#define EI_START_SPRITE \
+"Press SPACE to start\n" \
+"Press X to leave \n"
+
+#define EI_PLAYER_SPRITE "/-^-\\\n"
+
+#define EI_BUNKER_SPRITE \
+" ### \n" \
+" ##### \n" \
+"#######\n" \
+"## ##\n"
+
+#define EI_ALIENS0_SPRITE_1 ",^,\n"
+#define EI_ALIENS0_SPRITE_2 ".-.\n"
+#define EI_ALIENS12_SPRITE_1 "-O_\n"
+#define EI_ALIENS12_SPRITE_2 "_O-\n"
+#define EI_ALIENS34_SPRITE_1 "/^\\\n"
+#define EI_ALIENS34_SPRITE_2 "-^-\n"
+
+#define EI_STATUS_SPRITE_FORMAT "SCORE: %08d Lives: %d\n"
+
+#define EI_END_TITLE_SPRITE \
+" ________ __ _______ ____ _ _________ \n" \
+" / ___/ _ | / |/ / __/ / __ \\ | / / __/ _ \\\n" \
+"/ (_ / __ |/ /|_/ / _/ / /_/ / |/ / _// , _/\n" \
+"\\___/_/ |_/_/ /_/___/ \\____/|___/___/_/|_| \n"
+
+#define EI_TYPE_TITLE 0
+#define EI_TYPE_HELP 1
+#define EI_TYPE_START 2
+#define EI_TYPE_PLAYER 3
+#define EI_TYPE_PLAYER_MISSILE 4
+#define EI_TYPE_BUNKER 5
+#define EI_TYPE_ALIEN 6
+#define EI_TYPE_ALIEN_MISSILE 7
+#define EI_TYPE_UFO 8
+#define EI_TYPE_STATUS 9
+#define EI_TYPE_END_TITLE 10
+
+static const char *
+ei_get_group_sprite1(size_t group)
+{
+ const char *sprite;
+
+ if (group == 0) {
+ sprite = EI_ALIENS0_SPRITE_1;
+ } else if ((group == 1) || (group == 2)) {
+ sprite = EI_ALIENS12_SPRITE_1;
+ } else {
+ sprite = EI_ALIENS34_SPRITE_1;
+ }
+
+ return sprite;
+}
+
+static const char *
+ei_get_group_sprite2(size_t group)
+{
+ const char *sprite;
+
+ if (group == 0) {
+ sprite = EI_ALIENS0_SPRITE_2;
+ } else if ((group == 1) || (group == 2)) {
+ sprite = EI_ALIENS12_SPRITE_2;
+ } else {
+ sprite = EI_ALIENS34_SPRITE_2;
+ }
+
+ return sprite;
+}
+
+static int
+ei_get_group_color(size_t group)
+{
+ int color;
+
+ if (group == 0) {
+ color = EETG_COLOR_RED;
+ } else if ((group == 1) || (group == 2)) {
+ color = EETG_COLOR_GREEN;
+ } else {
+ color = EETG_COLOR_BLUE;
+ }
+
+ return color;
+}
+
+static struct eetg_object *
+ei_get_object(struct eetg_object *object1,
+ struct eetg_object *object2,
+ int type)
+{
+ struct eetg_object *object = NULL;
+
+ assert(object1);
+ assert(object2);
+
+ if (eetg_object_get_type(object1) == type) {
+ object = object1;
+ } else if (eetg_object_get_type(object2) == type) {
+ object = object2;
+ }
+
+ return object;
+}
+
+static bool
+ei_has_type(const struct eetg_object *object1,
+ const struct eetg_object *object2,
+ int type)
+{
+ return (eetg_object_get_type(object1) == type)
+ || (eetg_object_get_type(object2) == type);
+}
+
+static void
+ei_bunker_reset_sprite(struct ei_bunker *bunker)
+{
+ assert(bunker);
+
+ snprintf(bunker->sprite, sizeof(bunker->sprite), "%s", EI_BUNKER_SPRITE);
+}
+
+static void
+ei_bunker_init(struct ei_bunker *bunker)
+{
+ assert(bunker);
+
+ ei_bunker_reset_sprite(bunker);
+
+ eetg_object_init(&bunker->object, EI_TYPE_BUNKER, bunker->sprite);
+ eetg_object_set_color(&bunker->object, EETG_COLOR_CYAN);
+}
+
+static struct ei_bunker *
+ei_bunker_get(struct eetg_object *object)
+{
+ assert(eetg_object_get_type(object) == EI_TYPE_BUNKER);
+
+ return structof(object, struct ei_bunker, object);
+}
+
+static struct eetg_object *
+ei_bunker_get_object(struct ei_bunker *bunker)
+{
+ assert(bunker);
+
+ return &bunker->object;
+}
+
+static bool
+ei_bunker_damage(struct ei_bunker *bunker, int x, int y)
+{
+ char *sprite;
+ int index;
+
+ assert(bunker);
+
+ index = eetg_object_get_cell(&bunker->object, x, y);
+
+ sprite = (char *)eetg_object_get_sprite(&bunker->object);
+
+ sprite[index] = ' ';
+
+ return eetg_object_is_empty(&bunker->object);
+}
+
+static void
+ei_alien_init(struct ei_alien *alien, const char *sprite, int color)
+{
+ assert(alien);
+
+ eetg_object_init(&alien->object, EI_TYPE_ALIEN, sprite);
+ eetg_object_set_color(&alien->object, color);
+}
+
+static struct ei_alien *
+ei_alien_get(struct eetg_object *object)
+{
+ assert(eetg_object_get_type(object) == EI_TYPE_ALIEN);
+
+ return structof(object, struct ei_alien, object);
+}
+
+static struct eetg_object *
+ei_alien_get_object(struct ei_alien *alien)
+{
+ assert(alien);
+
+ return &alien->object;
+}
+
+static bool
+ei_alien_is_dead(const struct ei_alien *alien)
+{
+ assert(alien);
+
+ return (eetg_object_get_world(&alien->object) == NULL);
+}
+
+static int
+ei_alien_get_x(const struct ei_alien *alien)
+{
+ assert(alien);
+
+ return eetg_object_get_x(&alien->object);
+}
+
+static int
+ei_alien_get_width(const struct ei_alien *alien)
+{
+ assert(alien);
+
+ return eetg_object_get_width(&alien->object);
+}
+
+static bool
+ei_alien_move_down(struct ei_alien *alien)
+{
+ struct eetg_object *object;
+ bool game_over = false;
+ int y;
+
+ assert(alien);
+
+ object = &alien->object;
+
+ y = eetg_object_get_y(object) + 1;
+
+ eetg_object_move(object, eetg_object_get_x(object), y);
+
+ if (y >= (EETG_ROWS - 1)) {
+ game_over = true;
+ }
+
+ return game_over;
+}
+
+static void
+ei_alien_move_left(struct ei_alien *alien)
+{
+ struct eetg_object *object;
+
+ assert(alien);
+
+ object = &alien->object;
+
+ eetg_object_move(object, eetg_object_get_x(object) - 1,
+ eetg_object_get_y(object));
+}
+
+static void
+ei_alien_move_right(struct ei_alien *alien)
+{
+ struct eetg_object *object;
+
+ assert(alien);
+
+ object = &alien->object;
+
+ eetg_object_move(object, eetg_object_get_x(object) + 1,
+ eetg_object_get_y(object));
+}
+
+static void
+ei_alien_group_init(struct ei_alien_group *group, const char *sprite1,
+ const char *sprite2, int color)
+{
+ assert(group);
+ assert(sprite1);
+ assert(strlen(sprite1) == (EI_ALIEN_WIDTH + 1));
+ assert(sprite2);
+ assert(strlen(sprite2) == (EI_ALIEN_WIDTH + 1));
+
+ group->sprites[0] = sprite1;
+ group->sprites[1] = sprite2;
+ group->sprite_index = 0;
+
+ memcpy(group->sprite, group->sprites[group->sprite_index],
+ sizeof(group->sprite));
+
+ for (size_t i = 0; i < ARRAY_SIZE(group->aliens); i++) {
+ ei_alien_init(&group->aliens[i], group->sprite, color);
+ }
+}
+
+static void
+ei_alien_group_attach(struct ei_alien_group *group,
+ struct eetg_world *world, int y)
+{
+ int x = 0;
+
+ assert(group);
+
+ for (size_t i = 0; i < ARRAY_SIZE(group->aliens); i++) {
+ struct ei_alien *alien = &group->aliens[i];
+
+ eetg_world_add(world, ei_alien_get_object(alien), x, y);
+
+ x += EI_ALIEN_WIDTH;
+ }
+}
+
+static bool
+ei_alien_group_has_alien(const struct ei_alien_group *group,
+ const struct ei_alien *alien)
+{
+ bool alien_present = false;
+ const struct ei_alien *end;
+
+ assert(group);
+
+ end = &group->aliens[ARRAY_SIZE(group->aliens)];
+
+ if ((alien >= group->aliens) && (alien < end)) {
+ alien_present = true;
+ }
+
+ return alien_present;
+}
+
+static struct ei_alien *
+ei_alien_group_get(struct ei_alien_group *group, size_t index)
+{
+ assert(group);
+ assert(index < ARRAY_SIZE(group->aliens));
+
+ return &group->aliens[index];
+}
+
+static void
+ei_alien_group_twerk(struct ei_alien_group *group)
+{
+ assert(group);
+
+ group->sprite_index = (group->sprite_index + 1) & 1;
+ memcpy(group->sprite, group->sprites[group->sprite_index],
+ sizeof(group->sprite));
+}
+
+static bool
+ei_alien_group_move_down(struct ei_alien_group *group)
+{
+ bool game_over = false;
+
+ ei_alien_group_twerk(group);
+
+ for (size_t i = 0; i < ARRAY_SIZE(group->aliens); i++) {
+ struct ei_alien *alien = &group->aliens[i];
+
+ if (!ei_alien_is_dead(alien)) {
+ bool tmp;
+
+ tmp = ei_alien_move_down(alien);
+
+ if (tmp) {
+ game_over = true;
+ }
+ }
+ }
+
+ return game_over;
+}
+
+static bool
+ei_alien_group_move_left(struct ei_alien_group *group)
+{
+ bool border_reached = false;
+
+ ei_alien_group_twerk(group);
+
+ for (size_t i = 0; i < ARRAY_SIZE(group->aliens); i++) {
+ struct ei_alien *alien = &group->aliens[i];
+
+ if (!ei_alien_is_dead(alien)) {
+ ei_alien_move_left(alien);
+
+ if (!border_reached && (ei_alien_get_x(alien) == 0)) {
+ border_reached = true;
+ }
+ }
+ }
+
+ return border_reached;
+}
+
+static bool
+ei_alien_group_move_right(struct ei_alien_group *group)
+{
+ bool border_reached = false;
+
+ ei_alien_group_twerk(group);
+
+ for (size_t i = ARRAY_SIZE(group->aliens) - 1;
+ i < ARRAY_SIZE(group->aliens); i--) {
+ struct ei_alien *alien = &group->aliens[i];
+
+ if (!ei_alien_is_dead(alien)) {
+ ei_alien_move_right(alien);
+
+ if (!border_reached) {
+ int x;
+
+ x = ei_alien_get_x(alien) + ei_alien_get_width(alien) - 1;
+
+ if (x == (EETG_COLUMNS - 1)) {
+ border_reached = true;
+ }
+ }
+ }
+ }
+
+ return border_reached;
+}
+
+static struct ei_alien_group *
+ei_game_find_alien_group(struct ei_game *game, struct ei_alien *alien)
+{
+ struct ei_alien_group *group = NULL;
+
+ assert(alien);
+
+ for (size_t i = 0; i < ARRAY_SIZE(game->aliens); i++) {
+ struct ei_alien_group *tmp = &game->aliens[i];
+
+ if (ei_alien_group_has_alien(tmp, alien)) {
+ group = tmp;
+ break;
+ }
+ }
+
+ return group;
+}
+
+static void
+ei_game_add_bunkers(struct ei_game *game)
+{
+ int x = 6;
+
+ assert(game);
+
+ for (size_t i = 0; i < ARRAY_SIZE(game->bunkers); i++) {
+ struct ei_bunker *bunker = &game->bunkers[i];
+
+ ei_bunker_reset_sprite(bunker);
+ eetg_world_add(&game->world, ei_bunker_get_object(bunker), x, 17);
+
+ x += 20;
+ }
+}
+
+static void
+ei_game_add_aliens(struct ei_game *game)
+{
+ assert(game);
+
+ for (size_t i = 0; i < ARRAY_SIZE(game->aliens); i++) {
+ struct ei_alien_group *group = &game->aliens[i];
+
+ ei_alien_group_attach(group, &game->world,
+ EI_ALIEN_STARTING_ROW + (i * 2));
+ }
+}
+
+static void
+ei_game_update_status(struct ei_game *game)
+{
+ assert(game);
+
+ snprintf(game->status_sprite, sizeof(game->status_sprite),
+ EI_STATUS_SPRITE_FORMAT, game->score, game->nr_lives);
+}
+
+static void
+ei_game_prepare(struct ei_game *game)
+{
+ assert(game);
+
+ eetg_world_clear(&game->world);
+
+ game->state = EI_STATE_PREPARED;
+}
+
+static void
+ei_game_start(struct ei_game *game)
+{
+ assert(game);
+
+ eetg_world_clear(&game->world);
+
+ eetg_world_add(&game->world, &game->player, 37, 23);
+
+ ei_game_add_bunkers(game);
+ ei_game_add_aliens(game);
+
+ eetg_world_add(&game->world, &game->status, 26, 0);
+
+ game->player_missile_counter_reload = EI_FPS / EI_PLAYER_MISSILE_SPEED;
+ game->player_missile_counter = game->player_missile_counter_reload;
+ game->aliens_speed_counter_reload = EI_FPS / EI_ALIENS_SPEED;
+ game->aliens_speed_counter = game->aliens_speed_counter_reload;
+ game->first_alien_missile_counter = EI_FPS * EI_FIRST_ALIEN_MISSILE_DELAY;
+ game->alien_missile_counter_reload = EI_FPS / EI_ALIEN_MISSILE_SPEED;
+ game->ufo_counter_reload = EI_FPS / EI_UFO_SPEED;
+ game->nr_dead_aliens = 0;
+
+ game->aliens_move_left = false;
+ game->aliens_move_down = false;
+
+ game->state = EI_STATE_PLAYING;
+}
+
+static void
+ei_game_kill_alien(struct ei_game *game, struct ei_alien *alien)
+{
+ int group, score;
+
+ assert(alien);
+
+ group = ei_game_find_alien_group(game, alien) - game->aliens;
+
+ if (group == 0) {
+ score = EI_SCORE_ALIENS0;
+ } else if ((group == 1) || (group == 2)) {
+ score = EI_SCORE_ALIENS12;
+ } else {
+ score = EI_SCORE_ALIENS34;
+ }
+
+ game->score += score;
+
+ ei_game_update_status(game);
+
+ eetg_world_remove(&game->world, ei_alien_get_object(alien));
+
+ game->nr_dead_aliens++;
+
+ if (game->nr_dead_aliens == (EI_NR_ALIEN_GROUPS * EI_ALIEN_GROUP_SIZE)) {
+ ei_game_prepare(game);
+ } else {
+ int aliens_speed;
+
+ aliens_speed = game->nr_dead_aliens / 2;
+
+ if (aliens_speed < (EI_FPS / 5)) {
+ aliens_speed = EI_FPS / 5;
+ } else if (aliens_speed > ((EI_FPS * 4) / 5)) {
+ aliens_speed = ((EI_FPS * 4) / 5);
+ }
+
+ game->aliens_speed_counter_reload = EI_FPS / aliens_speed;
+ }
+}
+
+static void
+ei_game_damage_bunker(struct ei_game *game, struct ei_bunker *bunker,
+ int x, int y)
+{
+ bool destroyed;
+
+ destroyed = ei_bunker_damage(bunker, x, y);
+
+ if (destroyed) {
+ eetg_world_remove(&game->world, ei_bunker_get_object(bunker));
+ }
+}
+
+static void
+ei_game_terminate(struct ei_game *game)
+{
+ assert(game);
+
+ eetg_world_clear(&game->world);
+
+ eetg_world_add(&game->world, &game->end_title, 12, 10);
+ eetg_world_add(&game->world, &game->status, 26, 6);
+ eetg_world_add(&game->world, &game->start, 30, 20);
+
+ game->state = EI_STATE_GAME_OVER;
+}
+
+static void
+ei_game_kill_player(struct ei_game *game, bool game_over)
+{
+ assert(game);
+ assert(game->nr_lives > 0);
+
+ game->nr_lives--;
+
+ ei_game_update_status(game);
+
+ if (game_over || (game->nr_lives == 0))
+ {
+ ei_game_terminate(game);
+ }
+}
+
+static void
+ei_game_handle_player_missile_collision(struct ei_game *game,
+ struct eetg_object *missile,
+ struct eetg_object *object,
+ int x, int y)
+{
+ assert(game);
+
+ eetg_world_remove(&game->world, missile);
+
+ if (eetg_object_get_type(object) == EI_TYPE_BUNKER) {
+ ei_game_damage_bunker(game, ei_bunker_get(object), x, y);
+ } else if (eetg_object_get_type(object) == EI_TYPE_ALIEN_MISSILE) {
+ game->score += EI_SCORE_MISSILE;
+ eetg_world_remove(&game->world, object);
+ } else if (eetg_object_get_type(object) == EI_TYPE_ALIEN) {
+ ei_game_kill_alien(game, ei_alien_get(object));
+ } else if (eetg_object_get_type(object) == EI_TYPE_UFO) {
+ game->score += EI_SCORE_UFO_BASE * ((eetg_rand() % 5) + 1);
+ eetg_world_remove(&game->world, object);
+ }
+}
+
+static void
+ei_game_handle_alien_collision(struct ei_game *game,
+ struct eetg_object *object,
+ int x, int y)
+{
+ if (eetg_object_get_type(object) == EI_TYPE_BUNKER) {
+ ei_game_damage_bunker(game, ei_bunker_get(object), x, y);
+ } else if (eetg_object_get_type(object) == EI_TYPE_PLAYER) {
+ ei_game_kill_player(game, true);
+ }
+}
+
+static void
+ei_game_handle_alien_missile_collision(struct ei_game *game,
+ struct eetg_object *missile,
+ struct eetg_object *object,
+ int x, int y)
+{
+ assert(game);
+
+ if (eetg_object_get_type(object) == EI_TYPE_ALIEN) {
+ return;
+ }
+
+ eetg_world_remove(&game->world, missile);
+
+ if (eetg_object_get_type(object) == EI_TYPE_BUNKER) {
+ ei_game_damage_bunker(game, ei_bunker_get(object), x, y);
+ } else if (eetg_object_get_type(object) == EI_TYPE_PLAYER) {
+ ei_game_kill_player(game, false);
+ }
+}
+
+static void
+ei_game_handle_collision(struct eetg_object *object1,
+ struct eetg_object *object2,
+ int x, int y, void *arg)
+{
+ if (ei_has_type(object1, object2, EI_TYPE_PLAYER_MISSILE)) {
+ struct eetg_object *missile, *other;
+
+ missile = ei_get_object(object1, object2, EI_TYPE_PLAYER_MISSILE);
+ other = (object1 == missile) ? object2 : object1;
+
+ ei_game_handle_player_missile_collision(arg, missile, other, x, y);
+ } else if (ei_has_type(object1, object2, EI_TYPE_ALIEN)) {
+ struct eetg_object *alien, *other;
+
+ alien = ei_get_object(object1, object2, EI_TYPE_ALIEN);
+ other = (object1 == alien) ? object2 : object1;
+
+ ei_game_handle_alien_collision(arg, other, x, y);
+ } else if (ei_has_type(object1, object2, EI_TYPE_ALIEN_MISSILE)) {
+ struct eetg_object *missile, *other;
+
+ missile = ei_get_object(object1, object2, EI_TYPE_ALIEN_MISSILE);
+ other = (object1 == missile) ? object2 : object1;
+
+ ei_game_handle_alien_missile_collision(arg, missile, other, x, y);
+ }
+}
+
+static void
+ei_game_reset_history(struct ei_game *game)
+{
+ assert(game);
+
+ game->score = 0;
+ game->nr_lives = EI_NR_LIVES;
+
+ ei_game_update_status(game);
+}
+
+static bool
+ei_game_process_intro_input(struct ei_game *game, char c)
+{
+ assert(game);
+
+ if (c == 'x') {
+ return true;
+ }
+
+ if (c != ' ') {
+ return false;
+ }
+
+ ei_game_reset_history(game);
+ ei_game_prepare(game);
+
+ return false;
+}
+
+static bool
+ei_game_process_game_input(struct ei_game *game, char c)
+{
+ assert(game);
+
+ if (c == 'x') {
+ return true;
+ }
+
+ if (c == 's') {
+ struct eetg_object *player_object = &game->player;
+ int x;
+
+ x = eetg_object_get_x(player_object);
+
+ if (x > 0) {
+ eetg_object_move(player_object, x - 1,
+ eetg_object_get_y(player_object));
+ }
+ } else if (c == 'f') {
+ struct eetg_object *player_object = &game->player;
+ int x, width;
+
+ x = eetg_object_get_x(player_object);
+ width = eetg_object_get_width(player_object);
+
+ if ((x + width) < EETG_COLUMNS) {
+ eetg_object_move(player_object, x + 1,
+ eetg_object_get_y(player_object));
+ }
+ } else if (c == ' ') {
+ struct eetg_object *player_missile = &game->player_missile;
+
+ if (eetg_object_get_world(player_missile) == NULL) {
+ struct eetg_object *player_object = &game->player;
+ int x, y;
+
+ x = eetg_object_get_x(player_object);
+ y = eetg_object_get_y(player_object);
+
+ eetg_world_add(&game->world, player_missile, x + 2, y - 1);
+
+ game->player_missile_counter = game->player_missile_counter_reload;
+ }
+ }
+
+ return false;
+}
+
+static void
+ei_game_process_player_missile(struct ei_game *game)
+{
+ int x, y;
+
+ assert(game);
+
+ if (eetg_object_get_world(&game->player_missile) == NULL) {
+ return;
+ }
+
+ assert(game->player_missile_counter > 0);
+ game->player_missile_counter--;
+
+ if (game->player_missile_counter != 0) {
+ return;
+ }
+
+ game->player_missile_counter = game->player_missile_counter_reload;
+
+ x = eetg_object_get_x(&game->player_missile);
+ y = eetg_object_get_y(&game->player_missile) - 1;
+
+ if (y == 0) {
+ eetg_world_remove(&game->world, &game->player_missile);
+ } else {
+ eetg_object_move(&game->player_missile, x, y);
+ }
+}
+
+static struct ei_alien *
+ei_game_select_firing_alien(struct ei_game *game)
+{
+ struct ei_alien *alien = NULL;
+ int nr_firing_aliens = 0;
+
+ assert(game);
+
+ for (size_t i = 0; i < EI_ALIEN_GROUP_SIZE; i++) {
+ for (size_t j = 0; j < ARRAY_SIZE(game->aliens); j++) {
+ struct ei_alien *tmp = ei_alien_group_get(&game->aliens[j], i);
+
+ if (!ei_alien_is_dead(tmp)) {
+ nr_firing_aliens++;
+ break;
+ }
+ }
+ }
+
+ if (nr_firing_aliens > 0) {
+ int index;
+
+ index = eetg_rand() % nr_firing_aliens;
+
+ for (size_t i = ARRAY_SIZE(game->aliens) - 1;
+ i < ARRAY_SIZE(game->aliens); i--) {
+ struct ei_alien *tmp = ei_alien_group_get(&game->aliens[i], index);
+
+ if (!ei_alien_is_dead(tmp)) {
+ alien = tmp;
+ break;
+ }
+ }
+ }
+
+ return alien;
+}
+
+static void
+ei_game_process_alien_missile(struct ei_game *game)
+{
+ assert(game);
+
+ if (game->first_alien_missile_counter != 0) {
+ assert(game->first_alien_missile_counter > 0);
+ game->first_alien_missile_counter--;
+ return;
+ }
+
+ if (eetg_object_get_world(&game->alien_missile) != NULL) {
+ assert(game->alien_missile_counter > 0);
+ game->alien_missile_counter--;
+
+ if (game->alien_missile_counter == 0) {
+ int x, y;
+
+ game->alien_missile_counter = game->alien_missile_counter_reload;
+
+ x = eetg_object_get_x(&game->alien_missile);
+ y = eetg_object_get_y(&game->alien_missile);
+
+ if (y == EETG_ROWS) {
+ eetg_world_remove(&game->world, &game->alien_missile);
+ } else {
+ eetg_object_move(&game->alien_missile, x, y + 1);
+ }
+ }
+ } else {
+ struct ei_alien *alien;
+ int x, y;
+
+ alien = ei_game_select_firing_alien(game);
+
+ if (alien) {
+ x = eetg_object_get_x(&alien->object);
+ y = eetg_object_get_y(&alien->object);
+
+ eetg_world_add(&game->world, &game->alien_missile, x, y + 1);
+
+ game->alien_missile_counter = game->alien_missile_counter_reload;
+ }
+ }
+}
+
+static void
+ei_game_process_aliens(struct ei_game *game)
+{
+ assert(game);
+ assert(game->aliens_speed_counter > 0);
+
+ game->aliens_speed_counter--;
+
+ if (game->aliens_speed_counter != 0) {
+ return;
+ }
+
+ game->aliens_speed_counter = game->aliens_speed_counter_reload;
+
+ if (game->aliens_move_down) {
+ for (size_t i = ARRAY_SIZE(game->aliens) - 1;
+ i < ARRAY_SIZE(game->aliens); i--) {
+ bool game_over;
+
+ game_over = ei_alien_group_move_down(&game->aliens[i]);
+
+ if (game_over) {
+ ei_game_terminate(game);
+ }
+ }
+
+ game->aliens_move_down = false;
+
+ if (eetg_object_get_world(&game->ufo) == NULL) {
+ int n;
+
+ n = eetg_rand() % 3;
+
+ if (n == 0) {
+ int x;
+
+ n = eetg_rand() % 2;
+
+ if (n == 0) {
+ x = EETG_COLUMNS;
+ game->ufo_moves_left = true;
+ } else {
+ x = -eetg_object_get_width(&game->ufo);
+ game->ufo_moves_left = false;
+ }
+
+ eetg_world_add(&game->world, &game->ufo, x, 2);
+
+ game->ufo_counter = game->ufo_counter_reload;
+ }
+ }
+ } else {
+ bool border_reached = false;
+
+ for (size_t i = 0; i < ARRAY_SIZE(game->aliens); i++) {
+ struct ei_alien_group *group = &game->aliens[i];
+ bool border_reached_by_group;
+
+ if (game->aliens_move_left) {
+ border_reached_by_group = ei_alien_group_move_left(group);
+ } else {
+ border_reached_by_group = ei_alien_group_move_right(group);
+ }
+
+ if (border_reached_by_group) {
+ border_reached = true;
+ }
+ }
+
+ if (border_reached) {
+ game->aliens_move_down = true;
+ game->aliens_move_left = !game->aliens_move_left;
+ }
+ }
+}
+
+static void
+ei_game_process_ufo(struct ei_game *game)
+{
+ struct eetg_object *ufo;
+
+ assert(game);
+
+ ufo = &game->ufo;
+
+ if (eetg_object_get_world(ufo) == NULL) {
+ return;
+ }
+
+ assert(game->ufo_counter > 0);
+
+ game->ufo_counter--;
+
+ if (game->ufo_counter != 0) {
+ return;
+ }
+
+ game->ufo_counter = game->ufo_counter_reload;
+
+ if (game->ufo_moves_left) {
+ int x;
+
+ x = eetg_object_get_x(ufo) - 1;
+
+ if ((x + eetg_object_get_width(ufo)) <= 0) {
+ eetg_world_remove(&game->world, ufo);
+ } else {
+ eetg_object_move(ufo, x, eetg_object_get_y(ufo));
+ }
+ } else {
+ int x;
+
+ x = eetg_object_get_x(ufo) + 1;
+
+ if (x >= EETG_COLUMNS) {
+ eetg_world_remove(&game->world, ufo);
+ } else {
+ eetg_object_move(ufo, x, eetg_object_get_y(ufo));
+ }
+ }
+}
+
+static void
+ei_game_init_bunkers(struct ei_game *game)
+{
+ assert(game);
+
+ for (size_t i = 0; i < ARRAY_SIZE(game->bunkers); i++) {
+ ei_bunker_init(&game->bunkers[i]);
+ }
+}
+
+static void
+ei_game_init_aliens(struct ei_game *game)
+{
+ assert(game);
+
+ for (size_t i = 0; i < ARRAY_SIZE(game->aliens); i++) {
+ struct ei_alien_group *group = &game->aliens[i];
+ const char *sprite1, *sprite2;
+ int color;
+
+ sprite1 = ei_get_group_sprite1(i);
+ sprite2 = ei_get_group_sprite2(i);
+ color = ei_get_group_color(i);
+
+ ei_alien_group_init(group, sprite1, sprite2, color);
+ }
+}
+
+void
+ei_game_init(struct ei_game *game, eetg_write_fn write_fn, void *arg)
+{
+ assert(game);
+
+ game->sync_counter_reload = EI_FPS * 2;
+ game->sync_counter = 1;
+
+ game->state = EI_STATE_INTRO;
+
+ ei_game_reset_history(game);
+
+ eetg_world_init(&game->world, write_fn, arg);
+ eetg_world_register_collision_fn(&game->world,
+ ei_game_handle_collision,
+ game);
+
+ eetg_object_init(&game->title, EI_TYPE_TITLE, EI_TITLE_SPRITE);
+ eetg_object_set_color(&game->title, EETG_COLOR_BLUE);
+
+ eetg_object_init(&game->help, EI_TYPE_HELP, EI_HELP_SPRITE);
+ eetg_object_set_color(&game->help, EETG_COLOR_RED);
+
+ eetg_object_init(&game->start, EI_TYPE_START, EI_START_SPRITE);
+ eetg_object_set_color(&game->start, EETG_COLOR_RED);
+
+ eetg_object_init(&game->player, EI_TYPE_PLAYER, EI_PLAYER_SPRITE);
+ eetg_object_set_color(&game->player, EETG_COLOR_YELLOW);
+
+ eetg_object_init(&game->player_missile, EI_TYPE_PLAYER_MISSILE, "!\n");
+ eetg_object_set_color(&game->player_missile, EETG_COLOR_WHITE);
+
+ ei_game_init_bunkers(game);
+ ei_game_init_aliens(game);
+
+ eetg_object_init(&game->alien_missile, EI_TYPE_ALIEN_MISSILE, ":\n");
+ eetg_object_set_color(&game->alien_missile, EETG_COLOR_MAGENTA);
+
+ eetg_object_init(&game->ufo, EI_TYPE_UFO, "<o~o>\n");
+ eetg_object_set_color(&game->ufo, EETG_COLOR_MAGENTA);
+
+ eetg_object_init(&game->status, EI_TYPE_STATUS, game->status_sprite);
+ eetg_object_set_color(&game->status, EETG_COLOR_RED);
+
+ eetg_object_init(&game->end_title, EI_TYPE_END_TITLE, EI_END_TITLE_SPRITE);
+ eetg_object_set_color(&game->end_title, EETG_COLOR_WHITE);
+
+ eetg_world_add(&game->world, &game->title, 8, 1);
+ eetg_world_add(&game->world, &game->help, 30, 16);
+ eetg_world_add(&game->world, &game->start, 30, 20);
+}
+
+bool
+ei_game_process(struct ei_game *game, int8_t c)
+{
+ bool sync = false;
+ bool leave = false;
+
+ game->sync_counter--;
+
+ if (game->sync_counter == 0) {
+ sync = true;
+ game->sync_counter = game->sync_counter_reload;
+ }
+
+ eetg_world_render(&game->world, sync);
+
+ switch (game->state) {
+ case EI_STATE_INTRO:
+ case EI_STATE_GAME_OVER:
+ if (c >= 0) {
+ leave = ei_game_process_intro_input(game, (char)c);
+ }
+
+ break;
+ case EI_STATE_PREPARED:
+ ei_game_start(game);
+ break;
+ case EI_STATE_PLAYING:
+ ei_game_process_player_missile(game);
+ ei_game_process_aliens(game);
+ ei_game_process_ufo(game);
+ ei_game_process_alien_missile(game);
+
+ if (c >= 0) {
+ leave = ei_game_process_game_input(game, (char)c);
+ }
+
+ break;
+ }
+
+ return leave;
+}