1
\$\begingroup\$

I'm making a two player pong clone and my problem is that when the second player presses both of his/her buttons at once, the first player locks up and cannot move, even though this does not happen when player one does this. This is pretty much the last kink I have in this clone so I'd like to handle it so I can move on to something new. The code that takes the key strokes looks like this:

void Paddle::events()
{
    //press
        if (player == 1)
        {
            Uint8 *keystates = SDL_GetKeyState(NULL);
            if (keystates[SDLK_o] == 1)
            {
                yVel = -200;
                return;
            }
            if (keystates[SDLK_k] == 1)
            {
                yVel = 200;
                return;
            }
        }
        if (player == 2)
        {
            Uint8 *keystates = SDL_GetKeyState(NULL);
            if (keystates[SDLK_w] == 1)
            {
                yVel = -200;
                return;
            }
            if (keystates[SDLK_s] == 1)
            {
                yVel = 200;
                return;
            }
        }
        //release
        if (player == 1)
        {
            Uint8 *keystates = SDL_GetKeyState(NULL);
            if ((keystates[SDLK_o] == 0) && (keystates[SDLK_k] == 0))
            {
                yVel = 0;
                return;
            }
        }
        if (player == 2)
        {
            Uint8 *keystates = SDL_GetKeyState(NULL);
            if ((keystates[SDLK_w] == 0) && (keystates[SDLK_s] == 0))
            {
                yVel = 0;
                return;
            }
        }
    if (event.type == SDL_QUIT)
    {
        quit = true;
    }
}

I've stewed over it for quite some time, but don't see how it could be doing that. Could it be hardware related?

And as a note, event polling occurs in my main loop, which is supported by a state machine, so that's why you don't see the classic while (SDL_EventPoll (&event)) in there.

I fail to understand how implementing Polls for each player would work withing proper game loop writing, I've been taught only to Poll in the main loop and not in classes or methods. But either way, here is the entirety of the code:

#include "SDL.h"
#include "SDL_image.h"
#include "SDL_ttf.h"
#include "SDL_mixer.h"
#include <iostream>
#include <string>
#include <sstream>
#include <stdlib.h>

/*CLASSES*/

class GameState
{
public:
    virtual void events() = 0;
    virtual void logic() = 0;
    virtual void render() = 0;
    virtual ~GameState(){};
};

class Button
{
public:
    SDL_Rect button_clip[2];
    SDL_Rect button;
    SDL_Surface *button_sprite = NULL;
    Button();
};

class Paddle
{
private:

public:
    int player;
    float x, y, w, h;
    float yVel;
    SDL_Surface *sprite = NULL;
    SDL_Rect clip;
    SDL_Rect *cliping;
    void events();
    void logic(Uint32 deltaticks);
    void render();
    Paddle();
    ~Paddle();
};

class Ball
{
private:
    SDL_Rect clip[1];
public:
    float temp_x;
    float temp_y;
    bool win;
    SDL_Surface *win_message;
    int score_P1;
    int score_P2;
    Paddle *paddle_pointer1;
    Paddle *paddle_pointer2;
    SDL_Rect *collision;
    float x, y, w, h;
    float xVel, yVel;
    SDL_Surface *sprite = NULL;
    SDL_Rect *cliping;
    void events();
    void logic(Uint32 deltaticks);
    void render();
    Ball();
    ~Ball();
};

class Game : public GameState
{
public:
    SDL_Surface *score_message = NULL;
    int *score_P1;
    int *score_P2;
    Paddle PlayerOne;
    Paddle PlayerTwo;
    Ball Game_Ball;
    SDL_Rect clip[1];
    void events();
    void logic();
    void render();
    Game();
    ~Game();
};

class Title : public GameState
{
private:
    SDL_Surface *Title_Message = NULL;
    SDL_Rect *clip;
    Button Title_Button;
public:
    void events();
    void logic();
    void render();
    Title();
    ~Title();
};

class Timer
{
private:
    //The clock time when the timer started
    int startTicks;

    //The ticks stored when the timer was paused
    int pausedTicks;

    //The timer status
    bool paused;
    bool started;

public:
    //Initializes variables
    Timer();

    //The various clock actions
    void start();
    void stop();
    void pause();
    void unpause();

    //Gets the timer's time
    int get_ticks();

    //Checks the status of the timer
    bool is_started();
    bool is_paused();
};


/*CONSTANTS AND GLBALS*/
const int SCREEN_WIDTH = 700;
const int SCREEN_HEIGHT = 500;
const int SCREEN_BPP = 32;
const int FPS = 100;

/*VARS*/
bool quit = false;

