Pong in C with Raylib

The sheep have finished lambing and I finally have some spare time. I've been AFK for a few weeks and during that time I've been reflecting on life as a programmer. Upon this reflection I have made a decision. I want to write C. Writing C feels like proper programming and I wish to be a proper programmer.

This brings us to today's post. In order to increase my efficiency with C I will be undertaking various programming projects. To ease myself in, we have today's project "write a pong clone".

He's written lot's of pong clones already... It's his go to. Like a programming kata, remember those?

The Clone

a png clone written in C using raylib

I chose a Pong clone to start as it's super simple. There are only 3 objects, the two paddles and the ball, so everything can be kept on the stack. No need for memory allocation here, and I'm not going to bother with assets like sound or sprites, it's not needed for this one.

The following Pong clone was written in about 2 hours. I didn't refactor anything as I went along. I felt it unnessary, it's not like any one will actually play or use this. It's simply an exercise.

Check out the code below and critique the workings of my naive programming brain.

The next challenge is an Asteroids clone, which is a slight step up. I'll try to make a proper game loop for that one.

The Code

Link to the codeberg repo

#include "raylib.h"
#include <stdio.h>
#include <stdlib.h>

struct Court
{
    float x;
    float y;
    int w;
    int h;
};

struct Ball
{
    float x;
    float y;
    int vx;
    int vy;
    int speed;
    int r;
};

void drawBall(struct Ball *b)
{
    DrawCircle(b->x, b->y, b->r, RAYWHITE);
}

struct Paddle
{
    float x;
    float y;
    float vx;
    float vy;
    int w;
    int h;
};

void drawPaddle(struct Paddle *p)
{
    DrawRectangle(p->x, p->y, p->w, p->h, RAYWHITE);
}

struct Game
{
    int playerScore;
    int computerScore;
};

