diff options
-rw-r--r-- | Makefile | 41 | ||||
-rw-r--r-- | src/ei/Makefile | 5 | ||||
-rw-r--r-- | src/ei/ei.c (renamed from src/ei.c) | 5 | ||||
-rw-r--r-- | src/ei/ei.h (renamed from src/ei.h) | 2 | ||||
-rw-r--r-- | src/ei/main.c (renamed from src/main.c) | 3 | ||||
-rw-r--r-- | src/et/Makefile | 5 | ||||
-rw-r--r-- | src/et/et.c | 320 | ||||
-rw-r--r-- | src/et/et.h | 61 | ||||
-rw-r--r-- | src/et/main.c | 99 |
9 files changed, 523 insertions, 18 deletions
@@ -1,30 +1,43 @@ MAKEFLAGS += --no-builtin-rules MAKEFLAGS += --no-builtin-variables -CC = gcc +all: +.PHONY: all + +clean: +.PHONY: clean -BINARY = embedded_invaders +CC = gcc CFLAGS = -std=gnu11 +CFLAGS += -Isrc 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 +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +SOURCES = src/eetg.c +.PHONY: $(SOURCES) -OBJECTS = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(SOURCES))) +define gen_target +.PHONY: $(SOURCES-$(1)) -$(BINARY): $(OBJECTS) - $(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ $(LIBS) +OBJECTS-$(1) = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(SOURCES) $(SOURCES-$(1)))) -%.o: %.c - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +$(1): $$(OBJECTS-$(1)) + $(CC) -o $$@ $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $$^ $(LIBS) -clean: - rm -f $(BINARY) $(OBJECTS) +all: $(1) + +clean-$(1): + rm -f $(1) $$(OBJECTS-$(1)) +.PHONY: clean-$(1) + +clean: clean-$(1) +endef -.PHONY: clean $(SOURCES) +include src/ei/Makefile +include src/et/Makefile diff --git a/src/ei/Makefile b/src/ei/Makefile new file mode 100644 index 0000000..641bab6 --- /dev/null +++ b/src/ei/Makefile @@ -0,0 +1,5 @@ +SOURCES-ei = \ + src/ei/main.c \ + src/ei/ei.c + +$(eval $(call gen_target,ei)) @@ -23,9 +23,10 @@ #include <stdlib.h> #include <string.h> -#include "eetg.h" +#include <eetg.h> +#include <macros.h> + #include "ei.h" -#include "macros.h" #define EI_NR_LIVES 3 @@ -22,7 +22,7 @@ #include <stdbool.h> #include <stdint.h> -#include "eetg.h" +#include <eetg.h> #define EI_FPS 50 diff --git a/src/main.c b/src/ei/main.c index 0507aae..9216971 100644 --- a/src/main.c +++ b/src/ei/main.c @@ -24,7 +24,8 @@ #include <time.h> #include <unistd.h> -#include "eetg.h" +#include <eetg.h> + #include "ei.h" static struct termios orig_ios; diff --git a/src/et/Makefile b/src/et/Makefile new file mode 100644 index 0000000..81f05d8 --- /dev/null +++ b/src/et/Makefile @@ -0,0 +1,5 @@ +SOURCES-et = \ + src/et/main.c \ + src/et/et.c + +$(eval $(call gen_target,et)) diff --git a/src/et/et.c b/src/et/et.c new file mode 100644 index 0000000..3393c0a --- /dev/null +++ b/src/et/et.c @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2025 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 Tetris. + */ + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <eetg.h> +#include <macros.h> + +#include "et.h" + +#define ET_SCORE_MISSILE 40 +#define ET_SCORE_ALIENS0 30 +#define ET_SCORE_ALIENS12 20 +#define ET_SCORE_ALIENS34 10 +#define ET_SCORE_UFO_BASE 100 + +#define ET_TITLE_SPRITE \ + +#define ET_HELP_SPRITE \ +" s = left \n" \ +" f = right \n" \ +" space = shoot \n" + +#define ET_START_SPRITE \ +"Press SPACE to start\n" \ +"Press X to leave \n" + +#define ET_PLAYER_SPRITE "/-^-\\\n" + +#define ET_BUNKER_SPRITE \ +" ### \n" \ +" ##### \n" \ +"#######\n" \ +"## ##\n" + +#define ET_ALIENS0_SPRITE_1 ",^,\n" +#define ET_ALIENS0_SPRITE_2 ".-.\n" +#define ET_ALIENS12_SPRITE_1 "-O_\n" +#define ET_ALIENS12_SPRITE_2 "_O-\n" +#define ET_ALIENS34_SPRITE_1 "/^\\\n" +#define ET_ALIENS34_SPRITE_2 "-^-\n" + +#define ET_STATUS_SPRITE_FORMAT "SCORE: %08d Lives: %d\n" + +#define ET_END_TITLE_SPRITE \ +" ________ __ _______ ____ _ _________ \n" \ +" / ___/ _ | / |/ / __/ / __ \\ | / / __/ _ \\\n" \ +"/ (_ / __ |/ /|_/ / _/ / /_/ / |/ / _// , _/\n" \ +"\\___/_/ |_/_/ /_/___/ \\____/|___/___/_/|_| \n" + +#define ET_TYPE_TITLE 0 +#define ET_TYPE_HELP 1 +#define ET_TYPE_START 2 +#define ET_TYPE_PLAYER 3 +#define ET_TYPE_PLAYER_MISSILE 4 +#define ET_TYPE_BUNKER 5 +#define ET_TYPE_ALIEN 6 +#define ET_TYPE_ALIEN_MISSILE 7 +#define ET_TYPE_UFO 8 +#define ET_TYPE_STATUS 9 +#define ET_TYPE_END_TITLE 10 + +static void +et_game_update_status(struct et_game *game) +{ + assert(game); + + snprintf(game->status_sprite, sizeof(game->status_sprite), + ET_STATUS_SPRITE_FORMAT, game->score, 0); +} + +static void +et_game_prepare(struct et_game *game) +{ + assert(game); + + eetg_world_clear(&game->world); + + game->state = ET_STATE_PREPARED; +} + +static void +et_game_start(struct et_game *game) +{ + assert(game); + + eetg_world_clear(&game->world); + + eetg_world_add(&game->world, &game->status, 26, 0); + + game->state = ET_STATE_PLAYING; +} + +static void +et_game_terminate(struct et_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 = ET_STATE_GAME_OVER; +} + +static void +et_game_handle_collision(struct eetg_object *object1, + struct eetg_object *object2, + int x, int y, void *arg) +{ +#if 0 + if (et_has_type(object1, object2, ET_TYPE_PLAYER_MISSILE)) { + struct eetg_object *missile, *other; + + missile = et_get_object(object1, object2, ET_TYPE_PLAYER_MISSILE); + other = (object1 == missile) ? object2 : object1; + + et_game_handle_player_missile_collision(arg, missile, other, x, y); + } else if (et_has_type(object1, object2, ET_TYPE_ALIEN)) { + struct eetg_object *alien, *other; + + alien = et_get_object(object1, object2, ET_TYPE_ALIEN); + other = (object1 == alien) ? object2 : object1; + + et_game_handle_alien_collision(arg, other, x, y); + } else if (et_has_type(object1, object2, ET_TYPE_ALIEN_MISSILE)) { + struct eetg_object *missile, *other; + + missile = et_get_object(object1, object2, ET_TYPE_ALIEN_MISSILE); + other = (object1 == missile) ? object2 : object1; + + et_game_handle_alien_missile_collision(arg, missile, other, x, y); + } +#endif +} + +static void +et_game_reset_history(struct et_game *game) +{ + assert(game); + + game->score = 0; + + et_game_update_status(game); +} + +static bool +et_game_process_intro_input(struct et_game *game, char c) +{ + assert(game); + + if (c == 'x') { + return true; + } + + if (c != ' ') { + return false; + } + + et_game_reset_history(game); + et_game_prepare(game); + + return false; +} + +static bool +et_game_process_game_input(struct et_game *game, char c) +{ + assert(game); + + if (c == 'x') { + return true; + } + +#if 0 + 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; + } + } +#endif + + return false; +} + +void +et_game_init(struct et_game *game, eetg_write_fn write_fn, void *arg) +{ + assert(game); + + game->sync_counter_reload = ET_FPS * 2; + game->sync_counter = 1; + + game->state = ET_STATE_INTRO; + + et_game_reset_history(game); + + eetg_world_init(&game->world, write_fn, arg); + eetg_world_register_collision_fn(&game->world, + et_game_handle_collision, + game); + + eetg_object_init(&game->title, ET_TYPE_TITLE, ET_TITLE_SPRITE); + eetg_object_set_color(&game->title, EETG_COLOR_BLUE); + + eetg_object_init(&game->help, ET_TYPE_HELP, ET_HELP_SPRITE); + eetg_object_set_color(&game->help, EETG_COLOR_RED); + + eetg_object_init(&game->start, ET_TYPE_START, ET_START_SPRITE); + eetg_object_set_color(&game->start, EETG_COLOR_RED); + + eetg_object_init(&game->status, ET_TYPE_STATUS, game->status_sprite); + eetg_object_set_color(&game->status, EETG_COLOR_RED); + + eetg_object_init(&game->end_title, ET_TYPE_END_TITLE, ET_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 +et_game_process(struct et_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 ET_STATE_INTRO: + case ET_STATE_GAME_OVER: + if (c >= 0) { + leave = et_game_process_intro_input(game, (char)c); + } + + break; + case ET_STATE_PREPARED: +#if 0 + et_game_start(game); +#endif + break; + case ET_STATE_PLAYING: +#if 0 + et_game_process_player_missile(game); + et_game_process_aliens(game); + et_game_process_ufo(game); + et_game_process_alien_missile(game); +#endif + + if (c >= 0) { + leave = et_game_process_game_input(game, (char)c); + } + + break; + } + + return leave; +} diff --git a/src/et/et.h b/src/et/et.h new file mode 100644 index 0000000..86bf989 --- /dev/null +++ b/src/et/et.h @@ -0,0 +1,61 @@ +/* + * 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 ET_H +#define ET_H + +#include <stdbool.h> +#include <stdint.h> + +#include <eetg.h> + +#define ET_FPS 50 + +#define ET_STATE_INTRO 0 +#define ET_STATE_PREPARED 1 +#define ET_STATE_PLAYING 2 +#define ET_STATE_GAME_OVER 3 + +struct et_bunker { + struct eetg_object object; + char sprite[33]; +}; + +struct et_alien { + struct eetg_object object; +}; + +struct et_game { + struct eetg_world world; + struct eetg_object title; + struct eetg_object help; + struct eetg_object start; + struct eetg_object status; + struct eetg_object end_title; + unsigned int score; + int8_t state; + int8_t level; + int8_t sync_counter_reload; + int8_t sync_counter; + char status_sprite[32]; +}; + +void et_game_init(struct et_game *game, eetg_write_fn write_fn, void *arg); +bool et_game_process(struct et_game *game, int8_t c); + +#endif /* ET_H */ diff --git a/src/et/main.c b/src/et/main.c new file mode 100644 index 0000000..afdeada --- /dev/null +++ b/src/et/main.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024-2025 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 "et.h" + +static struct termios orig_ios; + +static struct et_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)); + + et_game_init(&game, write_terminal, NULL); + + do { + ssize_t nr_bytes; + int8_t c; + + usleep(1000000 / ET_FPS); + + nr_bytes = read(STDIN_FILENO, &c, 1); + + if (nr_bytes == -1) { + if (errno == EAGAIN) { + c = -1; + } else { + break; + } + } + + leave = et_game_process(&game, c); + } while (!leave); + + return EXIT_SUCCESS; +} |