/*GAMESTATES*/
enum States
{
    S_NULL,
    TITLE,
    GAME,
    WIN,
    EXIT,
};

/*SURFACES*/
SDL_Surface *screen = NULL;

/*FONTS*/
SDL_Color color = { 255, 255, 255 };
TTF_Font *font = NULL;

//STATE VARS
GameState *currentState = NULL;
int stateID = NULL;
int nextState = NULL;

//SDL event
SDL_Event event;

//timer
Timer delta;

//key
Uint8 *keystates = SDL_GetKeyState(NULL);

/*FUNCTIONS*/
SDL_Surface *load_image(std::string filename)
{
    //loaded image
    SDL_Surface* loadedImage = NULL;
    //optimized surface
    SDL_Surface* optimizedImage = NULL;
    //load image
    loadedImage = IMG_Load(filename.c_str());
    //if image loaded
    if (loadedImage != NULL)
    {
        //Create optimized image
        optimizedImage = SDL_DisplayFormat(loadedImage);
        //free old image
        SDL_FreeSurface(loadedImage);
        //if optimized
        if (optimizedImage != NULL)
        {
            //map color key
            Uint32 colorkey = SDL_MapRGB(optimizedImage->format, 0, 0, 0);
            //set all pixles of color 0,0,0 to be transparent
            SDL_SetColorKey(optimizedImage, SDL_SRCCOLORKEY, colorkey);
        }
    }
    return optimizedImage;
}

//apply image
void apply_surface(int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL)
{
    //temp rect
    SDL_Rect offset;
    //offsets
    offset.x = x;
    offset.y = y;
    //blit
    SDL_BlitSurface(source, clip, destination, &offset);
}

//initiate SDL etc
bool init()
{
    //initialize all SDL subsystems
    if (SDL_Init(SDL_INIT_EVERYTHING) == -1)
    {
        return false;
    }
    //set up screen
    screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);
    if (screen == NULL)
    {
        return false;
    }
    //check screen
    if (screen == NULL)
    {
        return false;
    }
    //init TTF
    if (TTF_Init() == -1)
    {
        return false;
    }
    //set window caption
    SDL_WM_SetCaption("PONG", NULL);
    //if evetything worked
    return true;
}

//load files
bool load_files()
{
    font = TTF_OpenFont("SF Pixelate.ttf", 45);
    if (font == NULL)
    {
        return false;
    }
    return true;
}

//quit
void clean_up()
{
    //delete game state
    //free image

    //quit ttf
    TTF_CloseFont(font);
    TTF_Quit();
    //quit SDL
    SDL_Quit();
}

// STATE MACHINE FUNCTIONS //
void set_next_state(int newState)
{
    if (nextState != EXIT)
    {
        nextState = newState;
    }
}

void change_state()
{
    if (nextState != S_NULL)
    {
        //change state
        switch (nextState)
        {
        case GAME:
            currentState = new Game();
            break;
        }
        //change state
        stateID = nextState;
        //null nextState
        nextState = S_NULL;
    }
}

/*CLASS DEFINITIONS*/

Timer::Timer()
{
    //Initialize the variables
    startTicks = 0;
    pausedTicks = 0;
    paused = false;
    started = false;
}

void Timer::start()
{
    //Start the timer
    started = true;

    //Unpause the timer
    paused = false;

    //Get the current clock time
    startTicks = SDL_GetTicks();
}

void Timer::stop()
{
    //Stop the timer
    started = false;

    //Unpause the timer
    paused = false;
}

void Timer::pause()
{
    //If the timer is running and isn't already paused
    if ((started == true) && (paused == false))
    {
        //Pause the timer
        paused = true;

        //Calculate the paused ticks
        pausedTicks = SDL_GetTicks() - startTicks;
    }
}

void Timer::unpause()
{
    //If the timer is paused
    if (paused == true)
    {
        //Unpause the timer
        paused = false;

        //Reset the starting ticks
        startTicks = SDL_GetTicks() - pausedTicks;

        //Reset the paused ticks
        pausedTicks = 0;
    }
}

int Timer::get_ticks()
{
    //If the timer is running
    if (started == true)
    {
        //If the timer is paused
        if (paused == true)
        {
            //Return the number of ticks when the timer was paused
            return pausedTicks;
        }
        else
        {
            //Return the current time minus the start time
            return SDL_GetTicks() - startTicks;
        }
    }

    //If the timer isn't running
    return 0;
}

bool Timer::is_started()
{
    return started;
}

bool Timer::is_paused()
{
    return paused;
}

Button::Button()
{

}

