2
\$\begingroup\$

I have written a pong game in C++ using SDL2.0 for the graphics. I have a couple of questions regarding this code:

  1. How can I make the AI better, in such a way that it is actually dumber? Right now, my AI can return the ball to me every time, which is unfair. I want the player to be able to score at least some of the time. Some sort of percentage of error variable might do the trick, not sure.

  2. How can I fix the ugliness of some of the functions? Currently, my load_rendered_text() function takes 2 things of text and returns 2 textures, when it should only take 1 thing of text for 1 texture, and be called twice. Also, my Ball::move_ball() function has more than 10 if statements, checking various conditions. How would one go about fixing that?

  3. Am I using too many comments? Not enough comments? I generally try and aim for self documenting code. Meaning that the code should tell you what its doing without the need for comments, most of the time. To achieve this I've tried my best to use consistent naming, and easy to understand variable names. Let me know if you spot any naming errors or things that could be better in this area.

Pong game code

class Ball
{
public:
    const int BALL_WIDTH = 15;
    const int BALL_HEIGHT = 15;
    const float BALL_VELOCITY = 2;

    Ball();
    void init_ball_direction();
    void move_ball(float timeStep, SDL_Rect playerPaddle, SDL_Rect aiPaddle);
    void render_ball();

private:
    float ballPositionX, ballPositionY;
    float ballVelocityX, ballVelocityY;
    bool isMovingDown, isMovingUp;
    bool isMovingLeft, isMovingRight;

    SDL_Rect bCollider;
};

...

class AI
{
public:
    const float AI_VELOCITY = 3;

    void init_ai();
    void move_ai();
    void handle_ai_events();

    SDL_Rect aiPaddle;

private:
    float aiYPosition;
    float aiVelocity;
};

...

void Ball::move_ball(float timeStep, SDL_Rect playerPaddle, SDL_Rect aiPaddle)
{
    //move the ball, its collider, and the raycast. Check for collisions with the paddles
    //by calling the check_collision() function.
    if(ballPositionY + BALL_HEIGHT > SCREEN_HEIGHT){
        isMovingDown = false;
        isMovingUp = true;
    }

    if(ballPositionY < 0){
        isMovingDown = true;
        isMovingUp = false;
    }

    if(ballPositionX + BALL_WIDTH > SCREEN_WIDTH){
        ballPositionX = SCREEN_WIDTH/2;
    }

    if(ballPositionX < 0){
        ballPositionX = SCREEN_WIDTH/2;
    }

    if(check_collision(bCollider, playerPaddle)){
        isMovingLeft = false;
        isMovingRight = true;
    }

    if(check_collision(bCollider, aiPaddle)){
        isMovingRight = false;
        isMovingLeft = true;
    }

    if(isMovingUp){
        ballPositionY -= ballVelocityY;
        bCollider.y = ballPositionY;
        bRaycastLine.y = ballPositionY;
    }

    if(isMovingDown){
        ballPositionY += ballVelocityY;
        bCollider.y = ballPositionY;
        bRaycastLine.y = ballPositionY;
    }

    if(isMovingLeft){
        ballPositionX -= ballVelocityX;
        bCollider.x = ballPositionX;
        bRaycastLine.x = ballPositionX;
    }

    if(isMovingRight){
        ballPositionX += ballVelocityX;
        bCollider.x = ballPositionX;
        bRaycastLine.x = ballPositionX;
    }
}

...

void AI::init_ai()
{
    //initialize the AI, not much here becaue the AI is very basic.
    aiPaddle.h = 40;
    aiPaddle.w = 10;
    aiPaddle.x = SCREEN_WIDTH - aiPaddle.w;
    aiPaddle.y = SCREEN_HEIGHT/2 - aiPaddle.h/2;

    aiYPosition = aiPaddle.y;
    aiVelocity = 0;
}

void AI::handle_ai_events()
{
    //check if the raycasted line is above/below the AI paddle...
    if(bRaycastLine.y > aiPaddle.y + aiPaddle.h){
        aiVelocity += AI_VELOCITY;
    } else if(bRaycastLine.y < aiPaddle.y){
        aiVelocity -= AI_VELOCITY;
    }
}

void AI::move_ai()
{
    //apply the velocities to the AI...
    aiYPosition = aiVelocity;
    aiPaddle.y = aiYPosition;

    if(aiYPosition < 0){
        aiYPosition -= aiVelocity;
        aiPaddle.y = aiYPosition;
    }

    if(aiYPosition > aiPaddle.y + SCREEN_HEIGHT){
        aiYPosition += aiVelocity;
        aiPaddle.y = aiYPosition;
    }
}

bRaycastLine is an SDL_Rect that follows the position of the ball.

\$\endgroup\$
  • \$\begingroup\$ Why do you have occasional ...'s in your code? \$\endgroup\$ – SirPython Feb 13 '15 at 0:58
