Pong!

The end of the second week of Game Programming I and our lecturer  guided the class in putting together basic, two-player Pong during a two-day intensive workshop. In this project we’ve gone beyond the scope of what we’ve covered so far in the course, which has no programming prerequisites, so it’s right into the deep end!

Collision in this version is rough:
The ball can sometimes get “stuck” against the paddle. The collision programming negates the direction but the ball sometimes doesn’t manage to move far enough away from the paddle before another collision is detected. The collision results in negated direction again and so on, so the ball ends up moving vertically along the paddle and looks “sticky.” A workaround would be to run a check during each collision detection to see if the direction is already negated (negative values here) and not to continue with the “bounce” if it is.

Click to download!

Start/Pause: Spacebar
Player1: W, S
Player2: O, L
Quit: Esc

If the exe isn’t working, grab the following .NET frameworks:

ASP.NET Web Frameworks and Tools 2012.2   4.1.21001.0
ASP.NET Web Frameworks and Tools 2013   5.0.11001.0

Here’s the project code:

 
// main.cpp

// includes 
#include <sdl.h>                                                        //header file, includes function prototypes (explain that a function exists and how it looks roughly, but not the function itself)
#include <cmath>

// Pro+: disable warning(s) that are annoying
#pragma warning(disable:4098)

// pragma directives (can also be linked through project settings)
#pragma comment(lib, "SDL2.lib")                                        //.lib är kompilerade filer med funktionalitet som beskriver hur dll filer fungerar
#pragma comment(lib, "SDL2main.lib")

// structs
struct Paddle
{
    float x, y;                                                            //vi måste ha en position på paddeln, float då vi kommer använda decimaltal för deltatime
    bool input[2];                                                        //vi definerar en boolean array för rörelse
};

struct Ball
{
    float x, y;                                                            //position på bollen
    float dirx, diry;                                                    //bollens riktning

};

enum EGameState
{
    GAME_STATE_PAUSE,                                                    //vi har två states, när allt på skärmen pauses och när vi spelar
    GAME_STATE_PLAY,                                                    //enum variables seperated by commas not semicolons
};

struct Game                                                                //struct är sätt att gruppera variabler, ex så vi kan ha en 2d position i två grupperade variabler som utför en struct
{
    SDL_Window*        window;                                                //en pekare till fönstret som sdl hjälper oss skapa
    SDL_Renderer* renderer;                                                
    int width, height;
    unsigned int tick;                                                    //för senare användning, när vi vill veta hur mycket tid gått sedan tidigare uppdatering, inte sedan datorn startades
    EGameState state;
    bool start;                                                            //kommer använda detta för att kontrolla byten mellan game states pause och play

    unsigned int score0, score1;                                        //variabel för att ha koll på score:en
    Paddle left;
    Paddle right;
    Ball ball;

};

void initialize_ball(Ball* ball, float x, float y)
{
    ball->x = x;                                                            //the ball is 20px wide, but positioning happens from the upper righthand corner, not the center of objects
    ball->y = y;                                                             //we're going to calc x and y elsewhere
    ball->dirx = 0.0f;
    ball->diry = 0.0f;
}

void initialize_paddle(Paddle* paddle, float x, float y)
{
    paddle->input[0] = false;
    paddle->input[1] = false,
    paddle->x = x;
    paddle->y = y;
}