Title::Title()
{
    //text/sprites
    Title_Message = TTF_RenderText_Solid(font, "PONG", color);
    Title_Button.button_sprite = load_image("Start.png");
    //region for button
    Title_Button.button.x = 300;
    Title_Button.button.y = 350;
    Title_Button.button.w = 100;
    Title_Button.button.h = 50;
    //clips not hover
    Title_Button.button_clip[0].x = 0;
    Title_Button.button_clip[0].y = 0;
    Title_Button.button_clip[0].w = 100;
    Title_Button.button_clip[0].h = 50;
    //clips hover
    Title_Button.button_clip[1].x = 0;
    Title_Button.button_clip[1].y = 50;
    Title_Button.button_clip[1].w = 100;
    Title_Button.button_clip[1].h = 50;
}

Title::~Title()
{
    SDL_FreeSurface(Title_Message);
    SDL_FreeSurface(Title_Button.button_sprite);
}

void Title::events()
{
    int x = 0;
    int y = 0;
        if (event.type == SDL_MOUSEMOTION)
        {
            x = event.motion.x;
            y = event.motion.y;
            if ((x > Title_Button.button.x) && (x < (Title_Button.button.x + Title_Button.button.w)) && (y > Title_Button.button.y) && (y < (Title_Button.button.y + Title_Button.button.h)))
            {
                clip = &Title_Button.button_clip[1];
            }
            else
            {
                clip = &Title_Button.button_clip[0];
            }
        }
        if (event.type == SDL_QUIT)
        {
            quit = true;
        }
        if (event.type == SDL_MOUSEBUTTONDOWN)
        {
            if (event.button.button == SDL_BUTTON_LEFT)
            {
                x = event.motion.x;
                y = event.motion.y;
                if ((x > Title_Button.button.x) && (x < (Title_Button.button.x + Title_Button.button.w)) && (y > Title_Button.button.y) && (y < (Title_Button.button.y + Title_Button.button.h)))
                {
                    set_next_state(GAME);
                }
            }
        }

}

void Title::logic()
{

}

void Title::render()
{
    apply_surface(Title_Button.button.x, Title_Button.button.y, Title_Button.button_sprite, screen, clip);
    apply_surface((SCREEN_WIDTH - Title_Message->w) / 2, 100, Title_Message, screen);
}

Paddle::Paddle()
{

}

Paddle::~Paddle()
{

}

void Paddle::events()
{
    //press
        if (player == 1)
        {
            Uint8 *keystates = SDL_GetKeyState(NULL);
            if (keystates[SDLK_o] == 1)
            {
                yVel = -200;
                return;
            }
            if (keystates[SDLK_k] == 1)
            {
                yVel = 200;
                return;
            }
        }
        if (player == 2)
        {
            Uint8 *keystates = SDL_GetKeyState(NULL);
            if (keystates[SDLK_w] == 1)
            {
                yVel = -200;
                return;
            }
            if (keystates[SDLK_s] == 1)
            {
                yVel = 200;
                return;
            }
        }
        //release
        if (player == 1)
        {
            Uint8 *keystates = SDL_GetKeyState(NULL);
            if ((keystates[SDLK_o] == 0) && (keystates[SDLK_k] == 0))
            {
                yVel = 0;
                return;
            }
        }
        if (player == 2)
        {
            Uint8 *keystates = SDL_GetKeyState(NULL);
            if ((keystates[SDLK_w] == 0) && (keystates[SDLK_s] == 0))
            {
                yVel = 0;
                return;
            }
        }
    if (event.type == SDL_QUIT)
    {
        quit = true;
    }
}

void Paddle::logic(Uint32 deltaticks)
{
    y += yVel * (deltaticks / 1000.f);
    if (y < 0)
    {
        y = 0;
    }
    else if (y > SCREEN_HEIGHT - h)
    {
        y = SCREEN_HEIGHT - h;
    }
}

void Paddle::render()
{
    apply_surface((int)x, (int)y, sprite, screen, cliping);
}

Ball::Ball()
{
    sprite = load_image("Game_sprite.png");
    clip[0].x = 0;
    clip[0].y = 0;
    clip[0].w = 20;
    clip[0].h = 20;
    cliping = &clip[0];
    win = false;
    x = 340;
    y = 240;
    w = 20;
    h = 20;
    yVel = 0;
    xVel = 200;
}

Ball::~Ball()
{

}

void Ball::events()
{
    if (event.type == SDL_QUIT)
    {
        quit = true;
    }
}