2
\$\begingroup\$

One way to make an AI 'dumber' is to instead give it a handicap. You could limit its movement speed until it doesn't always win.

Another option is to randomize the ball trajectory after it bounces, and then give the AI limited information about the ball trajectory, but rather represent it as a collection of potential states, which gets narrowed down as the ball gets closer to the AI paddle.

In order to use an approach like this, you need to make the AI smarter first, so it can do trajectory prediction, and still be able to respond realistically with limited information.

Needless to say, this is harder.

\$\endgroup\$
  • \$\begingroup\$ Hm, I was thinking about somehow limiting its movement speeds. Maybe adding a delay before moving the AI? I could also try only moving the AI if the ball is past the center of the screen, and slowing the ai down by a certain percentage. Will post back if that does the trick. \$\endgroup\$ – JohnBobSmith Dec 28 '14 at 21:52
1
\$\begingroup\$

It's not clear to me whether you need to store the variables isMovingLeft... etc. Assuming that you don't, you can make the function move_ball a little bit simpler.

void Ball::move_ball(float timeStep, SDL_Rect playerPaddle, SDL_Rect aiPaddle)
{
   int upDown = 0;
   int leftRight = 0;

   //move the ball, its collider, and the raycast. Check for collisions with the paddles
   //by calling the check_collision() function.
   if(ballPositionY + BALL_HEIGHT > SCREEN_HEIGHT){
      upDown = 1;
   }

   if(ballPositionY < 0){
      upDown = -1;
   }

   if(ballPositionX + BALL_WIDTH > SCREEN_WIDTH){
      ballPositionX = SCREEN_WIDTH/2;
   }

   if(ballPositionX < 0){
      ballPositionX = SCREEN_WIDTH/2;
   }

   if(check_collision(bCollider, playerPaddle)){
      leftRight = 1;
   }

   if(check_collision(bCollider, aiPaddle)){
      leftRight = -1;
   }

   ballPositionY -= upDown*ballVelocityY;;
   ballPositionX -= leftRight*ballVelocityX;

   if ( upDown != 0 ) {
      bCollider.y = ballPositionY;
      bRaycastLine.y = ballPositionY;
   }

   if ( leftRight != 0 ) {
      bCollider.x = ballPositionX;
      bRaycastLine.x = ballPositionX;
   }

}
\$\endgroup\$
  • \$\begingroup\$ Hm... How does one change the position of the ball though? Right now, you only subtract the velocities from the position with ballPositionX -= leftRight*ballVelocityX; and likewise for the Y velocity. So the ball always moves left and up (remember SDL y-coordinates are inverted). Also, I seem to loose the ability to randomize direction at the start of the game. Any ideas? \$\endgroup\$ – JohnBobSmith Dec 29 '14 at 16:25
  • \$\begingroup\$ The positions are set under the if (upDown != 0) and if ( leftRight != 0 ) blocks. \$\endgroup\$ – R Sahu Dec 29 '14 at 16:27
  • \$\begingroup\$ Right, but since you always subtract the velocities (and never add them) the ball position is always negative and hence goes to the top left of the screen. What if I want to move down or to the right? One would need to add the velocity on the x/y axis. Also, the leftRight and upDown variables can never equal 0 in this context, unless the ball moves off screen. So your conditions are always true. \$\endgroup\$ – JohnBobSmith Dec 29 '14 at 16:31
  • \$\begingroup\$ When upDown is -1, the subtraction term will effectively do an add. Same with leftRight. \$\endgroup\$ – R Sahu Dec 29 '14 at 16:32
  • \$\begingroup\$ Oh, I made a mistake. I miss read your code. upDown and leftRight are the directions of the ball, and not a second position variable. Perhaps a better name for them like isMovingUpDown (likewise for left/right) would be better? Also, is there a way to cut down on more of those if statements? We still have 8 of them, which is better than 10, but it still seems like quite a lot of if statements. \$\endgroup\$ – JohnBobSmith Dec 29 '14 at 16:39
0
\$\begingroup\$

A few things that come in mind :

  • Limited velocity : that seems to be already implemented, but you can always decrease the value.
  • Response time : if you want to be close to what a human do, then your AI must have a time of reaction greater than 0. One simple implementation would be to start moving when the ball reaches a certain Y value (if you play from top to bottom, and Y axis is vertical of course).
  • Prediction : one thing would be to put a bias to the desired position of your AI. Something realistic may be to set the final destination of your AI somewhere in a gaussian distribution around the expected position of the ball. As mentioned by someone else, the gaussian shape can get narrower as the ball gets closer (which is what a human being does, he roughly estimates first then narrow it down as the ball gets closer). Here to help you with the implementation : normal distribution in C++
\$\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.