diff options
author | Richard Braun <rbraun@sceen.net> | 2024-03-30 21:58:46 +0100 |
---|---|---|
committer | Richard Braun <rbraun@sceen.net> | 2024-03-30 21:58:46 +0100 |
commit | 7fbe749f9aee6c8c0ac74a409fdfd152f554e6a6 (patch) | |
tree | edb434a7cbd8d96ff9df3591b5dd4f7c42482fa5 |
Initial commit
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | LICENSE | 10 | ||||
-rw-r--r-- | Makefile | 30 | ||||
-rw-r--r-- | src/eetg.c | 719 | ||||
-rw-r--r-- | src/eetg.h | 114 | ||||
-rw-r--r-- | src/ei.c | 1184 | ||||
-rw-r--r-- | src/ei.h | 91 | ||||
-rw-r--r-- | src/macros.h | 27 | ||||
-rw-r--r-- | src/main.c | 98 |
9 files changed, 2277 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9783bf6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.* +*.o +cscope.out +/embedded_invaders @@ -0,0 +1,10 @@ +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b681393 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +MAKEFLAGS += --no-builtin-rules +MAKEFLAGS += --no-builtin-variables + +CC = gcc + +BINARY = embedded_invaders + +CFLAGS = -std=gnu11 +CFLAGS += -O0 -g +CFLAGS += -m32 +CFLAGS += -Wall -Wextra -Werror=implicit +CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wshadow + +SOURCES = \ + src/main.c \ + src/eetg.c \ + src/ei.c + +OBJECTS = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(SOURCES))) + +$(BINARY): $(OBJECTS) + $(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ $(LIBS) + +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +clean: + rm -f $(BINARY) $(OBJECTS) + +.PHONY: clean $(SOURCES) diff --git a/src/eetg.c b/src/eetg.c new file mode 100644 index 0000000..ebfd394 --- /dev/null +++ b/src/eetg.c @@ -0,0 +1,719 @@ +/* + * 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. + * + * + * Engine for embedded text-based games. + */ + +#include <stdio.h> + +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +#include "eetg.h" +#include "macros.h" + +#define EETG_RENDERING_DISABLED 0 + +#define EETG_BG_COLOR EETG_COLOR_BLACK +#define EETG_FG_COLOR EETG_COLOR_WHITE + +#define EETG_CSI "\e[" + +static unsigned int eetg_rand_next = 1; + +static void +eetg_object_set(struct eetg_object *object, struct eetg_world *world, + int x, int y) +{ + assert(object); + assert(!object->world); + assert(world); + + object->world = world; + object->x = x; + object->y = y; +} + +static void +eetg_object_unset(struct eetg_object *object) +{ + assert(object); + assert(object->world); + + object->world = NULL; + object->next = NULL; +} + +static char +eetg_object_get_char(const struct eetg_object *object, int x, int y) +{ + int index; + + index = eetg_object_get_cell(object, x, y); + + if (index == -1) + { + return index; + } + + return object->sprite[index]; +} + +static void +eetg_object_check_collision(struct eetg_object *object1, + struct eetg_object *object2, + eetg_handle_collision_fn handle_collision_fn, + void *handle_collision_fn_arg) +{ + int o1xbr, o1ybr, o2xbr, o2ybr; + int xtl, ytl, xbr, ybr; + + assert(object1); + assert(object2); + assert(handle_collision_fn); + + o1xbr = object1->x + object1->width - 1; + o1ybr = object1->y + object1->height - 1; + + o2xbr = object2->x + object2->width - 1; + o2ybr = object2->y + object2->height - 1; + + if ((object2->x > o1xbr) || (object1->x > o2xbr) || + (object2->y > o1ybr) || (object1->y > o2ybr)) { + return; + } + + xtl = (object1->x > object2->x) ? object1->x : object2->x; + ytl = (object1->y > object2->y) ? object1->y : object2->y; + xbr = (o1xbr < o2xbr) ? o1xbr : o2xbr; + ybr = (o1ybr < o2ybr) ? o1ybr : o2ybr; + + assert(xtl <= xbr); + assert(ytl <= ybr); + + for (int i = xtl; i <= xbr; i++) { + for (int j = ytl; j <= ybr; j++) { + char c1, c2; + + c1 = eetg_object_get_char(object1, i, j); + c2 = eetg_object_get_char(object2, i, j); + + if ((c1 <= 0) || (c2 <= 0)) { + return; + } else if ((c1 == ' ') || (c2 == ' ')) { + continue; + } + + handle_collision_fn(object1, object2, + i, j, handle_collision_fn_arg); + return; + } + } +} + +static void +eetg_view_cell_set(struct eetg_view_cell *view_cell, + char c, int color) +{ + assert(view_cell); + + view_cell->c = c; + view_cell->color = color; +} + +static char +eetg_view_cell_get_c(const struct eetg_view_cell *view_cell) +{ + assert(view_cell); + + return view_cell->c; +} + +static int +eetg_view_cell_get_color(const struct eetg_view_cell *view_cell) +{ + assert(view_cell); + + return view_cell->color; +} + +static struct eetg_view_cell * +eetg_view_row_get_cell(struct eetg_view_row *view_row, int index) +{ + assert(view_row); + assert(index >= 0); + assert(index < (int)ARRAY_SIZE(view_row->columns)); + + return &view_row->columns[index]; +} + +static void +eetg_view_row_init(struct eetg_view_row *view_row) +{ + assert(view_row); + + for (size_t i = 0; i < ARRAY_SIZE(view_row->columns); i++) { + struct eetg_view_cell *view_cell; + + view_cell = eetg_view_row_get_cell(view_row, (int)i); + eetg_view_cell_set(view_cell, ' ', EETG_BG_COLOR); + } +} + +static struct eetg_view_row * +eetg_view_get_row(struct eetg_view *view, int index) +{ + assert(view); + assert(index >= 0); + assert(index < (int)ARRAY_SIZE(view->rows)); + + return &view->rows[index]; +} + +static void +eetg_view_init(struct eetg_view *view) +{ + assert(view); + + for (size_t i = 0; i < ARRAY_SIZE(view->rows); i++) { + eetg_view_row_init(eetg_view_get_row(view, (int)i)); + } +} + +void +eetg_world_init(struct eetg_world *world, eetg_write_fn write_fn, void *arg) +{ + assert(world); + assert(write_fn); + + world->write_fn = write_fn; + world->write_fn_arg = arg; + + world->handle_collision_fn = NULL; + world->objects = NULL; + + eetg_view_init(&world->views[0]); + eetg_view_init(&world->views[1]); + + world->view = &world->views[0]; + world->prev_view = &world->views[1]; + + world->cursor_row = -1; + world->cursor_column = -1; + world->current_color = EETG_FG_COLOR; +} + +void +eetg_world_clear(struct eetg_world *world) +{ + struct eetg_object *object; + + assert(world); + + object = world->objects; + + while (object) { + struct eetg_object *next = object->next; + + eetg_object_unset(object); + object = next; + } + + world->objects = NULL; +} + +void +eetg_world_register_collision_fn(struct eetg_world *world, + eetg_handle_collision_fn handle_collision_fn, + void *arg) +{ + assert(world); + assert(handle_collision_fn); + + world->handle_collision_fn = handle_collision_fn; + world->handle_collision_fn_arg = arg; +} + +static void +eetg_world_scan_collisions(struct eetg_world *world, struct eetg_object *object) +{ + assert(world); + + if (!world->handle_collision_fn) { + return; + } + + for (struct eetg_object *tmp = world->objects; tmp; tmp = tmp->next) { + if (tmp == object) { + continue; + } + + eetg_object_check_collision(object, tmp, + world->handle_collision_fn, + world->handle_collision_fn_arg); + } +} + +void +eetg_world_add(struct eetg_world *world, struct eetg_object *object, + int x, int y) +{ + assert(world); + assert(eetg_object_get_world(object) == NULL); + + object->next = world->objects; + world->objects = object; + + eetg_object_set(object, world, x, y); + eetg_world_scan_collisions(world, object); +} + +void +eetg_world_remove(struct eetg_world *world, struct eetg_object *object) +{ + assert(world); + assert(eetg_object_get_world(object) == world); + + if (world->objects == object) { + world->objects = object->next; + goto out; + } + + for (struct eetg_object *tmp = world->objects; tmp->next; tmp = tmp->next) { + if (tmp->next == object) { + tmp->next = tmp->next->next; + break; + } + } + +out: + eetg_object_unset(object); +} + +static void +eetg_object_render_row(struct eetg_object *object, int row, + struct eetg_view_row *view_row) +{ + const char *line; + + assert(object); + assert(row < object->height); + + line = &object->sprite[(object->width + 1) * row]; + + for (int obj_column = 0; obj_column < object->width; obj_column++) { + int column = object->x + obj_column; + char c; + + if ((column < 0) || (column >= EETG_COLUMNS)) { + continue; + } + + c = line[obj_column]; + + if (c != ' ') { + struct eetg_view_cell *view_cell; + + view_cell = eetg_view_row_get_cell(view_row, column); + eetg_view_cell_set(view_cell, c, object->color); + } + } +} + +static void +eetg_object_render(struct eetg_object *object, struct eetg_view *view) +{ + assert(object); + + for (int obj_row = 0; obj_row < object->height; obj_row++) { + int row = object->y + obj_row; + + if ((row < 0) || (row >= EETG_ROWS)) { + continue; + } + + eetg_object_render_row(object, obj_row, eetg_view_get_row(view, row)); + } +} + +static void +eetg_world_write(struct eetg_world *world, const void *buffer, size_t size) +{ + assert(world); + assert(world->write_fn); + assert(buffer); + +#if EETG_RENDERING_DISABLED + (void)buffer; + (void)size; +#else + world->write_fn(buffer, size, world->write_fn_arg); +#endif +} + +static void +eetg_world_write_str(struct eetg_world *world, const char *str) +{ + eetg_world_write(world, str, strlen(str)); +} + +static void +eetg_world_set_cursor(struct eetg_world *world, int row, int column) +{ + char str[32]; + + assert(world); + assert(row >= 0); + assert(row < EETG_ROWS); + assert(column >= 0); + assert(column < EETG_COLUMNS); + + if ((world->cursor_row == row) && (world->cursor_column == column)) { + return; + } + + snprintf(str, sizeof(str), EETG_CSI "%d;%dH", row + 1, column + 1); + eetg_world_write_str(world, str); + + world->cursor_row = row; + world->cursor_column = column; +} + +static int +eetg_convert_fg_color(int color) +{ + return color + 30; +} + +static int +eetg_convert_bg_color(int color) +{ + return eetg_convert_fg_color(color) + 10; +} + +static void +eetg_world_set_color(struct eetg_world *world, int color, bool force) +{ + char str[16]; + + if ((color == world->current_color) && !force) { + return; + } + + snprintf(str, sizeof(str), EETG_CSI "%d;%dm", + eetg_convert_fg_color(color), + eetg_convert_bg_color(EETG_BG_COLOR)); + + eetg_world_write_str(world, str); + + world->current_color = color; +} + +static void +eetg_world_write_char(struct eetg_world *world, char c) +{ + eetg_world_write(world, &c, sizeof(c)); + + world->cursor_column++; + + if (world->cursor_column == EETG_COLUMNS) { + if (world->cursor_row == EETG_ROWS) { + world->cursor_column = EETG_COLUMNS - 1; + } else { + world->cursor_column = 0; + world->cursor_row++; + } + } +} + +static void +eetg_world_swap_views(struct eetg_world *world) +{ + struct eetg_view *tmp; + + assert(world); + + tmp = world->view; + world->view = world->prev_view; + world->prev_view = tmp; +} + +static void +eetg_world_render_sync(struct eetg_world *world) +{ + assert(world); + + eetg_world_write_str(world, EETG_CSI "?25l"); /* cursor invisible */ + eetg_world_set_color(world, EETG_FG_COLOR, true); + eetg_world_write_str(world, EETG_CSI "2J"); /* clear screen */ + eetg_world_set_cursor(world, 0, 0); + + for (int row = 0; row < EETG_ROWS; row++) { + struct eetg_view_row *view_row; + + view_row = eetg_view_get_row(world->view, row); + + for (int column = 0; column < EETG_COLUMNS; column++) { + struct eetg_view_cell *view_cell; + int color; + char c; + + view_cell = eetg_view_row_get_cell(view_row, column); + color = eetg_view_cell_get_color(view_cell); + c = eetg_view_cell_get_c(view_cell); + + if (c == ' ') { + continue; + } + + eetg_world_set_cursor(world, row, column); + eetg_world_set_color(world, color, false); + eetg_world_write_char(world, c); + } + } +} + +static void +eetg_world_render_delta(struct eetg_world *world) +{ + assert(world); + + for (int row = 0; row < EETG_ROWS; row++) { + struct eetg_view_row *view_row, *prev_view_row; + + view_row = eetg_view_get_row(world->view, row); + prev_view_row = eetg_view_get_row(world->prev_view, row); + + for (int column = 0; column < EETG_COLUMNS; column++) { + struct eetg_view_cell *view_cell, *prev_view_cell; + int color, prev_color; + char c, prev_c; + + view_cell = eetg_view_row_get_cell(view_row, column); + color = eetg_view_cell_get_color(view_cell); + c = eetg_view_cell_get_c(view_cell); + + prev_view_cell = eetg_view_row_get_cell(prev_view_row, column); + prev_color = eetg_view_cell_get_color(prev_view_cell); + prev_c = eetg_view_cell_get_c(prev_view_cell); + + if ((color != prev_color) || (c != prev_c)) { + eetg_world_set_cursor(world, row, column); + eetg_world_set_color(world, color, false); + eetg_world_write_char(world, c); + } + } + } +} + +void +eetg_world_render(struct eetg_world *world, bool sync) +{ + for (int row = 0; row < EETG_ROWS; row++) { + struct eetg_view_row *view_row; + + view_row = eetg_view_get_row(world->view, row); + + for (int column = 0; column < EETG_COLUMNS; column++) { + struct eetg_view_cell *view_cell; + + view_cell = eetg_view_row_get_cell(view_row, column); + eetg_view_cell_set(view_cell, ' ', EETG_FG_COLOR); + } + } + + for (struct eetg_object *obj = world->objects; obj; obj = obj->next) { + eetg_object_render(obj, world->view); + } + + if (sync) { + eetg_world_render_sync(world); + } else { + eetg_world_render_delta(world); + } + + eetg_world_set_cursor(world, 0, 0); + eetg_world_swap_views(world); +} + +void +eetg_object_init(struct eetg_object *object, int type, const char *sprite) +{ + size_t width; + char *ptr; + + assert(object); + + object->world = NULL; + object->next = NULL; + object->sprite = sprite; + object->type = type; + object->x = 0; + object->y = 0; + object->color = EETG_FG_COLOR; + + ptr = strchr(sprite, '\n'); + assert(ptr); + + width = ptr - sprite; + assert(width <= INT_MAX); + + object->width = (int)width; + object->height = 1; + + for (;;) { + char *next; + + ptr = &ptr[1]; + next = strchr(ptr, '\n'); + + if (!next) { + break; + } + + width = next - ptr; + assert(width <= INT_MAX); + assert((int)width == object->width); + + assert(object->height < INT_MAX); + object->height++; + + ptr = next; + } +} + +void +eetg_object_set_color(struct eetg_object *object, int color) +{ + assert(object); + + object->color = color; +} + +int +eetg_object_get_type(const struct eetg_object *object) +{ + assert(object); + + return object->type; +} + +const char * +eetg_object_get_sprite(const struct eetg_object *object) +{ + assert(object); + + return object->sprite; +} + +int +eetg_object_get_x(const struct eetg_object *object) +{ + assert(object); + + return object->x; +} + +int +eetg_object_get_y(const struct eetg_object *object) +{ + assert(object); + + return object->y; +} + +int +eetg_object_get_width(const struct eetg_object *object) +{ + assert(object); + + return object->width; +} + +int +eetg_object_get_height(const struct eetg_object *object) +{ + assert(object); + + return object->height; +} + +bool +eetg_object_is_empty(const struct eetg_object *object) +{ + assert(object); + + for (const char *ptr = object->sprite; ptr; ptr++) { + char c = *ptr; + + if (c == '\0') { + break; + } else if ((c != ' ') && (c != '\n')) { + return false; + } + } + + return true; +} + +void +eetg_object_move(struct eetg_object *object, int x, int y) +{ + assert(object); + + object->x = x; + object->y = y; + + if (object->world) { + eetg_world_scan_collisions(object->world, object); + } +} + +int +eetg_object_get_cell(const struct eetg_object *object, int x, int y) +{ + assert(object); + + x -= object->x; + y -= object->y; + + if ((x < 0) || (x >= object->width) || (y < 0) || (y >= object->height)) { + return -1; + } + + return ((object->width + 1) * y) + (x % object->width); +} + +struct eetg_world * +eetg_object_get_world(const struct eetg_object *object) +{ + assert(object); + + return object->world; +} + +void eetg_init_rand(unsigned int seed) +{ + eetg_rand_next = seed; +} + +int eetg_rand(void) +{ + eetg_rand_next = (eetg_rand_next * 1103515245) + 12345; + return (eetg_rand_next / 65536) % (EETG_RAND_MAX + 1); +} diff --git a/src/eetg.h b/src/eetg.h new file mode 100644 index 0000000..411eb83 --- /dev/null +++ b/src/eetg.h @@ -0,0 +1,114 @@ +/* + * 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. + * + * + * Engine for embedded text-based games. + */ + +#ifndef EETG_H +#define EETG_H + +#include <stdbool.h> +#include <stdint.h> + +#define EETG_COLUMNS 80 +#define EETG_ROWS 24 + +#define EETG_RAND_MAX 32767 + +#define EETG_COLOR_BLACK 0 +#define EETG_COLOR_RED 1 +#define EETG_COLOR_GREEN 2 +#define EETG_COLOR_YELLOW 3 +#define EETG_COLOR_BLUE 4 +#define EETG_COLOR_MAGENTA 5 +#define EETG_COLOR_CYAN 6 +#define EETG_COLOR_WHITE 7 + +struct eetg_world; + +struct eetg_object; + +typedef void (*eetg_write_fn)(const void *buffer, size_t size, void *arg); + +typedef void (*eetg_handle_collision_fn)(struct eetg_object *object1, + struct eetg_object *object2, + int x, int y, void *arg); + +struct eetg_object { + struct eetg_world *world; + struct eetg_object *next; + const char *sprite; + int8_t color; + int8_t type; + int8_t x; + int8_t y; + int8_t width; + int8_t height; +}; + +struct eetg_view_cell { + char c; + int8_t color; +}; + +struct eetg_view_row { + struct eetg_view_cell columns[EETG_COLUMNS]; +}; + +struct eetg_view { + struct eetg_view_row rows[EETG_ROWS]; +}; + +struct eetg_world { + eetg_write_fn write_fn; + void *write_fn_arg; + eetg_handle_collision_fn handle_collision_fn; + void *handle_collision_fn_arg; + struct eetg_object *objects; + struct eetg_view views[2]; + struct eetg_view *view; + struct eetg_view *prev_view; + int8_t cursor_row; + int8_t cursor_column; + int8_t current_color; +}; + +void eetg_world_init(struct eetg_world *world, + eetg_write_fn write_fn, void *arg); +void eetg_world_clear(struct eetg_world *world); +void eetg_world_register_collision_fn(struct eetg_world *world, + eetg_handle_collision_fn handle_collision_fn, void *arg); +void eetg_world_add(struct eetg_world *world, struct eetg_object *object, + int x, int y); +void eetg_world_remove(struct eetg_world *world, struct eetg_object *object); +void eetg_world_render(struct eetg_world *world, bool sync); + +void eetg_object_init(struct eetg_object *object, int type, const char *sprite); +void eetg_object_set_color(struct eetg_object *object, int color); +int eetg_object_get_type(const struct eetg_object *object); +const char *eetg_object_get_sprite(const struct eetg_object *object); +int eetg_object_get_x(const struct eetg_object *object); +int eetg_object_get_y(const struct eetg_object *object); +int eetg_object_get_width(const struct eetg_object *object); +int eetg_object_get_height(const struct eetg_object *object); +bool eetg_object_is_empty(const struct eetg_object *object); +void eetg_object_move(struct eetg_object *object, int x, int y); +int eetg_object_get_cell(const struct eetg_object *object, int x, int y); +struct eetg_world *eetg_object_get_world(const struct eetg_object *object); + +void eetg_init_rand(unsigned int seed); +int eetg_rand(void); + +#endif /* EETG_H */ 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; +} diff --git a/src/ei.h b/src/ei.h new file mode 100644 index 0000000..4772db2 --- /dev/null +++ b/src/ei.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +#ifndef EI_H +#define EI_H + +#include <stdbool.h> +#include <stdint.h> + +#include "eetg.h" + +#define EI_FPS 50 + +#define EI_NR_ALIEN_GROUPS 5 +#define EI_ALIEN_GROUP_SIZE 10 +#define EI_ALIEN_WIDTH 3 + +#define EI_STATE_INTRO 0 +#define EI_STATE_PREPARED 1 +#define EI_STATE_PLAYING 2 +#define EI_STATE_GAME_OVER 3 + +struct ei_bunker { + struct eetg_object object; + char sprite[33]; +}; + +struct ei_alien { + struct eetg_object object; +}; + +struct ei_alien_group { + struct ei_alien aliens[EI_ALIEN_GROUP_SIZE]; + const char *sprites[2]; + char sprite[EI_ALIEN_WIDTH + 2]; + int8_t sprite_index; +}; + +struct ei_game { + struct eetg_world world; + struct eetg_object title; + struct eetg_object help; + struct eetg_object start; + struct eetg_object player; + struct eetg_object player_missile; + struct ei_bunker bunkers[4]; + struct ei_alien_group aliens[EI_NR_ALIEN_GROUPS]; + struct eetg_object alien_missile; + struct eetg_object ufo; + struct eetg_object status; + struct eetg_object end_title; + int score; + int8_t sync_counter_reload; + int8_t sync_counter; + int8_t nr_lives; + int8_t player_missile_counter_reload; + int8_t player_missile_counter; + int8_t aliens_speed_counter_reload; + int8_t aliens_speed_counter; + int8_t first_alien_missile_counter; + int8_t alien_missile_counter_reload; + int8_t alien_missile_counter; + int8_t ufo_counter_reload; + int8_t ufo_counter; + int8_t nr_dead_aliens; + int8_t state; + bool aliens_move_left; + bool aliens_move_down; + bool ufo_moves_left; + char status_sprite[32]; +}; + +void ei_game_init(struct ei_game *game, eetg_write_fn write_fn, void *arg); +bool ei_game_process(struct ei_game *game, int8_t c); + +#endif /* EI_H */ diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 0000000..6910ae7 --- /dev/null +++ b/src/macros.h @@ -0,0 +1,27 @@ +/* + * 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. + * + * + * Macros. + */ + +#ifndef MACROS_H +#define MACROS_H + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#define structof(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* MACROS_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..bdd0c3a --- /dev/null +++ b/src/main.c @@ -0,0 +1,98 @@ +/* + * 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. + */ + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> + +#include "eetg.h" +#include "ei.h" + +static struct termios orig_ios; + +static struct ei_game game; + +static void +restore_termios(void) +{ + tcsetattr(STDIN_FILENO, TCSANOW, &orig_ios); + write(STDOUT_FILENO, "\ec", 2); +} + +static void +setup_io(void) +{ + struct termios ios; + + setvbuf(stdin, NULL, _IONBF, 0); + + fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK); + + tcgetattr(STDIN_FILENO, &orig_ios); + atexit(restore_termios); + ios = orig_ios; + ios.c_lflag &= ~(ICANON | ECHO); + ios.c_cc[VMIN] = 1; + ios.c_cc[VTIME] = 0; + tcsetattr(STDIN_FILENO, TCSANOW, &ios); +} + +static void +write_terminal(const void *buffer, size_t size, void *arg) +{ + (void)arg; + + write(STDOUT_FILENO, buffer, size); +} + +int +main(void) +{ + bool leave; + + setup_io(); + + eetg_init_rand(time(NULL)); + + ei_game_init(&game, write_terminal, NULL); + + do { + ssize_t nr_bytes; + int8_t c; + + usleep(1000000 / EI_FPS); + + nr_bytes = read(STDIN_FILENO, &c, 1); + + if (nr_bytes == -1) { + if (errno == EAGAIN) { + c = -1; + } else { + break; + } + } + + leave = ei_game_process(&game, c); + } while (!leave); + + return EXIT_SUCCESS; +} |