void Ball::logic(Uint32 deltaticks)
{
    temp_x = x + xVel * (deltaticks / 1000.f);
    temp_y = y + yVel * (deltaticks / 1000.f);
    if (x < 0)
    {
        score_P1 += 1;
        x = 340;
        y = 240;
        yVel = 200;
        xVel = -170;
        SDL_Delay(1000);
        return;
    }
    if (x > SCREEN_WIDTH - w)
    {
        score_P2 += 1;
        x = 340;
        y = 240;
        yVel = 200;
        xVel = 170;
        SDL_Delay(1000);
        return;
    }
    if ((temp_y < 0) || (y + w > SCREEN_HEIGHT))
    {
        yVel = -yVel;
        y += yVel * (deltaticks / 1000.f);
        return;
    }

    //standard collisions
    //P1
    if (((temp_x <= (paddle_pointer1->x + paddle_pointer1->w)) && ((temp_x + w) >= paddle_pointer1->x)) && (((temp_y + h) >= paddle_pointer1->y) && (temp_y <= (paddle_pointer1->y + paddle_pointer1->h))))
    {
        if (xVel > 0)
        {
            xVel += 30;
        }
        else
        {
            xVel -= 30;
        }
        if (yVel >= 0)
        {
            if (paddle_pointer2->yVel > 0)
            {
                yVel += 50;
                xVel = -xVel;
            }
            else
            {
                yVel += 50;
                yVel = -yVel;
                xVel = -xVel;
            }
            x -= paddle_pointer2->yVel * (deltaticks / 1000.f);
            return;
        }
        if (yVel <= 0)
        {
            if (paddle_pointer2->yVel > 0)
            {
                yVel += 50;
                yVel = -yVel;
                xVel = -xVel;
            }
            else
            {
                yVel += 50;
                xVel = -xVel;
            }
            x += paddle_pointer2->yVel * (deltaticks / 1000.f);
            return;
        }
    }
    //P2
    if (((temp_x <= (paddle_pointer2->x + paddle_pointer2->w)) && ((temp_x + w) >= paddle_pointer2->x)) && (((temp_y + h) >= paddle_pointer2->y) && (temp_y <= (paddle_pointer2->y + paddle_pointer2->h))))
    {
        if (xVel > 0)
        {
            xVel += 30;
        }
        else
        {
            xVel -= 30;
        }
        if (yVel >= 0)
        {
            if (paddle_pointer2->yVel > 0)
            {
                yVel += 50;
                xVel = -xVel;
            }
            else
            {
                yVel += 50;
                yVel = -yVel;
                xVel = -xVel;
            }
            x -= paddle_pointer2->yVel * (deltaticks / 1000.f);
            return;
        }
        if (yVel <= 0)
        {
            if (paddle_pointer2->yVel > 0)
            {
                yVel += 50;
                yVel = -yVel;
                xVel = -xVel;
            }
            else
            {
                yVel += 50;
                xVel = -xVel;
            }
            x += paddle_pointer2->yVel * (deltaticks / 1000.f);
            return;
        }
    }
    y += yVel * (deltaticks / 1000.f);
    x += xVel * (deltaticks / 1000.f);
}

void Ball::render()
{
    if (score_P1 == 11)
    {
        win_message = TTF_RenderText_Solid(font, "Player One Wins", color);
        apply_surface((SCREEN_WIDTH - win_message->w) / 2, (SCREEN_HEIGHT - win_message->h) / 2, win_message, screen);
        win = true;
    }
    else if (score_P2 == 11)
    {
        win_message = TTF_RenderText_Solid(font, "Player Two Wins", color);
        apply_surface((SCREEN_WIDTH - win_message->w) / 2, (SCREEN_HEIGHT - win_message->h) / 2, win_message, screen);
        win = true;
    }
    else
    {
        apply_surface((int)x, (int)y, sprite, screen, cliping);
    }
}

Game::Game()
{
    //clip sprite
    //paddle
    clip[0].x = 0;
    clip[0].y = 20;
    clip[0].w = 20;
    clip[0].h = 100;
    //ball
    clip[1].x = 0;
    clip[1].y = 0;
    clip[1].w = 20;
    clip[1].h = 20;
    //player one
    PlayerOne.sprite = load_image("Game_sprite.png");
    PlayerOne.clip.x = 0;
    PlayerOne.clip.y = 20;
    PlayerOne.clip.w = 20;
    PlayerOne.clip.h = 100;
    PlayerOne.x = 680;
    PlayerOne.y = 200;
    PlayerOne.w = 20;
    PlayerOne.h = 100;
    PlayerOne.cliping = &clip[0];
    PlayerOne.yVel = 0;
    PlayerOne.player = 1;
    //player two
    PlayerTwo.sprite = load_image("Game_sprite.png");
    PlayerTwo.clip.x = 0;
    PlayerTwo.clip.y = 20;
    PlayerTwo.clip.w = 20;
    PlayerTwo.clip.h = 100;
    PlayerTwo.x = 0;
    PlayerTwo.y = 200;
    PlayerTwo.w = 20;
    PlayerTwo.h = 100;
    PlayerTwo.cliping = &clip[0];
    PlayerTwo.yVel = 0;
    PlayerTwo.player = 2;
//pointers
    Game_Ball.paddle_pointer1 = &PlayerOne;
    Game_Ball.paddle_pointer2 = &PlayerTwo;
    score_P1 = &Game_Ball.score_P1;
    score_P2 = &Game_Ball.score_P2;
}