// functions
bool initialize(Game* game, int width, int height) {                        //pekare till struct:en Game
    SDL_Log("initialize()");
    SDL_Init(SDL_INIT_EVERYTHING);                                            //SDL_Init initierar sdl, här initierar vi allting

    game->window = nullptr;                                                    //för att accessa något i struct:en game måste vi använda punkt . om det inte är en pekare
    game->renderer = nullptr;
    game->width = width;
    game->height = height;
    game->tick = SDL_GetTicks();                                            //för användning när vi vill veta hur mycket tid gått sedan tidigare uppdatering, inte sedan datorn startades
    game->state = GAME_STATE_PAUSE;
    game->start = false;
    game->score0 = 0;
    game->score1 = 0;

    initialize_ball(&game->ball,                                            //vi sätter bollen i mitten, vi kommer behöva göra det flera ggr (efter varje mål)
    game->width / 2 - 20.0f,
    game->height / 2 - 20.0f);

    //initialization of game variables:

    initialize_paddle(&game->left,                                            //positioning of the paddles using a function, since we want to reset the screen after every goal
        50,
        game->height / 2 - 50);

    initialize_paddle(&game->right,
        game->width - 70,
        game->height / 2 - 50);

    //game.right.input[0] = false;
    //game.right.input[1] = false;
    //game.right.x = 954;                                                    //1024-70
    //game.right.y = game.height / 2 - 50;

    //game.left.input[0] = false;
    //game.left.input[1] = false;
    //game.left.x = 50;                                                        //koordinat-variabler för vänstra paddelns position (600/2 -50, då paddeln är satt till 100pix totallt i draw och skärmen 600)
    //game.left.y = game.height / 2 - 50;


    // creating a window
    game->window = SDL_CreateWindow("Pong",                                //vi använder en pil för att accessa struct medlemmens variabel
        SDL_WINDOWPOS_UNDEFINED, 
        SDL_WINDOWPOS_UNDEFINED,
        game->width, 
        game->height,
        SDL_WINDOW_OPENGL);                                                //vi vill använda hårdvaruaccelerarad utritning

    // error checking window
    if (game->window == nullptr) {
        return false;
    }

    // creating a renderer that we use to draw stuff with
    game->renderer = SDL_CreateRenderer(game->window,
        -1, SDL_RENDERER_ACCELERATED);

    // error checking renderer 
    if (game->renderer == nullptr) {
        return false;
    }

    // everything went ok!
    return true;
}

void shutdown(Game* game) {
    // clean up resources
    SDL_Log("shutdown()");

    // check if we have a valid renderer                                //renderer är beroende av window, så vi tar bort renderer först eftersom vi satt igång den först ovan
    if (game->renderer != nullptr) {
        SDL_DestroyRenderer(game->renderer);
    }
    game->renderer = nullptr;

    // check if we have a valid window
    if (game->window != nullptr) {
        SDL_DestroyWindow(game->window);
    }
    game->window = nullptr;

    // shut down SDL
    SDL_Quit();
}

bool update_input(Game* game) {
    // check events for user input
    SDL_Event event;
    while (SDL_PollEvent(&event)) {                                                    //sdl är en abstraktion över själva os, typ platformsoberoende, behöver bara kompileras om
        switch (event.type) {                                                        //switch för 'om något händer'
        case SDL_KEYDOWN:                                                            //om en knapp blir nertryckt
            switch (event.key.keysym.sym) {                                            //ytterliggare en switch
            // player 1 keys
            case SDLK_w:
                game->left.input[0] = true;                                            //pil för att pekaren kan komma åt en struct:s variabel, när pekaren väl pekare på left, punkt för att komma åt left:s variabel.
                break;
            case SDLK_s:
                game->left.input[1] = true;                                            //
                break;

            // player 2 keys
            case SDLK_o:
                game->right.input[0] = true;
                break;
            case SDLK_l:
                game->right.input[1] = true;
                break;

            // space-key
            case SDLK_SPACE:
                game->start = true;                                                    //vi sätter start till true när spacebar är nedtryckt för att byta till game state pause
                break;
            }
            break;

        case SDL_KEYUP:                                                                //vi tar reda på om ni slutar trycka ner en knapp
            switch (event.key.keysym.sym) {
            // player 1 keys
            case SDLK_w:    
                game->left.input[0] = false;                                        //en array för att skilja på upp och ner, position 0 i arrayen är upp, position 1 i arrayen är ner TROR JAG
                break;
            case SDLK_s:
                game->left.input[1] = false;                                        //
                break;

            // player 2 keys 
            case SDLK_o:
                game->right.input[0] = false;
                break;
            case SDLK_l:
                game->right.input[1] = false;
                break;

            // space-key
            case SDLK_SPACE:
                game->start = false;                                                //start till false när spacebar inte är nedtryckt så game state är play
                break;

            // quit-key
            case SDLK_ESCAPE:
                return false;
                break;
            }
            break;

        case SDL_QUIT:
            return false;
            break;
        }
    }

    return true;
}

