From 60ae11de104b4f62ae7b0e29c0658d8e0a5b029b Mon Sep 17 00:00:00 2001 From: Quinn Date: Sat, 22 Mar 2025 15:29:42 +0100 Subject: [PATCH] synchronise with the template - use the version of audio management which doesn't crash the application if something goes wrong - include colour32 - update error management to be more reflective of the one defined in the template - modify the renderer functions to be more reflective of the template - modify the game functions to be more reflective of the template (& use a more failproof method of initializing) - add gametime - remove set_gamestatus logic and just use a run boolean in gamedata - remove emscripten preprocessors as those haven't really been used --- src/error.c | 19 +-- src/error.h | 12 +- src/game/game.c | 60 +++++---- src/game/game.h | 31 +++-- src/game/gametime.h | 39 ++++++ src/game/tetromino/placing.c | 7 +- src/game/tetromino/placing.h | 4 +- src/game/tetromino/shapes.c | 18 +-- src/game/tetromino/shapes.h | 2 +- src/main.c | 71 +++++------ src/util/attributes.h | 35 ++++++ src/window/audio.c | 197 ++++++++++++++++++------------ src/window/audio.h | 39 +++--- src/window/colour/colour32.h | 42 +++++++ src/window/{ => colour}/colour8.h | 38 +++--- src/window/renderer.c | 52 ++++---- src/window/renderer.h | 15 +-- 17 files changed, 416 insertions(+), 265 deletions(-) create mode 100644 src/game/gametime.h create mode 100644 src/util/attributes.h create mode 100644 src/window/colour/colour32.h rename src/window/{ => colour}/colour8.h (55%) diff --git a/src/error.c b/src/error.c index 75af9ff..0bdafc2 100644 --- a/src/error.c +++ b/src/error.c @@ -16,16 +16,6 @@ (void)vsnprintf(buf, PRINT_BUFFER_SIZE, fmt, args); \ va_end(args); -static gamestatus status = STATUS_RUNNING; - -void set_gamestatus(gamestatus nstatus) { - status = nstatus; -} - -gamestatus get_gamestatus(void) { - return status; -} - void debug(char const* fmt, ...) { char const* env = getenv("DEBUG"); if (env == NULL || *env != '1') @@ -52,20 +42,19 @@ void warn(char const* fmt, ...) { void error(char const* fmt, ...) { char buf[PRINT_BUFFER_SIZE] = {0}; write_args(buf, fmt); - (void)fprintf(stderr, "\033[mE: %s\033[0m", buf); + (void)fprintf(stderr, "\033[91mE: %s\033[0m", buf); } -noreturn void fatal(gamestatus error_code, char const* fmt, ...) { +noreturn void fatal(gamestatus error_code, char const* fname, uint32_t ln, char const* fmt, ...) { char buf1[PRINT_BUFFER_SIZE] = {0}; write_args(buf1, fmt); char buf2[PRINT_BUFFER_SIZE * 2] = {0}; - sprintf(buf2, "%s\nexitcode: %u", buf1, error_code); + sprintf(buf2, "%s\n at %s:%u (exitcode: %u)", buf1, fname, ln, error_code); (void)fprintf(stderr, "\033[101mF: %s\033[0m\n", buf2); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "something went wrong! :O", buf2, NULL); // set status, but exit immediately, as code is not allowed to execute beyond this point - set_gamestatus(error_code); - exit(status); + exit(error_code); } diff --git a/src/error.h b/src/error.h index ede9321..8f7c3c0 100644 --- a/src/error.h +++ b/src/error.h @@ -30,12 +30,10 @@ enum gamestatus { }; typedef int8_t gamestatus; -void set_gamestatus(gamestatus); // sets the current status of the game -gamestatus get_gamestatus(void); // gets the current status of the game -void debug(char const*, ...); // prints a debug message to stdout if the DEBUG environment variable is set, otherwise the call is ignored. -void info(char const*, ...); // prints an info message to stdout -void warn(char const*, ...); // prints a warning message to stderr -void error(char const*, ...); // prints an warning message to stderr +__attribute__((format(printf, 1, 2))) void debug(char const*, ...); // prints a debug message to stdout if the DEBUG environment variable is set, otherwise the call is ignored. +__attribute__((format(printf, 1, 2))) void info(char const*, ...); // prints an info message to stdout +__attribute__((format(printf, 1, 2))) void warn(char const*, ...); // prints a warning message to stderr +__attribute__((format(printf, 1, 2))) void error(char const*, ...); // prints an warning message to stderr // prints an error message to stderr before exiting -noreturn void fatal(gamestatus, char const* fmt, ...); +__attribute__((format(printf, 4, 5))) noreturn void fatal(gamestatus, char const* file_name, uint32_t line, char const* fmt, ...); diff --git a/src/game/game.c b/src/game/game.c index 1e142fc..55c98b4 100644 --- a/src/game/game.c +++ b/src/game/game.c @@ -1,18 +1,18 @@ #include "game.h" +#include +#include #include +#include #include #include #include #include #include -#include "../error.h" #include "../window/audio.h" -#include "../window/colour8.h" +#include "../window/colour/colour8.h" #include "./tetromino/shapes.h" -#include "SDL_audio.h" -#include "SDL_timer.h" #include "tetromino/placing.h" // shuffle the array using a Fisher–Yates shuffle @@ -25,7 +25,7 @@ static inline void shuffle(uint8_t const size, shape_id* const elmnts) { } } -void next_shape(game_data* const dat) { +void next_shape(gamedata* const dat) { dat->curr_idx++; // increase the current shape index dat->sel_x = COLUMNS / 2 - SHAPE_WIDTH / 2; // move the shape position to the centre dat->sel_y = 0; @@ -42,19 +42,34 @@ void next_shape(game_data* const dat) { dat->nxt[TETROMINO_COUNT - 1] = cache; } -void game_init(game_data* const dat) { - // zero-initialize the game data - *dat = (game_data){0}; - - // allocate size for each row - for (int8_t i = 0; i < ROWS; i++) { - dat->rows[i] = calloc(COLUMNS, sizeof(colour8)); - // game_data->rows[i][0] = (colour8){(uint8_t)((((i + 1) ^ ((i + 1) >> 3)) * 0x27) & 0xFF)}; // for debugging rows - } - +void game_init(gamedata* const dat, gametime* gt) { // set a random seed using the system clock srand(time(NULL)); + // initialize audio device + audiodevice* ad = audio_device_init(32000, AUDIO_S16, 1, 4096); + + *dat = (gamedata){ + {0}, // row + gt, // time + ad, // audio_device + audio_wav_load(ad, "korobeiniki.wav"), // music + 0, // timer_music + 0, // timer_update + 0, // timer_input + 0, // score + {0}, // nxt + 0, // curr_idx + 0, // sel_x + 0, // sel_y + true, // run + }; + + // initialize the rows within the game data + for (int8_t i = 0; i < ROWS; i++) { + dat->rows[i] = calloc(COLUMNS, sizeof(colour8)); // TODO: add memory safety check + } + // set the shape data in each slot to it's corrsponding ID for (shape_id i = 0; i < TETROMINO_COUNT; i++) dat->nxt[i] = i; @@ -62,16 +77,14 @@ void game_init(game_data* const dat) { dat->curr_idx = -1; // set the current index to the max so it becomes zero after increasement next_shape(dat); // select the next shape (shuffle should not be triggered) shuffle(TETROMINO_COUNT, dat->nxt); // manually trigger a shuffle - - // initialize audio - dat->audio_device = audio_device_init(32000, AUDIO_S16, 1, 4096); - dat->music = audio_wav_load(dat->audio_device, "korobeiniki.wav"); } // called every time the game's state is updated -void game_update(game_data* const dat, uint8_t const* const keys) { +void game_update(gamedata* const dat) { + uint8_t const* keys = SDL_GetKeyboardState(NULL); + if (keys[SDL_SCANCODE_ESCAPE]) - set_gamestatus(STATUS_SUCCESS); + dat->run = false; input_data move = MOVE_NONE; // contains the move data uint32_t ctime = SDL_GetTicks(); @@ -107,7 +120,7 @@ void game_update(game_data* const dat, uint8_t const* const keys) { place_update(dat, move); } -void game_free(game_data* const dat) { +void game_free(gamedata* const dat) { audio_wav_unload(&dat->music); audio_device_free(dat->audio_device); @@ -116,4 +129,7 @@ void game_free(game_data* const dat) { free(dat->rows[i]); dat->rows[i] = NULL; } + + // zero-out the rest of the data + *dat = (gamedata){0}; } diff --git a/src/game/game.h b/src/game/game.h index 71dbf7e..b0874d3 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -1,10 +1,19 @@ #pragma once - +#include +#include +#include #include #include "../window/audio.h" -#include "../window/colour8.h" -#include "tetromino/shapes.h" +#include "../window/colour/colour8.h" +#include "./tetromino/shapes.h" +#include "gametime.h" + +// constants for pi(π) and tau(τ) +#define PI (M_PI) // π constant +#define TAU (M_PI * 2.0) // τ constant +#define PIf (M_PIf) // π constant as a 32-bit floating point +#define TAUf (M_PIf * 2.0F) // τ constant as a 32-bit floating point // stores the data used in the game #define COLUMNS ((int8_t)10) @@ -15,8 +24,9 @@ typedef colour8* row; typedef struct { row rows[ROWS]; - audio_device* audio_device; - audio_data music; + gametime* time; + audiodevice* audio_device; + audiodata music; uint32_t timer_music; uint32_t timer_update; uint32_t timer_input; @@ -25,9 +35,10 @@ typedef struct { uint8_t curr_idx; // current shape index int8_t sel_x; // selected shape x position int8_t sel_y; // selected shape y position -} game_data; + bool run; +} gamedata; -void next_shape(game_data* dat); -void game_init(game_data* dat); // initializes the game -void game_update(game_data* dat, uint8_t const* keys); // updates the game's state -void game_free(game_data* dat); // free all data stored with the game +void next_shape(gamedata*); // initializes everything needed to start the game; outputs to gamedata +void game_init(gamedata*, gametime*); // initializes the game +void game_update(gamedata*); // causes an update to occur within the game +void game_free(gamedata*); // frees the resources associated with the game diff --git a/src/game/gametime.h b/src/game/gametime.h new file mode 100644 index 0000000..4605256 --- /dev/null +++ b/src/game/gametime.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "../util/attributes.h" + +typedef struct { + struct timespec ts; // stores the time at the current update + double sec; // stores the current time in seconds + float scale; // multiplier for the time calculation, default value is 1.0 + float delta; // the time that it took between updates +} gametime; + +// initializes the gametime struct +atrb_const static inline gametime gametime_new(void) { + struct timespec ts; + timespec_get(&ts, TIME_UTC); + + return (gametime){ + ts, + 0.0, + 1.0F, + 0.0F, + }; +} + +// updates the internal variables +static inline void gametime_update(gametime* gt) { + struct timespec ts; + timespec_get(&ts, TIME_UTC); + gt->sec = (double)ts.tv_nsec * 1e-9; // calculate the current time in seconds + gt->delta = ((double)(ts.tv_nsec - gt->ts.tv_nsec) * 1e-9) * gt->scale; // calculate how much time has passed between this and last frame + gt->ts = ts; // update the game's timespec +} + +// gets how many times the game updates per second +atrb_const static inline float gametime_get_ups(gametime* gt) { + return 1.0F / gt->delta; +} diff --git a/src/game/tetromino/placing.c b/src/game/tetromino/placing.c index ccd7c81..ab83bb4 100644 --- a/src/game/tetromino/placing.c +++ b/src/game/tetromino/placing.c @@ -2,8 +2,7 @@ #include -#include "../../error.h" -#include "../../window/colour8.h" +#include "../../window/colour/colour8.h" #include "../game.h" #include "shapes.h" @@ -101,7 +100,7 @@ static inline shape_id rotate_id(shape_id const id, int const dir) { return (id + dir) & 31; } -void place_update(game_data* const game_data, input_data const move) { +void place_update(gamedata* const game_data, input_data const move) { // store the current index and ID, only changes when placed (which yields no movement) and rotation (which occurs last) uint8_t const curr_idx = game_data->curr_idx; shape_id const curr_id = game_data->nxt[curr_idx]; @@ -116,7 +115,7 @@ void place_update(game_data* const game_data, input_data const move) { next_shape(game_data); if (shape_intersects(game_data->rows, game_data->curr_idx, game_data->sel_x, game_data->sel_y)) - set_gamestatus(STATUS_SUCCESS); + game_data->run = false; return; } diff --git a/src/game/tetromino/placing.h b/src/game/tetromino/placing.h index 23be325..e3ef5cc 100644 --- a/src/game/tetromino/placing.h +++ b/src/game/tetromino/placing.h @@ -15,8 +15,8 @@ enum { MOVE_ROTRIGHT = 16, }; -void place_update(game_data* game_data, input_data move); +void place_update(gamedata* game_data, input_data move); #ifdef DEBUG -void dbg_set_all(game_data* game_data); +void dbg_set_all(gamedata* game_data); #endif diff --git a/src/game/tetromino/shapes.c b/src/game/tetromino/shapes.c index c4ba88f..c6467be 100644 --- a/src/game/tetromino/shapes.c +++ b/src/game/tetromino/shapes.c @@ -1,6 +1,6 @@ #include "shapes.h" -#include "../../window/colour8.h" +#include "../../window/colour/colour8.h" /* 0 1 2 3 */ #define SHAPE_O ((shape)0x0660) // 0000 0110 0110 0000 the O tetromino with no rotation @@ -55,13 +55,13 @@ shape shape_from_id(shape_id const id) { colour8 colour_from_id(shape_id const id) { switch (id & 7) { - case TETROMINO_O: return COLOUR_YELLOW; - case TETROMINO_I: return COLOUR_CYAN; - case TETROMINO_S: return COLOUR_RED; - case TETROMINO_Z: return COLOUR_GREEN; - case TETROMINO_T: return COLOUR_MAGENTA; - case TETROMINO_L: return COLOUR_ORANGE; - case TETROMINO_J: return COLOUR_BLUE; - default: return COLOUR_BLACK; + case TETROMINO_O: return COLOUR8_YELLOW; + case TETROMINO_I: return COLOUR8_CYAN; + case TETROMINO_S: return COLOUR8_RED; + case TETROMINO_Z: return COLOUR8_GREEN; + case TETROMINO_T: return COLOUR8_MAGENTA; + case TETROMINO_L: return COLOUR8_ORANGE; + case TETROMINO_J: return COLOUR8_BLUE; + default: return COLOUR8_BLACK; } } diff --git a/src/game/tetromino/shapes.h b/src/game/tetromino/shapes.h index 41c9f4b..132e582 100644 --- a/src/game/tetromino/shapes.h +++ b/src/game/tetromino/shapes.h @@ -2,7 +2,7 @@ #include #include -#include "../../window/colour8.h" +#include "../../window/colour/colour8.h" typedef uint16_t shape; typedef uint8_t shape_row; diff --git a/src/main.c b/src/main.c index a12dffe..e0f204c 100644 --- a/src/main.c +++ b/src/main.c @@ -1,44 +1,34 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include -#include "SDL_ttf.h" #include "error.h" #include "game/game.h" +#include "game/gametime.h" #include "window/renderer.h" -#ifdef __EMSCRIPTEN__ // for web builds -# include -#endif +// initialized in init(), reading beforehand is undefined behaviour +static gametime gt; +static gamedata gdat; +static renderdata rdat; - -bool playing = true; - -render_data render_dat = {0}; -game_data game_dat = {0}; - - -// handles game application initialisation +// initialize the game static void init(void) { // initialize SDL if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) - fatal(ERROR_SDL_INIT, "SDL could not initialize! SDL Error: %s", SDL_GetError()); + fatal(ERROR_SDL_INIT, __FILE_NAME__, __LINE__, "SDL could not initialize! SDL Error: %s", SDL_GetError()); if (TTF_Init() != 0) - fatal(ERROR_SDL_FONT_INIT, "the TTF module of SDL could not initialize! TTF Error: %s", TTF_GetError()); + fatal(ERROR_SDL_FONT_INIT, __FILE_NAME__, __LINE__, "the TTF module of SDL could not initialize! TTF Error: %s", TTF_GetError()); - // initialize units - game_init(&game_dat); - renderer_init(&render_dat, &game_dat); + // initialize other game components + gt = gametime_new(); + game_init(&gdat, >); + render_init(&rdat, &gdat); } -// handles game application updating +// perform the updates to the game static void update(void) { // update the input { @@ -46,36 +36,31 @@ static void update(void) { while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_QUIT: - set_gamestatus(STATUS_SUCCESS); + gdat.run = false; break; } } } - // preform updates - game_update(&game_dat, SDL_GetKeyboardState(NULL)); - renderer_update(&render_dat); + // perform updates + gametime_update(>); + game_update(&gdat); + render_update(&rdat); } -// handles game application quitting -void stop(void) { - playing = false; -} - -// entry point of the application -int main(int const argc, char const* const* const argv) { - (void)argc; - (void)argv; +// entry-point of the application +int32_t main(int32_t argc, char** argv) { + (void)argc, (void)argv; init(); + debug("successfully initialized!"); - while (get_gamestatus() == -1) { + while (gdat.run == true) update(); - } - // cleanup of resources - game_free(&game_dat); + debug("done! starting to free resources..."); + game_free(&gdat); + render_free(&rdat); SDL_Quit(); - return 0; } diff --git a/src/util/attributes.h b/src/util/attributes.h new file mode 100644 index 0000000..223f4fe --- /dev/null +++ b/src/util/attributes.h @@ -0,0 +1,35 @@ +#pragma once + + +// default attribute definitions (empty) +#define atrb_deprecated +#define atrb_unused +#define atrb_pure +#define atrb_const + +// define the attributes where possible +#if defined(__GNUC__) || defined(__clang__) + +# if __has_attribute(deprecated) +# undef atrb_deprecated +# define atrb_deprecated __attribute__((deprecated)) +# endif + +# if __has_attribute(unused) +# undef atrb_unused +# define atrb_unused __attribute__((unused)) +# endif + +# if __has_attribute(pure) +# undef atrb_pure +# define atrb_pure __attribute__((pure)) +# endif + +# if __has_attribute(const) +# undef atrb_const +# define atrb_const __attribute__((const)) +# endif +#elif defined(_MSC_VER) +# undef atrb_depatrb_deprecated +# define atrb_deprecated __declspec(deprecated) +#endif diff --git a/src/window/audio.c b/src/window/audio.c index ec8c689..e5710df 100644 --- a/src/window/audio.c +++ b/src/window/audio.c @@ -7,112 +7,117 @@ #include #include +#ifdef __unix__ +# include +# define fexists(fname) (access(fname, F_OK) == 0) +#elif _WIN32 +# include +# define fexists(fname) (_access(fname, 0) == 0) +#else +# error platform not supported! +#endif + #include "../error.h" -// -// audio mixing -// -// audio callback from SDL_AudioSpec; called when the audio device needs more data -static void audio_mixer(void* const userdata, uint8_t* const stream, int32_t const len) { - memset(stream, 0, len); // clear the playing audio - audio_device* const dev = userdata; // get the callback data +static void audiomixer(void* const userdata, uint8_t* const stream, int32_t const len) { + memset(stream, 0, len); // clear the playing audio + audiodevice* const dev = userdata; // retreive the callback data - audio_player* prev = NULL; - audio_player* curr = dev->audio_dat; + // return if dev is null, since it can fail to initialize + if (dev == NULL) return; + + struct audioplayer* prev = NULL; + struct audioplayer* curr = dev->audio_players; while (curr != NULL) { - // if the current length, remove self from the list + // if the current audio fragment has reached the end of their data if (curr->len == 0) { - audio_player* ncurr = curr->nxt; // store the next player as the new current player + struct audioplayer* ncurr = curr->nxt; - // set the current player to the new current player & free the current player + // free the memory allocated to it and assign the next to to the currently playing free(curr); - curr = ncurr; // ncurr can be NULL, this is fine + curr = ncurr; - // update the previous pointer accordingly + // write to the audio device if prev hasn't been set yet if (prev == NULL) - dev->audio_dat = ncurr; + dev->audio_players = curr; else - prev->nxt = ncurr; + prev->nxt = curr; - // continue code execution + // continue so if curr is now NULL, the loop stops continue; } - // get the length of which we shall be mixing - uint32_t const mix_length = SDL_min(curr->len, (uint32_t)len); + // calculate how much of the current audio player we should mix into the stream + uint32_t const mixlen = SDL_min(curr->len, (uint32_t)len); - // mix the audio with the stream - SDL_MixAudioFormat(stream, curr->buf, dev->fmt, mix_length, SDL_MIX_MAXVOLUME); - curr->buf += mix_length; // move the pointer up a a bit - curr->len -= mix_length; // move up the mixed amount + // mix the current buffer into the stream, and update the audio player values accordingly + SDL_MixAudioFormat(stream, curr->buf, dev->fmt, mixlen, SDL_MIX_MAXVOLUME); + curr->buf += mixlen; + curr->len -= mixlen; - // increment across the list + // increment the current node prev = curr; curr = curr->nxt; } } -// converts the audio to the format of the audio device, reallocates wav_buf to a new size outputted to wav_len -static void convert_audio(audio_device const* const dev, SDL_AudioSpec const wav_spec, uint8_t** const wav_buf, uint32_t* const wav_len) { - // build the audio converter with the audio given - SDL_AudioCVT cvt = {0}; - SDL_BuildAudioCVT(&cvt, wav_spec.format, wav_spec.channels, wav_spec.freq, dev->fmt, dev->channels, dev->freq); - cvt.len = (*wav_len) * wav_spec.channels; // calculate the size of the source data in bytes by multiplying the length by the amount of channels (warn: uint32_t -> int32_t) - cvt.buf = realloc(*wav_buf, cvt.len * cvt.len_mult); // grow the inputted buffer for the conversion +static int8_t audio_cvt(audiodevice const* dev, SDL_AudioSpec const* spec, uint8_t** bufptr, uint32_t* len) { + // init the converter + SDL_AudioCVT cvt; + SDL_BuildAudioCVT(&cvt, spec->format, spec->channels, spec->freq, dev->fmt, dev->channels, dev->freq); + cvt.len = (*len) * spec->channels; // calculate the size of the source data in bytes by multiplying the length by the amount of channels (warn: uint32_t -> int32_t) + cvt.buf = realloc(*bufptr, cvt.len * cvt.len_mult); // grow the inputted buffer for the conversion - // performs the conversion - if (SDL_ConvertAudio(&cvt) != 0) - fatal(ERROR_SDL_AUDIO_INIT, "something went wrong when converting an audio buffer! SDL Error: %s", SDL_GetError()); + // ensure the conversion buffer reallocation goes correctly + if (cvt.buf == NULL) { + error("%s:%u something went wrong whilst growing the audio buffer whilst converting!", __FILE_NAME__, __LINE__); + free(*bufptr); + return 1; + } - // set the length to the new length - *wav_len = cvt.len_cvt; + // converts the audio to the new format + if (SDL_ConvertAudio(&cvt)) { + error("something went wrong when loading/converting an audio buffer! SDL Error: %s", SDL_GetError()); + free(cvt.buf); + return 1; + } - // reallocate the conversion buffer to match the new size - *wav_buf = realloc(cvt.buf, cvt.len_cvt); - if (*wav_buf == NULL) - fatal(ERROR_SDL_AUDIO, "null value when reallocating the audio buffer"); + *len = cvt.len; + + *bufptr = realloc(cvt.buf, cvt.len_cvt); + if (*bufptr == NULL) { + warn("%s:%u something went wrong whilst shrinking the audio buffer whilst converting!", __FILE_NAME__, __LINE__); + *bufptr = cvt.buf; // use the conversion buffer, as this one will be valid if realloc fails + } + + return 0; } +audiodevice* audio_device_init(int32_t freq, SDL_AudioFormat fmt, uint8_t channels, uint16_t samples) { + audiodevice* dev = malloc(sizeof(audiodevice)); -// -// audio / audio device management -// -// loads a WAV file and returns the relevant information -audio_data audio_wav_load(audio_device const* const dev, char const* const fpath) { - SDL_AudioSpec wav_spec = {0}; - audio_data audio = {0}; - - SDL_LoadWAV(fpath, &wav_spec, &audio.buf, &audio.len); - convert_audio(dev, wav_spec, &audio.buf, &audio.len); - - // calculate the amount of seconds that the audio fragment has - audio.ms = 1000 * (((audio.len) / (SDL_AUDIO_BITSIZE(dev->fmt) / 8)) / wav_spec.channels / dev->freq); - - return audio; -} - -// initializes the audio device -audio_device* audio_device_init(int32_t const freq, SDL_AudioFormat const fmt, uint8_t const channels, uint16_t const samples) { - // allocate memory for the audio device - audio_device* const dev = malloc(sizeof(audio_device)); + if (dev == NULL) { + error("%s:%u null pointer when allocating memory for the audio device!", __FILE_NAME__, __LINE__); + return NULL; + } // define the audio specification SDL_AudioSpec spec = {freq, fmt, channels, 0, samples, 0, 0, NULL, NULL}; - spec.callback = audio_mixer; + spec.callback = audiomixer; spec.userdata = dev; // create the audio device - *dev = (audio_device){ - NULL, // allocate memory on the heap for the playing audio array + *dev = (audiodevice){ + NULL, SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0), freq, fmt, channels, }; - // if the audio device isn't set, cause an error if (dev->id < 1) { - fatal(ERROR_SDL_AUDIO_INIT, "AudioDivice failed to open! SDL Error: %s", SDL_GetError()); + error("%s:%u audio device failed to open! SDL Error: %s", __FILE_NAME__, __LINE__, SDL_GetError()); + free(dev); return NULL; } @@ -121,28 +126,64 @@ audio_device* audio_device_init(int32_t const freq, SDL_AudioFormat const fmt, u return dev; } -// plays the audio -void audio_play(audio_device* const dev, audio_data const* audio) { +void audio_play(audiodevice* dev, audiodata const* audio) { + if (dev == NULL) return; // dev might fail to initialize + if (audio->len == 0) return; // audio might fail to initialize + // create an audio player - audio_player* player = malloc(sizeof(audio_player)); - *player = (audio_player){ - dev->audio_dat, // set nxt to the first item in dev (can be NULL, this is fine) + struct audioplayer* player = malloc(sizeof(struct audioplayer)); + *player = (struct audioplayer){ + dev->audio_players, // set nxt to the first item in dev (can be NULL, this is fine) audio->buf, audio->len, }; // assign ourselves to the first item - dev->audio_dat = player; + dev->audio_players = player; } -// frees the audio device -void audio_device_free(audio_device* const dev) { +void audio_device_free(audiodevice* dev) { + if (dev == NULL) return; SDL_CloseAudioDevice(dev->id); - free(dev->audio_dat); + + struct audioplayer* curr = dev->audio_players; + + // free all audio players + while (curr != NULL) { + dev->audio_players = curr->nxt; // use audio_players in dev as a cache + free(curr); + curr = dev->audio_players; + } + + // free the audio device itself free(dev); } -// frees the buffer of the audio data -void audio_wav_unload(audio_data* dat) { - free(dat->buf); +audiodata audio_wav_load(audiodevice const* dev, char const* fpath) { + if (dev == NULL) return (audiodata){0}; + SDL_AudioSpec spec; + audiodata audio; + + debug("loading audio file '%s'...", fpath); + + if (!fexists(fpath)) { + error("%s:%u couldn't find audio file '%s'!", __FILE_NAME__, __LINE__, fpath); + return (audiodata){0}; + } + + // load and parse the audio to the correct format + SDL_LoadWAV(fpath, &spec, &audio.buf, &audio.len); + if (audio_cvt(dev, &spec, &audio.buf, &audio.len)) { + return (audiodata){0}; + } + + // calculate the time in miliseconds of the audio fragment + audio.ms = 1000 * (((audio.len) / (SDL_AUDIO_BITSIZE(dev->fmt) / 8)) / spec.channels / dev->freq); + + return audio; +} + +void audio_wav_unload(audiodata* audio) { + free(audio->buf); + *audio = (audiodata){0}; // zero out all audio data } diff --git a/src/window/audio.h b/src/window/audio.h index 99f223f..4453bad 100644 --- a/src/window/audio.h +++ b/src/window/audio.h @@ -3,30 +3,33 @@ #include #include -typedef struct { - uint8_t* buf; // the audio buffer +struct audiodata { + uint8_t* buf; // pointer to the audio buffer uint32_t len; // length in bytes of the audio buffer - uint32_t ms; // length in seconds of the audio buffer -} audio_data; + uint32_t ms; // length in miliseconds of the audio buffer +}; -typedef struct audio_player { - struct audio_player* nxt; // pointer to the next audio fragment (can be NULL) - uint8_t* buf; // pointer to the current - uint32_t len; // length remaining of the audio buffer -} audio_player; +// contains the data of the audio fragments to be played +struct audioplayer { + struct audioplayer* nxt; // pointer to the next audioplayer (may be null) + uint8_t* buf; // pointer to the current item in the buffer to be played + uint32_t len; // the length in bytes that the buffer has remaining +}; -typedef struct { - audio_player* audio_dat; // linked list of audio players - SDL_AudioDeviceID id; // the audio device id +struct audiodevice { + struct audioplayer* audio_players; + SDL_AudioDeviceID id; int32_t freq; SDL_AudioFormat fmt; uint8_t channels; -} audio_device; +}; +typedef struct audiodata audiodata; +typedef struct audiodevice audiodevice; -audio_data audio_wav_load(audio_device const* audio_device, char const* file_path); -audio_device* audio_device_init(int freq, SDL_AudioFormat format, uint8_t channels, uint16_t samples); -void audio_play(audio_device* audio_device, audio_data const* audio); +audiodevice* audio_device_init(int32_t, SDL_AudioFormat, uint8_t, uint16_t); +void audio_play(audiodevice*, audiodata const*); +void audio_device_free(audiodevice*); -void audio_device_free(audio_device* dev); -void audio_wav_unload(audio_data* dat); +audiodata audio_wav_load(audiodevice const*, char const*); +void audio_wav_unload(audiodata*); diff --git a/src/window/colour/colour32.h b/src/window/colour/colour32.h new file mode 100644 index 0000000..dbe25fe --- /dev/null +++ b/src/window/colour/colour32.h @@ -0,0 +1,42 @@ +#pragma once +#include + +#include "SDL_render.h" + +// stores colour in a rgba format, each channel being a 8 bits wide. +typedef union { + uint32_t packed; + struct { + uint8_t a; + uint8_t b; + uint8_t g; + uint8_t r; + }; +} colour32; + +#define COLOUR32_BLACK ((colour32){0x000000FF}) +#define COLOUR32_RED ((colour32){0xFF0000FF}) +#define COLOUR32_YELLOW ((colour32){0xFFFF00FF}) +#define COLOUR32_ORANGE ((colour32){0xFF6D00FF}) +#define COLOUR32_GREEN ((colour32){0x00FF00FF}) +#define COLOUR32_CYAN ((colour32){0x00FFFFFF}) +#define COLOUR32_BLUE ((colour32){0x0000FFFF}) +#define COLOUR32_MAGENTA ((colour32){0xFF00FFFF}) +#define COLOUR32_WHITE ((colour32){0xFFFFFFFF}) + +// sets the render colour to a colour32 value +static inline void set_colour32(SDL_Renderer* const renderer, colour32 const c) { + (void)SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a); +} + +// american macros: +#define color32 colour32 +#define COLOR32_BLACK COLOUR32_BLACK +#define COLOR32_RED COLOUR32_RED +#define COLOR32_YELLOW COLOUR32_YELLOW +#define COLOR32_ORANGE COLOUR32_ORANGE +#define COLOR32_GREEN COLOUR32_GREEN +#define COLOR32_CYAN COLOUR32_CYAN +#define COLOR32_BLUE COLOUR32_BLUE +#define COLOR32_MAGENTA COLOUR32_MAGENTA +#define COLOR32_WHITE COLOUR32_WHITE diff --git a/src/window/colour8.h b/src/window/colour/colour8.h similarity index 55% rename from src/window/colour8.h rename to src/window/colour/colour8.h index df82bbf..b3a8678 100644 --- a/src/window/colour8.h +++ b/src/window/colour/colour8.h @@ -7,15 +7,15 @@ typedef uint8_t colour8; /* rrrg ggbb */ -#define COLOUR_BLACK ((colour8)0x00) // 0000 0000 -#define COLOUR_RED ((colour8)0xE0) // 1110 0000 -#define COLOUR_YELLOW ((colour8)0xFC) // 1111 1100 -#define COLOUR_ORANGE ((colour8)0xEC) // 1111 1100 -#define COLOUR_GREEN ((colour8)0x1C) // 0001 1100 -#define COLOUR_CYAN ((colour8)0x1F) // 0001 1111 -#define COLOUR_BLUE ((colour8)0x03) // 0000 0011 -#define COLOUR_MAGENTA ((colour8)0xE3) // 1110 0011 -#define COLOUR_WHITE ((colour8)0xFF) // 1111 1111 +#define COLOUR8_BLACK ((colour8)0x00) // 0000 0000 +#define COLOUR8_RED ((colour8)0xE0) // 1110 0000 +#define COLOUR8_YELLOW ((colour8)0xFC) // 1111 1100 +#define COLOUR8_ORANGE ((colour8)0xEC) // 1111 1100 +#define COLOUR8_GREEN ((colour8)0x1C) // 0001 1100 +#define COLOUR8_CYAN ((colour8)0x1F) // 0001 1111 +#define COLOUR8_BLUE ((colour8)0x03) // 0000 0011 +#define COLOUR8_MAGENTA ((colour8)0xE3) // 1110 0011 +#define COLOUR8_WHITE ((colour8)0xFF) // 1111 1111 // gets the red channel in 32 bit colour space static inline uint8_t colour8_red32(colour8 const colour) { @@ -37,17 +37,17 @@ static inline void set_colour8(SDL_Renderer* const renderer, colour8 const c) { (void)SDL_SetRenderDrawColor(renderer, colour8_red32(c), colour8_green32(c), colour8_blue32(c), 0xFF); } -// American macros: +// american macros: #define color8 colour8 #define color8_red32 colour8_red32 #define color8_green32 colour8_green32 #define color8_blue32 colour8_blue32 -#define COLOR_BLACK COLOUR_BLACK -#define COLOR_RED COLOUR_RED -#define COLOR_YELLOW COLOUR_YELLOW -#define COLOR_ORANGE COLOUR_ORANGE -#define COLOR_GREEN COLOUR_GREEN -#define COLOR_CYAN COLOUR_CYAN -#define COLOR_BLUE COLOUR_BLUE -#define COLOR_MAGENTA COLOUR_MAGENTA -#define COLOR_WHITE COLOUR_WHITE +#define COLOR8_BLACK COLOUR8_BLACK +#define COLOR8_RED COLOUR8_RED +#define COLOR8_YELLOW COLOUR8_YELLOW +#define COLOR8_ORANGE COLOUR8_ORANGE +#define COLOR8_GREEN COLOUR8_GREEN +#define COLOR8_CYAN COLOUR8_CYAN +#define COLOR8_BLUE COLOUR8_BLUE +#define COLOR8_MAGENTA COLOUR8_MAGENTA +#define COLOR8_WHITE COLOUR8_WHITE diff --git a/src/window/renderer.c b/src/window/renderer.c index 85b753c..fabdecc 100644 --- a/src/window/renderer.c +++ b/src/window/renderer.c @@ -1,37 +1,38 @@ -// initializes the window and renderer #include "renderer.h" #include #include #include #include +#include #include #include #include #include #include +#include +#include #include "../error.h" #include "../game/game.h" #include "../game/tetromino/shapes.h" -#include "SDL_surface.h" -#include "colour8.h" -#include "renderer.h" +#include "colour/colour32.h" +#include "colour/colour8.h" -#define COLOUR_SCORE COLOUR_YELLOW +#define COLOUR_SCORE COLOUR32_YELLOW -void renderer_init(render_data* const render_dat, game_data const* const game_dat) { +void render_init(renderdata* const render_dat, gamedata const* const game_dat) { SDL_Window* const window = SDL_CreateWindow("tetris clone", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); - if (window == NULL) fatal(ERROR_SDL_RENDERING_INIT, "Window failed to be created! SDL Error: %s", SDL_GetError()); + if (window == NULL) fatal(ERROR_SDL_RENDERING_INIT, __FILE_NAME__, __LINE__, "Window failed to be created! SDL Error: %s", SDL_GetError()); SDL_Renderer* const renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); - if (renderer == NULL) fatal(ERROR_SDL_RENDERING_INIT, "Renderer failed to be created! SDL Error: %s", SDL_GetError()); + if (renderer == NULL) fatal(ERROR_SDL_RENDERING_INIT, __FILE_NAME__, __LINE__, "Renderer failed to be created! SDL Error: %s", SDL_GetError()); TTF_Font* const font = TTF_OpenFont("pixeldroid_botic-regular.ttf", PX_DENS); - if (font == NULL) fatal(ERROR_SDL_FONT_INIT, "Failed to open font! TTF Error: %s", TTF_GetError()); + if (font == NULL) fatal(ERROR_SDL_FONT_INIT, __FILE_NAME__, __LINE__, "Failed to open font! TTF Error: %s", TTF_GetError()); // initialize the render data - *render_dat = (render_data){ + *render_dat = (renderdata){ game_dat, window, renderer, @@ -48,7 +49,7 @@ static inline int32_t get_row_pos(uint8_t row) { return row * BLOCK_HEIGHT + 1 + TET_PADDING; } -static void draw_score_text(render_data const* dat) { +static void draw_score_text(renderdata const* dat) { struct render_cache* const cache = dat->cache; uint16_t const score = dat->game_dat->score; @@ -60,7 +61,7 @@ static void draw_score_text(render_data const* dat) { if (!score) sprintf(score_text, "0"); else sprintf(score_text, "%hu0", score); - SDL_Surface* const txt_surface = TTF_RenderText_Solid(font, score_text, (SDL_Colour){colour8_red32(COLOUR_SCORE), colour8_green32(COLOUR_SCORE), colour8_blue32(COLOUR_SCORE), 0xFF}); + SDL_Surface* const txt_surface = TTF_RenderText_Solid(font, score_text, (SDL_Colour){COLOUR_SCORE.r, COLOUR_SCORE.g, COLOUR_SCORE.b, COLOUR_SCORE.a}); SDL_Texture* const txt_texture = SDL_CreateTextureFromSurface(renderer, txt_surface); if (cache->score_texture != NULL) { @@ -84,15 +85,10 @@ static inline int draw_block(SDL_Renderer* const renderer, int8_t const x, int8_ return SDL_RenderFillRect(renderer, &block); } -// sets the colour32 from the colour8 -static inline void set_colour(SDL_Renderer* const renderer, colour8 const c) { - (void)SDL_SetRenderDrawColor(renderer, colour8_red32(c), colour8_green32(c), colour8_blue32(c), 0xFF); -} - // draws a shape at the specified position static void draw_shape(SDL_Renderer* const renderer, shape_id const id, int8_t const pos_x, int8_t const pos_y) { shape const shape = shape_from_id(id); - set_colour(renderer, colour_from_id(id)); + set_colour8(renderer, colour_from_id(id)); for (int8_t y = 0; y < SHAPE_HEIGHT; y++) { shape_row const shape_row = shape_get_row(shape, y); @@ -107,31 +103,30 @@ static void draw_shape(SDL_Renderer* const renderer, shape_id const id, int8_t c } // draw the block data in the level -static void render_level(SDL_Renderer* const renderer, game_data const* const data) { +static void render_level(SDL_Renderer* const renderer, gamedata const* const data) { for (int8_t y = 0; y < ROWS; y++) { row_const const row = data->rows[y]; for (int8_t x = 0; x < COLUMNS; x++) { if (row[x] != 0) { - set_colour(renderer, row[x]); + set_colour8(renderer, row[x]); draw_block(renderer, x, y); } } } } -void renderer_update(render_data const* const dat) { +void render_update(renderdata const* const dat) { SDL_Renderer* const renderer = dat->renderer; - game_data const* const game_data = dat->game_dat; - + gamedata const* const game_data = dat->game_dat; int success = 0; // if an error occurs, this value is <0 // clear render - set_colour(renderer, COLOUR_BLACK); + set_colour32(renderer, COLOUR32_BLACK); // using colour32 is more efficient, as it sets the colours directly success |= SDL_RenderClear(renderer); - set_colour(renderer, COLOUR_WHITE); + set_colour32(renderer, COLOUR32_WHITE); static SDL_Rect const field_size = {TET_PADDING, TET_PADDING, TET_WIDTH + 1, TET_HEIGHT + 1}; SDL_RenderDrawRect(renderer, &field_size); @@ -150,13 +145,10 @@ void renderer_update(render_data const* const dat) { SDL_RenderPresent(renderer); } -void renderer_free(render_data* const render_data) { +void render_free(renderdata* const render_data) { SDL_DestroyRenderer(render_data->renderer); SDL_DestroyWindow(render_data->window); TTF_CloseFont(render_data->font); free(render_data->cache); - render_data->renderer = NULL; - render_data->window = NULL; - render_data->font = NULL; - render_data->cache = NULL; + *render_data = (renderdata){0}; } diff --git a/src/window/renderer.h b/src/window/renderer.h index a495547..d31c721 100644 --- a/src/window/renderer.h +++ b/src/window/renderer.h @@ -1,9 +1,8 @@ #pragma once + #include -#include #include #include -#include #include "../game/game.h" @@ -16,20 +15,22 @@ #define BLOCK_WIDTH (TET_WIDTH / COLUMNS) // width of a block #define BLOCK_HEIGHT (TET_HEIGHT / ROWS) // height of a block +// contains the data that's cached between renders struct render_cache { SDL_Texture* score_texture; SDL_Surface* score_surface; uint16_t prevscore; }; +// contains the data necessary for rendering typedef struct { - game_data const* game_dat; + gamedata const* game_dat; SDL_Window* window; SDL_Renderer* renderer; TTF_Font* font; struct render_cache* cache; -} render_data; +} renderdata; -void renderer_init(render_data* render_dat, game_data const* game_dat); -void renderer_update(render_data const* dat); -void renderer_free(render_data* dat); +void render_init(renderdata*, gamedata const*); // initializes the renderer, outputs to render_data +void render_update(renderdata const*); // causes a draw to occur, will also determine update rate +void render_free(renderdata*); // frees the memory allocated to the renderer in render_data