int main(void)
{
    struct Court court = { 100, 100, 400, 600 };
    Vector2 courtTopLeft = {court.x, court.y};
    Vector2 courtTopRight = {court.x + court.w, court.y};
    Vector2 courtBotLeft = {court.x, court.y + court.h};
    Vector2 courtBotRight = {court.x + court.w, court.y + court.h};
    float courtLineThickness = 4;

    InitWindow(600, 800, "Pong");

    SetRandomSeed(1337);

    struct Game game = { 0, 0 };
    struct Ball ball;
    struct Paddle topPaddle;
    struct Paddle bottomPaddle;

    ball.r = 10;
    ball.x = court.x + (court.w / 2);
    ball.y = court.y + (court.h / 2);
    ball.vx = GetRandomValue(0, 1) ? 1 : -1;
    ball.vy = -1;
    ball.speed=100;

    int paddleYBuffer = 20;

    topPaddle.y = court.y + paddleYBuffer;
    bottomPaddle.y = court.y + court.h - (paddleYBuffer * 2);

    int initialPaddleWidth = 80;
    int initialPaddleHeight = 20;

    topPaddle.w = initialPaddleWidth;
    topPaddle.h = initialPaddleHeight;

    bottomPaddle.w = initialPaddleWidth;
    bottomPaddle.h = initialPaddleHeight;

    topPaddle.x = court.x + (court.w / 2) - (topPaddle.w / 2);
    bottomPaddle.x = court.x + (court.w / 2) - (bottomPaddle.w / 2);

    Vector2 playerMovement;
    int paddleMoveSpeed = 400;
    Vector2 playerVelocity;
    float friction = 0.78;

    int cellSize = 100;
    int rowCount = GetScreenHeight() / cellSize;
    int colCount = GetScreenWidth() / cellSize;

    float bottomHit = 0.0;
    float topHit = 0.0;

    while (!WindowShouldClose())
    {
        //update
        if(IsKeyDown(KEY_R))
        {
            game.playerScore = 0;
            game.computerScore = 0;
        }

        if(IsKeyDown(KEY_LEFT))
        {
            playerVelocity.x = -1;
        }

        if(IsKeyDown(KEY_RIGHT))
        {
            playerVelocity.x = 1;
        }

        if(topPaddle.vx)
        {
            topPaddle.x += topPaddle.vx * (paddleMoveSpeed * GetFrameTime());

            topPaddle.vx *= friction;
            if(topPaddle.vx < 0.1 && topPaddle.vx > -0.1)
            {
                topPaddle.vx=0;
            }
        }

        if(topPaddle.x < court.x)
        {
            topPaddle.x = court.x;
        }

        if(topPaddle.x + topPaddle.w > court.x + court.w)
        {
            topPaddle.x = court.x + court.w - topPaddle.w;
        }

        if(playerVelocity.x)
        {
            bottomPaddle.x += playerVelocity.x * (paddleMoveSpeed * GetFrameTime());

            playerVelocity.x *= friction;
            if(playerVelocity.x < 0.1 && playerVelocity.x > -0.1)
            {
                playerVelocity.x=0;
            }
        }

        if(bottomPaddle.x < court.x)
        {
            bottomPaddle.x = court.x;
        }

        if(bottomPaddle.x + bottomPaddle.w > court.x + court.w)
        {
            bottomPaddle.x = court.x + court.w - bottomPaddle.w;
        }


        ball.x += ball.vx * (paddleMoveSpeed * GetFrameTime());
        ball.y += ball.vy * (paddleMoveSpeed * GetFrameTime());


        if(ball.x - ball.r < court.x)
        {
            ball.vx = 1;
        }

        if(ball.x + ball.r > court.x + court.w)
        {
            ball.vx = -1;
        }

        if(ball.y - ball.r < court.y)
        {
            ball.vy = 1;
        }

        if(ball.y + ball.r > court.y + court.h)
        {
            ball.vy = -1;
        }

        Vector2 center = { ball.x, ball.y };
        Rectangle botRect = { bottomPaddle.x, bottomPaddle.y, bottomPaddle.w, bottomPaddle.h };
        if(CheckCollisionCircleRec(center, ball.r, botRect))
        {
            ball.vy = -1;
            ball.vx = GetRandomValue(0, 1) ? -1 : 1;
        }

        Rectangle topRect = { topPaddle.x, topPaddle.y, topPaddle.w, topPaddle.h };
        if(CheckCollisionCircleRec(center, ball.r, topRect))
        {
            ball.vy = 1;
            ball.vx = GetRandomValue(0, 1) ? -1 : 1;
        }


        if(ball.vy == -1)
        {
            if(topPaddle.x > ball.x + ball.r)
            {
                topPaddle.vx = -1;
            }

            if(topPaddle.x +topPaddle.w < ball.x - ball.r)
            {
                topPaddle.vx = 1;
            }
        }

        if(ball.vy == 1)
        {
            if(topPaddle.x + ((float)topPaddle.w / 2) > court.x + court.w /2)
            {
                topPaddle.vx = -1;
            }

            if(topPaddle.x + ((float)topPaddle.w / 2) < court.x + court.w / 2)
            {
                topPaddle.vx = 1;
            }
        }

        if(topHit)
        {
            topHit -= 0.01;
        }

        if(bottomHit)
        {
            bottomHit -= 0.01;
        }

        if(CheckCollisionCircleLine(center, ball.r, courtTopLeft, courtTopRight))
        {
            topHit = 1.0;
            game.playerScore += 1;
        }

        if(CheckCollisionCircleLine(center, ball.r, courtBotLeft, courtBotRight))
        {
            bottomHit = 1.0;
			game.computerScore += 1;
        }

        //draw
        BeginDrawing();
        ClearBackground(BLACK);
        for(int rowIndex = 0; rowIndex < rowCount; rowIndex++)
        {
            for(int colIndex = 0; colIndex < colCount; colIndex++)
            {
                DrawRectangle(
                    colIndex * cellSize,
                    rowIndex * cellSize,
                    cellSize,
                    cellSize,
                    (colIndex + rowIndex) % 2 == 0 ? GREEN : LIME
                );
            }
        }

        char scoreText[32];
        snprintf(scoreText, sizeof(scoreText), "%d : %d", game.playerScore, game.computerScore);
        int fontSize = 80;
        int textWidth = MeasureText(scoreText, fontSize);
        DrawText(scoreText, court.x - textWidth / 2 + court.w / 2, 10, fontSize, RAYWHITE);

        //top
        DrawLineEx(courtTopLeft, courtTopRight, courtLineThickness, topHit > 0 ? RED : RAYWHITE);
        //bot
        DrawLineEx(courtBotLeft, courtBotRight, courtLineThickness, bottomHit > 0 ? RED : RAYWHITE);
        //left
        DrawLineEx(courtTopLeft, courtBotLeft, courtLineThickness, RAYWHITE);
        //right
        DrawLineEx(courtTopRight, courtBotRight, courtLineThickness, RAYWHITE);

        drawBall(&ball);
        drawPaddle(&topPaddle);
        drawPaddle(&bottomPaddle);
        EndDrawing();
    }

    CloseWindow();
    return 0;
}

Until next time,

- Brian