commit 8c61e53dcd243f152d0e32f44b073f64b64f5d70 Author: rdegreef Date: Fri Oct 6 17:30:56 2023 +0200 initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..764e528 --- /dev/null +++ b/.clang-format @@ -0,0 +1,13 @@ +BasedOnStyle: Microsoft + +# Specify column limit of 80, this way you can squize more terminals on screen +ColumnLimit: 80 + +# Pointers are part of the type not the variable name +PointerAlignment: Left + +# Consecutive macros are more readable +AlignConsecutiveMacros: true + +# It is more readable to have short {} statements on the same line +AllowShortBlocksOnASingleLine: true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..510163c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Do not include compiled object files into git +*.o +# Do not include the built binary into git +pong diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b57f348 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +CC=gcc +LD=gcc + +# I'm using GCC to link code. This is prefered over using LD directly since +# it will handle all the GLIBC linkage for you. + +SRCS = $(wildcard src/*.c) +OBJS = ${SRCS:.c=.o} + +DEST = pong + +CC_FLAGS = -I include/ -g -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -O3 +LD_FLAGS = +LIBS = -lSDL2 + +# This is to let make know these are not actually file targets but rather +# command targets. +.PHONY = all clean format debug run + +all: $(DEST) + +# Link all objects together into one binary +$(DEST): ${OBJS} + $(LD) ${LD_FLAGS} $^ $(LIBS) -o $(DEST) + +# For every c file, compile it to an object file +%.o: %.c + $(CC) ${CC_FLAGS} -c $< -o $@ + +# Run the formatter +format: + clang-format -i src/*.c include/*.h + +# Run the created binary +run: $(DEST) + ./$(DEST) + +# Run the created binary using the debugger +debug: $(DEST) + gdb ./$(DEST) -ex "set disassembly-flavor intel" + +# Clean the project of all build files +clean: + $(info [INFO] Cleaning directory) + rm -rf ${OBJS} $(DEST) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9f2267 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# This is an example project to test if SDL2 is working on WSL2 + +## Required software +You need to install the SDL2 development library. On Debian +based systems such as Ubuntu or Linux Mint, you can run + + sudo apt install libsdl2-dev + +You will also need gcc and make to build the project and gdb +if you want to debug the project. Further clang-formatt can +be used to format the project. + +These can all be installed using + + sudo apt install build-essential gdb clang-format + +## Building the project +In the main directory, simply run `make` with no arguments to +build the project. You can run `make run` to run the built project +and you can run `make debug` to debug the project using gdb. diff --git a/include/ball.h b/include/ball.h new file mode 100644 index 0000000..21263a6 --- /dev/null +++ b/include/ball.h @@ -0,0 +1,21 @@ +#ifndef BALL_H +#define BALL_H + +#define BALL_WIDTH 15 +#define BALL_SPEED 1000 + +struct ball +{ + int x_location; + int y_location; + int width; + int speed; + float x_direction; + float y_direction; +}; + +struct ball* ball_init(int x_location, int y_location, int width, int speed); +struct game_data; +void ball_update(struct game_data* data, struct ball* ball, float dt); + +#endif /* BALL_H */ diff --git a/include/game.h b/include/game.h new file mode 100644 index 0000000..b15cc4c --- /dev/null +++ b/include/game.h @@ -0,0 +1,19 @@ +#ifndef GAME_H +#define GAME_H + +struct game_data +{ + int is_running; + struct render_data* render_data; + + struct player* player1; + struct player* player2; + struct ball* ball; + + int window_width; + int window_height; +}; + +void game_start(int window_width, int window_height); + +#endif /* GAME_H */ diff --git a/include/player.h b/include/player.h new file mode 100644 index 0000000..0753452 --- /dev/null +++ b/include/player.h @@ -0,0 +1,25 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#define PLAYER_WIDTH 25 +#define PLAYER_HEIGHT 250 +#define PLAYER_SPEED 6000.0 + +struct player +{ + int x_location; + int y_location; + int speed; +}; + +enum player_move_dir +{ + PLAYER_MOVE_UP, + PLAYER_MOVE_DOWN, +}; + +void player_move(struct player* player, float dt, enum player_move_dir dir); +void player_update(struct game_data* data, struct player* player, float dt); +struct player* player_init(int x_location, int move_limit, int speed); + +#endif /* PLAYER_H */ diff --git a/include/pong_ai.h b/include/pong_ai.h new file mode 100644 index 0000000..a764822 --- /dev/null +++ b/include/pong_ai.h @@ -0,0 +1,6 @@ +#ifndef PONG_AI_H +#define PONG_AI_H + +void pong_ai_update(struct game_data* game, struct player* player, float dt); + +#endif /* PONG_AI_H */ diff --git a/include/render.h b/include/render.h new file mode 100644 index 0000000..bc5a860 --- /dev/null +++ b/include/render.h @@ -0,0 +1,8 @@ +#ifndef RENDER_H +#define RENDER_H + +struct render_data; +struct render_data* render_init(int width, int height); +void render(struct game_data* data); + +#endif /* RENDER_H */ diff --git a/src/ball.c b/src/ball.c new file mode 100644 index 0000000..975082c --- /dev/null +++ b/src/ball.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include + +struct ball* ball_init(int x_location, int y_location, int width, int speed) +{ + struct ball* ball = malloc(sizeof(struct ball)); + memset(ball, 0, sizeof(struct ball)); + + ball->x_location = x_location; + ball->y_location = y_location; + ball->width = width; + ball->speed = speed; + ball->x_direction = 1.0; + ball->y_direction = 1.0; + + return ball; +} + +static bool check_collision(int x1, int y1, int w1, int h1, int x2, int y2, + int w2, int h2) +{ + // Simple AABB collision + return (x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2); +} + +static void collide_with_player(struct player* player, struct ball* ball) +{ + ball->x_direction = -ball->x_direction; + // Calculate how much drift this hit should add to the ball + // + // _ player->y_location + // ¦ ¦ + // ¦ ¦ ball->y_location + // ¦ ¦ O + // ¦ ¦ ----- player->y_location / 2 + // ¦ ¦ + // ¦ ¦ + // ¦_¦ + // + // The following equation will create a float between -1.0 and 1.0 where + // 0 means the ball hit the player paddle straight on, -1.0 means the ball + // hit the topmost point of the paddle and 1.0 means the bottommost point. + float drift = (float)((ball->y_location + (ball->width / 2)) - + (player->y_location + PLAYER_HEIGHT / 2)) / + ((float)PLAYER_HEIGHT / 2.0f); + ball->y_direction += drift; +} + +void ball_update(struct game_data* game, struct ball* ball, float dt) +{ + ball->x_location += ball->x_direction * (float) ball->speed * dt; + ball->y_location += ball->y_direction * (float) ball->speed * dt; + + // Check collisions with the top and bottom of the frame + if (ball->y_location < 0) + { + // collide with top of the frame + ball->y_direction = -ball->y_direction; + ball->y_location = 0; + } + else if (ball->y_location + ball->width > game->window_height) + { + // collide with bottom of the frame + ball->y_direction = -ball->y_direction; + ball->y_location = game->window_height - ball->width; + } + // Check collisions with the left and right of the frame + if (ball->x_location < 0) + { + // end game on the left + game->is_running = 0; + } + else if (ball->x_location + ball->width > game->window_width) + { + // end game on the right + game->is_running = 0; + } + + // Check collision with players + struct player* p1 = game->player1; + struct player* p2 = game->player2; + if (check_collision(ball->x_location, ball->y_location, ball->width, + ball->width, p1->x_location, p1->y_location, + PLAYER_WIDTH, PLAYER_HEIGHT)) + { + ball->x_location = p1->x_location + PLAYER_WIDTH; + collide_with_player(p1, ball); + } + else if (check_collision(ball->x_location, ball->y_location, ball->width, + ball->width, p2->x_location, p2->y_location, + PLAYER_WIDTH, PLAYER_HEIGHT)) + { + ball->x_location = p2->x_location - ball->width; + collide_with_player(p2, ball); + } +} \ No newline at end of file diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..0c52558 --- /dev/null +++ b/src/game.c @@ -0,0 +1,104 @@ +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +static void check_events(struct game_data* data, float dt) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + printf("Exit requested\n"); + data->is_running = 0; + break; + case SDL_KEYDOWN: + } + } + + const uint8_t* state = SDL_GetKeyboardState(NULL); + if (state[SDL_SCANCODE_UP]) + { + player_move(data->player1, dt, PLAYER_MOVE_DOWN); + } + else if (state[SDL_SCANCODE_DOWN]) + { + player_move(data->player1, dt, PLAYER_MOVE_UP); + } +} + +static void game_update(struct game_data* data, float dt) +{ + // Check and act on events + check_events(data, dt); + + // Update the AI + pong_ai_update(data, data->player2, dt); + + // Update all players and the ball + player_update(data, data->player1, dt); + player_update(data, data->player2, dt); + ball_update(data, data->ball, dt); +} + +static void game_loop(struct game_data* data) +{ + printf("Starting game loop\n"); + + float dt = 0; + while (data->is_running) + { + unsigned long long start = SDL_GetPerformanceCounter(); + + // Update the game and render + game_update(data, dt); + render(data); + + // Calculate the frame time + unsigned long long end = SDL_GetPerformanceCounter(); + dt = (end - start) / (float)SDL_GetPerformanceFrequency(); + + // Keep the framrate at 60 Fps + long long delay = 16.666f - (dt * 1000.0f); + if (delay > 0) + SDL_Delay(delay); + } + + printf("Goodbye\n"); +} + +void game_start(int window_width, int window_height) +{ + printf("Starting game\n"); + + // Create a structure to hold all the information about the game + struct game_data* data = malloc(sizeof(struct game_data)); + memset(data, 0, sizeof(struct game_data)); + + // Fill the game data structure with the correct values + data->is_running = 1; + data->window_width = window_width; + data->window_height = window_height; + + // Initialize SDL2 + data->render_data = render_init(window_width, window_height); + + // Create the players and the ball + data->player1 = player_init(15, window_height, PLAYER_SPEED); + data->player2 = player_init(window_width - PLAYER_WIDTH - 15, window_height, + PLAYER_SPEED); + data->ball = + ball_init(window_width / 2 - BALL_WIDTH, + window_height / 2 - BALL_WIDTH, BALL_WIDTH, BALL_SPEED); + + game_loop(data); +} \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..b669d53 --- /dev/null +++ b/src/main.c @@ -0,0 +1,40 @@ +#include +#include +#include + +#include + +int main(int argc, char** argv) +{ + + int window_width = 500; + int window_height = 500; + + const char* help = + " -w Set the width of the window, default 500\n" + " -h Set the height of the window, default 500\n"; + + int opt; + while ((opt = getopt(argc, argv, "w:h:")) != -1) + { + switch (opt) + { + case 'w': + /* Set the window width */ + window_width = atoi(optarg); + break; + case 'h': + /* Set the window height */ + window_height = atoi(optarg); + break; + + default: + fprintf(stderr, "Unknown argument '%s'\n%s", argv[optind], help); + exit(1); + } + } + + game_start(window_width, window_height); + + return 0; +} \ No newline at end of file diff --git a/src/player.c b/src/player.c new file mode 100644 index 0000000..7d0e6fc --- /dev/null +++ b/src/player.c @@ -0,0 +1,42 @@ +#include +#include + +#include +#include + +void player_move(struct player* player, float dt, enum player_move_dir dir) +{ + if (dir == PLAYER_MOVE_DOWN) + { + player->y_location -= (int)(player->speed * dt); + } + else + { + player->y_location += (int)(player->speed * dt); + } +} + +void player_update(struct game_data* game, struct player* player, float dt) +{ + // Constrain the players location + if (player->y_location < 0) + { + player->y_location = 0; + } + else if (player->y_location + PLAYER_HEIGHT > game->window_height) + { + player->y_location = game->window_height - PLAYER_HEIGHT; + } +} + +struct player* player_init(int x_location, int move_limit, int speed) +{ + struct player* player = malloc(sizeof(struct player)); + memset(player, 0, sizeof(struct player)); + + player->x_location = x_location; + player->y_location = (move_limit - PLAYER_HEIGHT) / 2; + player->speed = speed; + + return player; +} \ No newline at end of file diff --git a/src/pong_ai.c b/src/pong_ai.c new file mode 100644 index 0000000..c86e3a2 --- /dev/null +++ b/src/pong_ai.c @@ -0,0 +1,16 @@ +#include +#include +#include + +void pong_ai_update(struct game_data* game, struct player* player, float dt) +{ + int distance = game->ball->y_location - (player->y_location + PLAYER_HEIGHT / 2); + if (distance < -1) + { + player_move(player, dt, PLAYER_MOVE_DOWN); + } + else if (distance > 1) + { + player_move(player, dt, PLAYER_MOVE_UP); + } +} \ No newline at end of file diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..7cee919 --- /dev/null +++ b/src/render.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +struct render_data +{ + SDL_Renderer* renderer; + SDL_Window* window; +}; + +struct render_data* render_init(int width, int height) +{ + // Initialize SDL2 + struct render_data* data = malloc(sizeof(struct render_data)); + memset(data, 0, sizeof(struct render_data)); + + printf("Start initializing SDL2\n"); + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) + fprintf(stderr, "Could not initialize SDL2: '%s'\n", SDL_GetError()); + + data->window = + SDL_CreateWindow("Pong", 50, 50, width, height, SDL_WINDOW_SHOWN); + if (!data->window) + fprintf(stderr, "Could not create window: '%s'\n", SDL_GetError()); + + data->renderer = + SDL_CreateRenderer(data->window, -1, SDL_RENDERER_PRESENTVSYNC); + + printf("Finished initializing SDL2\n"); + return data; +} + +static void player_render(struct player* player, struct render_data* data) +{ + SDL_Rect rect; + rect.x = player->x_location; + rect.y = player->y_location; + rect.w = PLAYER_WIDTH; + rect.h = PLAYER_HEIGHT; + + SDL_SetRenderDrawColor(data->renderer, 0xFF, 0xFF, 0xFF, 0xFF); + SDL_RenderDrawRect(data->renderer, &rect); +} + +static void ball_render(struct ball* ball, struct render_data* data) +{ + SDL_Rect rect; + rect.x = ball->x_location; + rect.y = ball->y_location; + rect.w = ball->width; + rect.h = ball->width; + + SDL_SetRenderDrawColor(data->renderer, 0xFF, 0xFF, 0xFF, 0xFF); + SDL_RenderDrawRect(data->renderer, &rect); +} + +void render(struct game_data* game) +{ + // Clear the screen of the game + SDL_SetRenderDrawColor(game->render_data->renderer, 0, 0, 0, 0xFF); + SDL_RenderClear(game->render_data->renderer); + + player_render(game->player1, game->render_data); + player_render(game->player2, game->render_data); + ball_render(game->ball, game->render_data); + + // Show the newly rendered screen + SDL_RenderPresent(game->render_data->renderer); +} \ No newline at end of file