bool check_collision(Paddle* paddle, Ball* ball)                        //titta kollision mellan paddel och bollen
{
    if (paddle->x + 20.0f < ball->x || paddle->x > ball->x + 20.0f)
        return false;
    if (paddle->y + 100.0f < ball->y || paddle->y > ball->y + 20.0f)
        return false;

    return true;
}

bool update(Game* game) {                                                            
    if (!update_input(game)) {                                            //om vi har tryckt på en specifik knapp, stänger vi ner spelet. Annars körs det
        SDL_Log("Quit.");
        return false;
    }

    unsigned int now = SDL_GetTicks();                                    //vi vill ha TIME DEPENDENT movement, inte FRAME DEPENDENT movement. Representerar en tid i millisekunder. Unsigned då tiden ska vara positiva tal
    unsigned int delta = now - game->tick;                                //formel för att kunna räkna ut förändringen delta, dvs hur länge det gått sedan sista uppdateringen
    game->tick = now;
    float deltatime = static_cast<float>(delta) * 0.001f;                //vi vill omvandla millisekunder till sekunder, och vi vill veta hur mycket tid gått sedan tidigare uppdatering, inte sedan datorn startades
    float paddleSpeed = 500.0f;                                            //definerar en paddle speed variable

    // för att gå från ms till s: 0.001f * 1000.0f == 1
    // att dela på 1000 är likt med att multiplicera på 0.001

    switch (game->state)                                                //vi har två game states, det här är sättet att hantera dem
    {
    case GAME_STATE_PAUSE:
        if (game->start)
        {
            game->state = GAME_STATE_PLAY;
            if (now % 2 == 0)                                            //vi vill slumpa fram en viss rörelse på bollen när spelet startar
            {
                game->ball.dirx = -1.0f;
                game->ball.diry = 1.0f;
            }
            else
            {
                game->ball.dirx = 1.0f;
                game->ball.diry = -1.0f;
            }
            float len = sqrtf(                                                    //we want the hypoteneuse, since we want a vector from the x and y values, so pyth theorum: c = sqrtf(a*a+b*b);
                game->ball.dirx * game->ball.dirx +
                game->ball.diry * game->ball.diry);

            game->ball.dirx /= len;
            game->ball.diry /= len;
        }
        break;

    case GAME_STATE_PLAY:

        if (game->left.input[0])                                            //OM tangenten w är nedtryckt, flyttar man vänstra paddeln uppåt
        {
            game->left.y -= paddleSpeed * deltatime;                        //likt med sig själv -1, paddeln flyttas ned i en takt per uppdateringsperiod, dvs time dependent movement
            if (game->left.y < 0)
                game->left.y = 0;                                            //korrigerar och stoppar paddeln vid y=0 eller toppen av fönstret
        }

        if (game->left.input[1])                                            //OM tangenten s är nedtryckt, flyttar man vänstra paddeln neråt
        {
            game->left.y += paddleSpeed * deltatime;                        //likt med sig själv +paddlespeed, paddeln flyttas ned i en viss hastighet beroende på tiden sedan sista uppdateringen
            if (game->left.y > game->height - 100)
                game->left.y = game->height - 100;                            //korrigerar och stoppar paddeln vid fönsterns höjd - paddelns höjd
        }

        if (game->right.input[0])
        {
            game->right.y -= paddleSpeed * deltatime;
            if (game->right.y < 0)
                game->right.y = 0;
        }

        if (game->right.input[1])
        {
            game->right.y += paddleSpeed * deltatime;
            if (game->right.y > game->height - 100)
                game->right.y = game->height - 100;
        }

        //update ball
        game->ball.x += game->ball.dirx * deltatime * paddleSpeed;
        game->ball.y += game->ball.diry * deltatime * paddleSpeed;

        if (game->ball.y < 0.0f)
        {
            game->ball.y = 0.0f;
            game->ball.diry = -game->ball.diry;
        }

        if (game->ball.y > game->height - 20.0f)
        {
            game->ball.y = game->height - 20.0f;
            game->ball.diry = -game->ball.diry;
        }

        if (game->ball.x < 0.0f)
        {
            game->score1++;
            initialize_paddle(&game->left, 50.0f, game->height / 2 - 50.0f);    //poäng till högerspelaren om bollen når vänstra kanten x = 0.0f
            initialize_paddle(&game->right, game->width - 70.0f, game->height / 2 - 50.0f);
            initialize_ball(&game->ball, game->width / 2 - 10.0f, game->height / 2 - 10.0f);
            game->state = GAME_STATE_PAUSE;
            SDL_Log("Player1: %d Player2: %d", game->score0, game->score1);
        }

        if (game->ball.x > game->width - 20.0f)
        {
            game->score0++;
            initialize_paddle(&game->left, 50.0f, game->height / 2 - 50.0f);    //poäng till högerspelaren om bollen når vänstra kanten x = 0.0f
            initialize_paddle(&game->right, game->width - 70.0f, game->height / 2 - 50.0f);
            initialize_ball(&game->ball, game->width / 2 - 10.0f, game->height / 2 - 10.0f);
            game->state = GAME_STATE_PAUSE;
            SDL_Log("Player1: %d Player2: %d", game->score0, game->score1);

        }

        if (check_collision(&game->left, &game->ball))

        {
            game->ball.dirx = -game->ball.dirx;
        }

        else if (check_collision(&game->right, &game->ball))
        {
            game->ball.dirx = -game->ball.dirx;
        }

        break;
    }

    SDL_Delay(10);                                                        //sist i update-function:en, säger vi gör inget i minst 10s. 

    return true;

}