Game::~Game()
{
    SDL_FreeSurface(PlayerOne.sprite);
    SDL_FreeSurface(PlayerTwo.sprite);
}

void Game::events()
{
    Game_Ball.events();
    PlayerOne.events();
    PlayerTwo.events();
}

void Game::logic()
{
    Game_Ball.logic(delta.get_ticks());
    PlayerOne.logic(delta.get_ticks());
    PlayerTwo.logic(delta.get_ticks());
}

void Game::render()
{
    std::stringstream score_current;
    score_current << *score_P2 << " : " << *score_P1;
    score_message = TTF_RenderText_Solid(font, score_current.str().c_str(), color);
    SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0, 0, 0));
    apply_surface((SCREEN_WIDTH - score_message->w) / 2, 0, score_message, screen);
    if (Game_Ball.win == false)
    {
        Game_Ball.render();
        PlayerOne.render();
        PlayerTwo.render();
    }
    if (Game_Ball.win == true)
    {
        PlayerOne.render();
        PlayerTwo.render();
        SDL_Flip(screen);
        SDL_Delay(2000);
        set_next_state(TITLE);
    }
}

/*MAIIIIIIN*/
int main(int argc, char* args[])
{
    Timer fps;
    //init SDL
    if (init() == false)
    {
        return 1;
    }
    //load everything
    if (load_files() == false)
    {
        return 1;
    }
    delta.start();
    currentState = new Title();
    while (quit == false)
    {
        //Keystates
        Uint8 *keystates = SDL_GetKeyState(NULL);
        //handle state events
        while (SDL_PollEvent(&event))
        {
            currentState->events();
        }
        // do state logic
        currentState->logic();
        //timer reset
        delta.start();
        //change state if needed
        change_state();
        //render state 
        currentState->render();
        if (SDL_Flip(screen) == -1)
        {
            return 1;
        }
        if (fps.get_ticks() < (1000 / FPS))
        {
            SDL_Delay((1000 / FPS) - fps.get_ticks());
        }
    }
    clean_up();
    return 0;
}

Hopefully this makes it clear the way I'm implementing the event handling.

\$\endgroup\$
  • \$\begingroup\$ what does the yVel variable do? I notice it is being set for both players \$\endgroup\$ – Matthew Pigram Jan 10 '14 at 5:15
  • \$\begingroup\$ Y axis velocity. Up or down. \$\endgroup\$ – knefcy Jan 10 '14 at 5:17
  • \$\begingroup\$ also shouldn't each players events be in their own event function i.e. instead of events() you should have player1Events() and player2Events() with an SDL_EventPoll() call for each. I am almost certain this is the problem \$\endgroup\$ – Matthew Pigram Jan 10 '14 at 5:17
  • \$\begingroup\$ It's in one class so that the Game() class can call and manage two different instances of one class. \$\endgroup\$ – knefcy Jan 10 '14 at 5:20
  • \$\begingroup\$ I can post more code if it makes it easier to find the problem. It's only about 850 lines right now. \$\endgroup\$ – knefcy Jan 10 '14 at 5:24
4
\$\begingroup\$

You may be dealing with keyboard ghosting, the phenomenon where certain keys cannot be pressed down simultaneously. This differs between different keyboards, but you can test it out in the link I've provided, which has an interactive program for testing key presses.

You may be able to fix this problem by finding different keys that can be pressed together, or using different devices or even multiple keyboards. You could also try more expensive keyboards; a lot of gaming keyboards advertise "anti-ghosting" features.

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ Holy crap, you're right. How bizarre. I'll just use different keys I guess. Thank you! \$\endgroup\$ – knefcy Jan 10 '14 at 5:50
  • \$\begingroup\$ @knefcy And make the keys configurable. Remember that which keys will interfere with each other will likely be different on different keyboards, so setting it up so that it works on your keyboard won't necessarily make it work on someone else's keyboard! \$\endgroup\$ – Trevor Powell Jan 11 '14 at 1:16
-3
\$\begingroup\$

You need a keyboard with n-key rollover

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.