initial commit

This commit is contained in:
Robbe De Greef 2023-10-06 17:30:56 +02:00
commit 8c61e53dcd
15 changed files with 533 additions and 0 deletions

13
.clang-format Normal file
View File

@ -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

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Do not include compiled object files into git
*.o
# Do not include the built binary into git
pong

45
Makefile Normal file
View File

@ -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)

20
README.md Normal file
View File

@ -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.

21
include/ball.h Normal file
View File

@ -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 */

19
include/game.h Normal file
View File

@ -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 */

25
include/player.h Normal file
View File

@ -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 */

6
include/pong_ai.h Normal file
View File

@ -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 */

8
include/render.h Normal file
View File

@ -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 */

100
src/ball.c Normal file
View File

@ -0,0 +1,100 @@
#include <ball.h>
#include <game.h>
#include <player.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
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);
}
}

104
src/game.c Normal file
View File

@ -0,0 +1,104 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL2/SDL.h>
#include <ball.h>
#include <game.h>
#include <player.h>
#include <pong_ai.h>
#include <render.h>
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);
}

40
src/main.c Normal file
View File

@ -0,0 +1,40 @@
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <game.h>
int main(int argc, char** argv)
{
int window_width = 500;
int window_height = 500;
const char* help =
" -w <width> Set the width of the window, default 500\n"
" -h <height> 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;
}

42
src/player.c Normal file
View File

@ -0,0 +1,42 @@
#include <stdlib.h>
#include <string.h>
#include <game.h>
#include <player.h>
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;
}

16
src/pong_ai.c Normal file
View File

@ -0,0 +1,16 @@
#include <ball.h>
#include <game.h>
#include <player.h>
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);
}
}

70
src/render.c Normal file
View File

@ -0,0 +1,70 @@
#include <SDL2/SDL.h>
#include <ball.h>
#include <game.h>
#include <player.h>
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);
}