void draw(Game* game) {
    SDL_SetRenderDrawColor(game->renderer, 
        0x11, 0x22, 0x33, 0xff);                                        //hexadec definitioner av färger
    SDL_RenderClear(game->renderer);                                    //vi rensar skärmen

    // here will be drawing of game
    SDL_SetRenderDrawColor(game->renderer,
        0xff, 0xff, 0xff, 0xff);

    // below an example of drawing left paddle
    {
        SDL_Rect rect = { game->left.x, game->left.y, 20, 100 };        //vi ritar paddelns rörelse
        //SDL_Rect rect = { 50, (game->height / 2) - 50, 20, 100 };
        SDL_RenderFillRect(game->renderer, &rect);                        //pekare till struct:en renderer och height
    }

    // below an example of drawing right paddle
    {
        SDL_Rect rect = { game->right.x, game->right.y, 20, 100 };
        //SDL_Rect rect = { game->width - 60, (game->height / 2) - 50, 20, 100 };
        SDL_RenderFillRect(game->renderer, &rect);
    }

    // below an example of drawing the ball
    {
        SDL_Rect rect = { game->ball.x, game->ball.y, 20, 20 };
        SDL_RenderFillRect(game->renderer, &rect);
    }

    SDL_RenderPresent(game->renderer);                                    //man har två stycken skärmar, den som man visar till användaren, och den som man jobbar på (back buffer)
}


// ,--------------.
// | main function |
// `--------------´
int main(int argc, char* argv[])
{
    // game instance 
    Game game;

    // initialize game
    if (initialize(&game, 1024, 600)) {                                        //om minnesaddressen existerar (vi använder ju en pekare i början)
        // game loop 
        while (update(&game)) {                                                //hämtar input från spelaren, man uppdaterar logiken för spelet
            draw(&game);
        }
    }
    shutdown(&game);

    return 0;
}

Ideally we don’t want to write all of the code in one file. For a small game such as this it was manageable but we definitely will be using different files for different elements going forward.

About Dee Majek

2014  Programming