From 8352eda5279267fe92180d439b0f464c6144ca60 Mon Sep 17 00:00:00 2001 From: Quinn <99677023+thepigeongenerator@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:30:24 +0200 Subject: [PATCH] add missing files --- assets/bounce.wav | Bin 0 -> 10544 bytes build.sh | 71 ++++++++++++++++++ src/errors.c | 32 ++++++++ src/errors.h | 24 ++++++ src/game/level.c | 168 ++++++++++++++++++++++++++++++++++++++++++ src/game/level.h | 38 ++++++++++ src/game/vector2.h | 18 +++++ src/main.h | 4 + src/window/audio.c | 110 +++++++++++++++++++++++++++ src/window/audio.h | 20 +++++ src/window/colour.h | 9 +++ src/window/renderer.c | 73 ++++++++++++++++++ src/window/renderer.h | 15 ++++ 13 files changed, 582 insertions(+) create mode 100644 assets/bounce.wav create mode 100755 build.sh create mode 100644 src/errors.c create mode 100644 src/errors.h create mode 100644 src/game/level.c create mode 100644 src/game/level.h create mode 100644 src/game/vector2.h create mode 100644 src/main.h create mode 100644 src/window/audio.c create mode 100644 src/window/audio.h create mode 100644 src/window/colour.h create mode 100644 src/window/renderer.c create mode 100644 src/window/renderer.h diff --git a/assets/bounce.wav b/assets/bounce.wav new file mode 100644 index 0000000000000000000000000000000000000000..e003dd912901eac915ae687aa068615f68da1154 GIT binary patch literal 10544 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 +#include +#include + +#define MAX_STR_LEN 128 + +void error(const ErrorCode error_code, const char* format, ...) { + char buffer[MAX_STR_LEN] = {0}; // contains the buffer of the final string + + va_list args = {0}; + va_start(args, format); + vsnprintf(buffer, MAX_STR_LEN, format, args); + va_end(args); + + printf("\033[91mE\033[0m: %s\n", buffer); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "something went wrong! :O", buffer, NULL); + + exit(error_code); +} + +void warn(const char* format, ...) { + char buffer[MAX_STR_LEN] = {0}; // contains the buffer of the final string + + va_list args = {0}; + va_start(args, format); + vsnprintf(buffer, MAX_STR_LEN, format, args); + va_end(args); + + printf("\033[93mW\033[0m: %s\n", buffer); +} diff --git a/src/errors.h b/src/errors.h new file mode 100644 index 0000000..2ad27d4 --- /dev/null +++ b/src/errors.h @@ -0,0 +1,24 @@ +#pragma once + +typedef unsigned char ErrorCode; +enum { + SUCCESS = 0, + FAILURE = -1, + + FAILURE_STD = 1 << 0, // 1 : marks a generic C library error + FAILURE_MEMORY = FAILURE_STD | (1 << 1), // 3 : marks a memory related error + + // SDL ERRORS + FAILURE_SDL = 1 << 1, // 2 : marks a generic SDL error + FAILURE_SDL_INIT = FAILURE_SDL | (1 << 2), // 6 : marks an error during SDL initialisation + FAILURE_SDL_WINDOW = FAILURE_SDL | (1 << 3), // 10 : marks an error with the window + FAILURE_SDL_RENDERER = FAILURE_SDL | (1 << 4), // 18 : marks an error with the renderer + FAILURE_SDL_AUDIO = FAILURE_SDL | (1 << 5), // 34 : marks an error with audio + + // GAME ERRORS + FAILURE_GAME = 1 << 2, // 4 : marks a generic game error +}; + +// call when a fatal error has occurred, the program will immediately terminate when called +void error(const ErrorCode error_code, const char* format, ...); +void warn(const char* format, ...); diff --git a/src/game/level.c b/src/game/level.c new file mode 100644 index 0000000..d968a42 --- /dev/null +++ b/src/game/level.c @@ -0,0 +1,168 @@ +#include "level.h" + +#include +#include + +#include "../constants.h" +#include "../main.h" +#include "../window/renderer.h" +#include "vector2.h" + + +// prepares the level to be in a playable state +void level_init(Level* level) { + // initialize bouncer + level->bouncer.pos.x = (SCREEN_WIDTH / 2) - (BOUNCER_WIDTH_DEFAULT / 2); + level->bouncer.pos.y = SCREEN_HEIGHT - (BOUNCER_HEIGHT * 2); + level->bouncer.width = BOUNCER_WIDTH_DEFAULT; + + // initialize ball + level->ball.pos.x = (SCREEN_WIDTH / 2) - (BALL_SIZE_DEFAULT / 2); + level->ball.pos.y = level->bouncer.pos.y - BOUNCER_HEIGHT - BALL_SIZE_DEFAULT; + level->ball.moving = false; + level->ball.size = BALL_SIZE_DEFAULT; + level->ball.direction = (Vector2){0, -BALL_SPEED}; + + // initialize bricks + // define the colours of the brick rows + const Colour colours[BRICK_COLUMNS] = { + {0x5BCEFAFF}, + {0xF5A9B8FF}, + {0xFFFFFFFF}, + {0xF5A9B8FF}, + {0x5BCEFAFF}}; + + int brick_width = BRICK_WIDTH + BRICK_PADDING; + int brick_height = BRICK_HEIGHT + (BRICK_PADDING / 2); + float level_padding_left = ((float)SCREEN_WIDTH - ((SCREEN_WIDTH / brick_width) * brick_width)) / 2.0F; // for centering + + // store bricks in the level + for (int x = 0; x < BRICK_COLUMNS; x++) { + for (int y = 0; y < BRICK_ROWS; y++) { + Brick* brick = &level->bricks[x][y]; + brick->colour = colours[y]; + brick->pos.x = (x * brick_width) + level_padding_left; + brick->pos.y = (y * brick_height) + BRICK_PADDING_TOP; + brick->destroyed = false; + } + } +} + +// updates the player's "bouncer" +static void update_player(Bouncer* bouncer, Ball* ball, const Uint8* keys) { + // if move bouncer LEFT + if (keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_LEFT]) { + if (((bouncer->pos.x) < 0) == false) { + bouncer->pos.x -= BOUNCER_SPEED; + + if (ball->moving == false) + ball->pos.x -= BOUNCER_SPEED; + } + } + + // if move bouncer RIGHT + if (keys[SDL_SCANCODE_D] || keys[SDL_SCANCODE_RIGHT]) { + if (((bouncer->pos.x + bouncer->width) > SCREEN_WIDTH) == false) { + bouncer->pos.x += BOUNCER_SPEED; // increase the bouncer pos + + if (ball->moving == false) + ball->pos.x += BOUNCER_SPEED; + } + } + + // ball launching + if (ball->moving == false && keys[SDL_SCANCODE_SPACE]) { + ball->moving = true; + } +} + +// updates the player's ball +static void update_ball(Level* level, Ball* ball, Bouncer* bouncer) { + // update ball position + ball->pos.x += ball->direction.x; + ball->pos.y += ball->direction.y; + + + // check X axis out of bounds collisions + if ((ball->pos.x + ball->size) > SCREEN_WIDTH || ball->pos.x < 0) { + audio_play(level->audio_device, level->bounce_sfx); + ball->direction.x *= -1; + ball->pos.x = (ball->pos.x < 0) ? 0 : SCREEN_WIDTH - ball->size; // correct the ball's position + } + + // check Y axis out of bounds collisions + if (ball->pos.y < 0) { + audio_play(level->audio_device, level->bounce_sfx); + ball->direction.y *= -1; + ball->pos.y = 0; // correct the ball's position + } + + + // check bouncer collisions + if ((ball->pos.x + ball->size) > bouncer->pos.x && ball->pos.x < (bouncer->pos.x + bouncer->width) && + (ball->pos.y + ball->size) > bouncer->pos.y && ball->pos.y < (bouncer->pos.y + BOUNCER_HEIGHT)) { + float x = ball->pos.x - bouncer->pos.x + (ball->size / 2) + 2; // get the X axis relative to the bouncer (add 2, see below) + unsigned max = bouncer->width + 4; // get the maxiumum of this X axis (add 4 to make it feel more accurate) + float angle = (x - (max / 2.0F)) / max * (PI / 1.5F); // calculate the angle in radians where the bouncer X axis falls on -60° to 60° + + // change the ball direction + audio_play(level->audio_device, level->bounce_sfx); + ball->direction.x = SDL_sinf(angle) * BALL_SPEED; + ball->direction.y = -SDL_cosf(angle) * BALL_SPEED; + } + + // check brick collisions + bool collided = false; + for (int x = 0; x < BRICK_COLUMNS; x++) { + for (int y = 0; y < BRICK_ROWS; y++) { + const Brick* brick = &level->bricks[x][y]; + + if (brick->destroyed == true) { + continue; + } + + const float max_brick_x = brick->pos.x + BRICK_WIDTH; + const float brick_max_y = brick->pos.y + BRICK_HEIGHT; + if (ball->pos.x < max_brick_x && (ball->pos.x + ball->size) > brick->pos.x && + ball->pos.y < brick_max_y && (ball->pos.y + ball->size) > brick->pos.y) { + level->bricks[x][y].destroyed = true; + + // skip changing direction of we already did + if (collided == true) { + continue; + } + + // float ball_centre_x = ball->pos.x + (ball->size / 2.0F); + float ball_centre_y = ball->pos.y + (ball->size / 2.0F); + + // manage ball bounce direction; only bounce along the X axis if the ball's Y centre is in between dthe top and bottom of the block + if (brick->pos.y < ball_centre_y && ball_centre_y < brick_max_y) { + ball->direction.x *= -1; + } else { + ball->direction.y *= -1; + } + + audio_play(level->audio_device, level->bounce_sfx); + collided = true; + } + } + } +} + +// updates the level +void level_update(Level* level, const Uint8* keys) { + Bouncer* bouncer = &level->bouncer; + Ball* ball = &level->ball; + + update_player(bouncer, ball, keys); + + if (ball->moving == true) + update_ball(level, ball, bouncer); + + + // check lose condition + if ((ball->pos.y + ball->size) > SCREEN_HEIGHT) { + stop(); + ball->moving = false; + } +} diff --git a/src/game/level.h b/src/game/level.h new file mode 100644 index 0000000..2fdb71b --- /dev/null +++ b/src/game/level.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include + +#include "../constants.h" +#include "../window/colour.h" +#include "../window/audio.h" +#include "vector2.h" + +typedef struct { + Vector2 pos; + Vector2 direction; + unsigned size; + bool moving; +} Ball; + +typedef struct { + Vector2 pos; + unsigned width; +} Bouncer; + +typedef struct { + Vector2 pos; + Colour colour; + bool destroyed; +} Brick; + +typedef struct { + Ball ball; + Bouncer bouncer; + Brick bricks[BRICK_COLUMNS][BRICK_ROWS]; + AudioDevice* audio_device; + AudioData bounce_sfx; +} Level; + + +void level_init(Level* level); +void level_update(Level* level, const Uint8* keys); diff --git a/src/game/vector2.h b/src/game/vector2.h new file mode 100644 index 0000000..f1d79d6 --- /dev/null +++ b/src/game/vector2.h @@ -0,0 +1,18 @@ +#pragma once + +#define VECTOR2_UP ((Vector2){0, 1}) +#define VECTOR2_DOWN ((Vector2){0, -1}) +#define VECTOR2_LEFT ((Vector2){-1, 0}) +#define VECTOR2_RIGHT ((Vector2){1, 0}) +#define VECTOR2_ZERO ((Vector2){0, 0}) +#define VECTOR2_ONE ((Vector2){1, 1}) + +#define vec2_add(v1, v2) ((Vector2){v1.x + v2.x, v1.y + v2.y}) +#define vec2_subt(v1, v2) ((Vector2){v1.x - v2.x, v1.y - v2.y}) +#define vec2_mult(v, a) ((Vector2){v.x * a, v.y * a}) +#define vec2_div(v, a) ((Vector2){v.x / a, v.y / a}) + +typedef struct { + float x; + float y; +} Vector2; diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..1369892 --- /dev/null +++ b/src/main.h @@ -0,0 +1,4 @@ +#pragma once + +// stops execution of the game +void stop(void); diff --git a/src/window/audio.c b/src/window/audio.c new file mode 100644 index 0000000..985142d --- /dev/null +++ b/src/window/audio.c @@ -0,0 +1,110 @@ +#include "audio.h" + +#include + +#include "../errors.h" + +// the maximum amount of sounds that can play at once +#define MAX_SOUNDS 64 + +typedef struct { + AudioData* playing_audio; + AudioDevice audio_device; +} AudioCallbackData; + +// audio callback from SDL_AudioSpec; called when the audio device needs more data +static void audio_mixer(void* userdata, Uint8* stream, int len) { + memset(stream, 0, len); // clear the playing audio + AudioDevice* device = userdata; // get the callback data + AudioData* audio = device->playing_audio; + + for (int i = 0; i < MAX_SOUNDS; i++) { + // skip if the audio doesn't conain any further data + if (audio[i].length <= 0) { + continue; + } + + // get the length of which we shall be mixing + Uint32 mix_length = SDL_min(audio[i].length, len); + + // mix the audio with the stream + SDL_MixAudioFormat(stream, audio[i].buffer, device->format, mix_length, SDL_MIX_MAXVOLUME); + audio[i].buffer += mix_length; // move the pointer up a a bit + audio[i].length -= mix_length; // move up the mixed amount + } +} + +// converts the audio to the format of the audio device +static void convert_audio(const AudioDevice* audio_device, const SDL_AudioSpec wav_spec, Uint8** wav_buffer, Uint32* wav_length) { + // build the audio converter with the audio given + SDL_AudioCVT cvt = {0}; + SDL_BuildAudioCVT(&cvt, wav_spec.format, wav_spec.channels, wav_spec.freq, audio_device->format, audio_device->channels, audio_device->freq); + + cvt.len = (*wav_length) * wav_spec.channels; // the buffer length + cvt.buf = (Uint8*)SDL_malloc(cvt.len * cvt.len_mult); // allocate size for the new buffer + memcpy(cvt.buf, *wav_buffer, *wav_length); // copy wav data to cvt buffer; + + // convert + SDL_ConvertAudio(&cvt); + + // output + *wav_length = cvt.len_cvt; // set the length to the new length + memcpy(*wav_buffer, cvt.buf, cvt.len_cvt); // copy converted cvt buffer back to wav buffer + + free(cvt.buf); // free the memory allocated to the cvt buffer +} + +// loads a WAV file and returns the relevant information +AudioData audio_load_wav(const AudioDevice* audio_device, const char* file_path) { + SDL_AudioSpec wav_spec = {0}; + AudioData audio = {0}; + + SDL_LoadWAV(file_path, &wav_spec, &audio.buffer, &audio.length); + convert_audio(audio_device, wav_spec, &audio.buffer, &audio.length); + + return audio; +} + +// initializes the audio device +AudioDevice* audio_device_init(const int freq, const SDL_AudioFormat format, const Uint8 channels, const Uint16 samples) { + // allocate memory for the audio device + AudioDevice* audio_device = malloc(sizeof(AudioDevice)); + + // define the audio specification + SDL_AudioSpec spec = {freq, format, channels, samples}; + spec.callback = audio_mixer; + spec.userdata = audio_device; + + // create the audio device + *audio_device = (AudioDevice){ + SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0), + freq, + format, + channels, + calloc(MAX_SOUNDS, sizeof(AudioData)), // allocate memory on the heap for the playing audio array + }; + + // if the audio device isn't set, cause an error + if (audio_device->id < 1) { + error(FAILURE_SDL_AUDIO, "AudioDivice failed to open! SDL Error: %s", SDL_GetError()); + return NULL; + } + + // default state of the device is paused, so we unpause it here + SDL_PauseAudioDevice(audio_device->id, 0); + return audio_device; +} + +// plays the audio +void audio_play(const AudioDevice* audio_device, const AudioData audio) { + AudioData* playing_audio = audio_device->playing_audio; + + for (int i = 0; i < MAX_SOUNDS; i++) { + // overrite audio that has been deallocated + if (playing_audio[i].length <= 0) { + // override the audio + playing_audio[i] = audio; + break; // don't continue. :3 + } + } +} diff --git a/src/window/audio.h b/src/window/audio.h new file mode 100644 index 0000000..3a03b7b --- /dev/null +++ b/src/window/audio.h @@ -0,0 +1,20 @@ +#pragma once +#include + +typedef struct { + Uint32 length; + Uint8* buffer; +} AudioData; + +typedef struct { + SDL_AudioDeviceID id; + int freq; + SDL_AudioFormat format; + Uint8 channels; + AudioData* playing_audio; +} AudioDevice; + + +AudioData audio_load_wav(const AudioDevice* audio_device, const char* file_path); +AudioDevice* audio_device_init(const int freq, const SDL_AudioFormat format, const Uint8 channels, const Uint16 samples); +void audio_play(const AudioDevice* audio_device, const AudioData audio); diff --git a/src/window/colour.h b/src/window/colour.h new file mode 100644 index 0000000..756bec9 --- /dev/null +++ b/src/window/colour.h @@ -0,0 +1,9 @@ +typedef union { + unsigned packed; + struct { + unsigned char a; + unsigned char b; + unsigned char g; + unsigned char r; + }; +} Colour; diff --git a/src/window/renderer.c b/src/window/renderer.c new file mode 100644 index 0000000..22deee6 --- /dev/null +++ b/src/window/renderer.c @@ -0,0 +1,73 @@ +#include "renderer.h" + +#include +#include + +#include "../constants.h" +#include "../errors.h" +#include "../game/level.h" + +// initializes the window and renderer +int renderer_init(SDL_Window** window, SDL_Renderer** renderer) { + // init the window + *window = SDL_CreateWindow("Quinn's Breakout Clone", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); + if (*window == NULL) { + error(FAILURE_SDL_WINDOW, "Window failed to be created! SDL_Error: %s", SDL_GetError()); + return -1; + } + + // create a renderer + *renderer = SDL_CreateRenderer(*window, -1, SDL_RENDERER_PRESENTVSYNC); + + if (*renderer == NULL) { + error(FAILURE_SDL_RENDERER, "Renderer failed to be created! SDL_Error: %s", SDL_GetError()); + return -1; + } + + return SUCCESS; +} + +// renders the screen +void renderer_update(RenderData* render_data) { + SDL_Renderer* renderer = render_data->renderer; + Level* level = render_data->level; + + int success = 0; // if an error occurs, this value is <0 + + // render background + success |= SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF); + success |= SDL_RenderClear(renderer); + + // draw player components + success |= SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); + success |= SDL_RenderFillRectF(renderer, &(SDL_FRect){level->bouncer.pos.x, level->bouncer.pos.y, level->bouncer.width, 5}); // draw bouncer + success |= SDL_RenderFillRectF(renderer, &(SDL_FRect){level->ball.pos.x, level->ball.pos.y, level->ball.size, level->ball.size}); // draw ball + + // draw bricks + for (int x = 0; x < BRICK_COLUMNS; x++) { + for (int y = 0; y < BRICK_ROWS; y++) { + Brick* brick = &level->bricks[x][y]; + + if (brick->destroyed == true) { + continue; + } + + success |= SDL_SetRenderDrawColor(renderer, brick->colour.r, brick->colour.g, brick->colour.b, brick->colour.a); + success |= SDL_RenderFillRectF(renderer, &(SDL_FRect){brick->pos.x, brick->pos.y, BRICK_WIDTH, BRICK_HEIGHT}); // draw brick + } + } + + if (success < 0) { + error(FAILURE_SDL_RENDERER, "something went wrong whilst rendering: %s\n", SDL_GetError()); + return; + } + + // present the result to the renderer + SDL_RenderPresent(renderer); +} + +void renderer_destroy(SDL_Window* window, SDL_Renderer* renderer) { + SDL_DestroyWindow(window); + SDL_DestroyRenderer(renderer); + SDL_Quit(); +} diff --git a/src/window/renderer.h b/src/window/renderer.h new file mode 100644 index 0000000..8368eed --- /dev/null +++ b/src/window/renderer.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "../game/level.h" + +typedef struct { + SDL_Window* window; + SDL_Renderer* renderer; + Level* level; +} RenderData; + +int renderer_init(SDL_Window** window, SDL_Renderer** renderer); +void renderer_update(RenderData* render_data); +void renderer_destroy(SDL_Window* window, SDL_Renderer* renderer);