Sign in to follow this  
neob

C++ Pong

Recommended Posts

I made this pong game in allegro c++. It compiles and works well for my standards. I’m posting so all the great minds here can judge it and tell me what I did wrong and what can make it better. EDITED:: Right now I’m trying to do this. How can I make the ball bounce off the paddle with a more realistic feeling? For example I’m doing it with rand, shouldn’t the ball be affected by the direction the paddle was moving before it hit it? Thank you for your time. Main.cpp
#include "Pong.h"

int main() 
{
    allegro_init();
    install_keyboard();
                     
    Pong P;
    P.setGame();

    return 0;
}
END_OF_MAIN()


Pong.h
#ifndef H_Pong
#define H_Pong

#include <allegro.h>
#include <cstdlib>
#include <time.h>

class Pong
{
  public:     
   BITMAP *buffer; 
   time_t secs;      
         
   int ball_x;
   int ball_y;
   
   int ball_tempX;
   int ball_tempY;

   int AI_x;
   int AI_y;

   int AI_tempX;
   int AI_tempY;

   int p2_x;
   int p2_y;

   int p2_tempX;
   int p2_tempY;
         
   int speed; // for ball speed
   
   int hits;
   
   int AIScore;
   int p2Score;
   
   // Keeps track of circles direction
   // 1 = up and left, 2 = down and left, 3 = up and right, 4 = down and right              
   int dir; 

   void setGame();
   void startNew(); 
   void AIMove();      // Player 1
   void p2Move();      // Player 2
   void moveBall();    // Controls ball
   void checkWinner(); // Checks for winner and points scored 
   void menu();        // Updates score and hits

   // Constructor 
   Pong();
};
#endif


Pong.cpp
#include "Pong.h"

void Pong::setGame()
{
    ball_x = 400;
    ball_y = 240;
   
    ball_tempX = 400;
    ball_tempY = 240;

    AI_x = 31;
    AI_y = 255;

    AI_tempX = 31;
    AI_tempY = 255;

    p2_x = 753;
    p2_y = 255;
    
    p2_tempX = 753;
    p2_tempY = 255;

    speed = 1; 
   
    hits = 0;
   
    AIScore = 0;
    p2Score = 0;

    set_color_depth(16);
    set_gfx_mode( GFX_AUTODETECT, 800, 600, 0, 0);
    
    buffer = create_bitmap( 800, 600 ); 

    acquire_screen();
    // background
    clear_to_color ( buffer, makecol( 197, 219, 191 ));
           
    draw_sprite ( screen, buffer, 0, 0 );
    release_screen();
    
    time(&secs);
    srand( (unsigned int)secs);
    dir = rand() % 4 + 1;  
    
    // Check for key press to exit
    while( !key[KEY_ESC])
         {   
          AIMove();
          p2Move();
          moveBall();
          checkWinner();
         }      
}  

void Pong::startNew()
{
     // comment this later
    clear_keybuf();
    readkey();
    
    // background
    clear_to_color ( buffer, makecol ( 197, 219, 191 ));
    // resets ball back to the middle
    ball_x = 400;
    ball_y = 240;

    // resets paddles after victory.
    AI_x = 31;
    AI_y = 255;

    p2_x = 753;
    p2_y = 255;
    
    speed = 1;
    
    hits = 0;
}    

// Controls the ball
void Pong::moveBall()
{
    ball_tempX = ball_x;
    ball_tempY = ball_y;

   // Hits test for ball speed    
    if (hits >= 30)
       {
        speed = 5;
       }
    else 
        if (hits >= 20)
           {
            speed = 4;
           }   
         else 
             if (hits >= 10)
                {
                 speed = 3;
                }
             else 
                 if (hits >= 5)
                    {
                     speed = 2;
                    }

    if (( dir == 1 ) && ( ball_x > 5 )&& ( ball_y > 5 ))
       {
         if (( ball_x == AI_x + 15 ) && ( ball_y >= AI_y ) && ( ball_y <= AI_y + 60 ))
            {
              dir = rand() % 2 + 3;
              
              hits++;
            }
         else
            {    
             ball_x -= speed;
             ball_y -= speed;
            }       
       }
        
    else 
        if (( dir == 2 ) && ( ball_x > 5 ) && ( ball_y < 600 ))
           {
            if (( ball_x == AI_x + 15 ) && ( ball_y >= AI_y ) && ( ball_y <= AI_y + 60 ))
               {
                dir = rand()% 2 + 3;
                
                hits++;
               }
            else
               {    
                  ball_x -= speed;
                  ball_y += speed;
               }
            } 
            
        else 
            if (( dir == 3 ) && ( ball_x < 800 ) && ( ball_y > 5 ))
               {
                if(( ball_x + 5 == p2_x ) && ( ball_y >= p2_y )&& ( ball_y <= p2_y + 60 ))
                  {
                   dir = rand()% 2 + 1;
                   
                   hits++;
                  }
                else
                   {    
                    ball_x += speed;
                    ball_y -= speed;
                   }
                } 
                
            else 
                if ((dir == 4 ) && ( ball_x < 800 ) && ( ball_y < 600 ))
                   {
                    if(( ball_x + 5 == p2_x ) && ( ball_y >= p2_y ) && ( ball_y <= p2_y + 60 ))
                      {
                       dir = rand()% 2 + 1;
                       
                       hits++;
                      }
                    else
                        {   
                          ball_x += speed;
                          ball_y += speed;
                        }
                     } 
                     
                  else 
                     { 
                      if (( dir == 1 ) || ( dir == 3 ))    
                         {
                          ++dir;
                         }
                      else 
                          if (( dir == 2 ) || ( dir == 4 ))
                             { 
                              --dir;
                             }
                      }    
    
    acquire_screen();
    circlefill ( buffer, ball_tempX, ball_tempY, 5, makecol( 197, 219, 191 ));
    circlefill ( buffer, ball_x, ball_y, 5, makecol( 22, 136, 11));
    draw_sprite( screen, buffer, 0, 0);
    release_screen();
    
    // Updates menu
    menu();
    
    rest(5);
}    

// Player 1
void Pong::AIMove()
{
    AI_tempY = AI_y;
       
    //if on cpu's side of court
    if (( ball_x < 391 ) && ( AI_y > 0 ))
       { 
        // 391?? i could gmake it 400 but 
        //this makes it m,ore natural
        
        //attempt to position in the middle 
        if ( AI_y < 183 )
           {
            AI_y +=4;
           }
        
        if ( AI_y > 217 )
           {
            AI_y -= 4;
           }
       }
    
    //if ball is close to player
    if (( ball_x < 117 ) && ( AI_y < 540 ))
       {
        // attempt y-axis allignment
        if ( AI_y < ball_y )
        {
         AI_y += 10;
        }
        
        if ( AI_y > ball_y )
           {
            AI_y -= 10;
           }
       }
    
    acquire_screen();
    rectfill( buffer, AI_tempX, AI_tempY, AI_tempX + 10, AI_tempY + 60, makecol ( 197, 219, 191 ));
    rectfill( buffer, AI_x, AI_y, AI_x + 10, AI_y + 60, makecol ( 218, 173, 50 ));
    release_screen(); 
}  

// Player 2
void Pong::p2Move()
{
    p2_tempY = p2_y;
 
    if (( key[KEY_UP] ) && ( p2_y > 0 ))
       {
        //--p2_y;  
        p2_y -= 3;    
       }
    
    else 
        if (( key[KEY_DOWN] ) && ( p2_y < 540 ))
           {
            //++p2_y;  
            p2_y += 3;  
           }     
    
    acquire_screen();
    rectfill( buffer, p2_tempX, p2_tempY, p2_tempX + 10, p2_tempY + 60, makecol ( 197, 219, 191 ));
    // actual player 2
    rectfill( buffer, p2_x, p2_y, p2_x + 10, p2_y + 60, makecol ( 29, 98, 255));
    release_screen();      
}    

// Checks for winner
void Pong::checkWinner()
{
    if ( AIScore == 11 )
       {
        textprintf_centre_ex(screen, font, 400, 250, makecol(0,0,255),-1, "GAME OVER!!\n\n PLAYER 1 WINS!");
        clear_keybuf();
        readkey();
        AIScore = 0;
       }
        
    else
        if ( p2Score == 11 )
           {
            textprintf_centre_ex(screen, font, 400, 300, makecol(255,0,0),-1, "GAME OVER!!\n\n PLAYER 2 WINS!");
            clear_keybuf();
            readkey();
            p2Score = 0;
           }

    // Checks for goal score
    if ( ball_x > p2_x + 20 )
       {
        AIScore++; 
        startNew();
       }   
    else 
        if ( ball_x < AI_x - 20 )
           {
            p2Score++;
            startNew();
           }  
}    

void Pong::menu()
{
    textprintf_centre_ex(screen, font, 50, 10, makecol(218, 173, 50),-1, "Score: %d", AIScore);
    textprintf_centre_ex(screen, font, 750, 10, makecol(29, 98, 255),-1, "Score: %d", p2Score);
    textprintf_centre_ex(screen, font, 400, 10, makecol(22, 136, 11),-1, "Hits: %d", hits);
}

Pong::Pong()
{
}


[Edited by - neob on June 6, 2007 6:44:10 PM]

Share this post


Link to post
Share on other sites
Looks very good![smile]

I do have three suggestions though:


  • Your constructor should initialize all membersof the class


  • Initializating members (Espically pointers) can dramtically reduce the number
    of potential problems.

  • You should try avoiding the use of "Magic Numbers"


  • In C++, you should use either enumerations or constants
    in place of numbers. For example:, instead of seeing:

    ball_x = 400;
    ball_y = 240;

    Readers of your code can see:

    ball_x = SCREENX/2;
    ball_y = SCREENY/2;

    SCREENX and SCREENY could be declared in the class like:

    const int SCREENX = 800;
    const int SCREENY = 480;

    Because they are constants, the compilier can optomize the divide out of the
    code (So it effectivley just assigns the varables).

    Two benifits of doing this is to increase readbility, and make changes
    easier as changing SCREENX will change all code that indirectly refrences it.

  • Temporary varables shouldnt be not be a class member.


  • If you need to store the temporary varable for longer then a function call,
    make it static:

    void foo () {

    static int i=0;
    std::cout << i++ << std::endl;
    }

    // prints numbers 0 to 9
    for (int i=0; i<10; i++)
    foo ();


    If you need the varable for a more important purpose, coinsider
    changing its name to reflect its primary purpose instead.


Share this post


Link to post
Share on other sites
Good points I shall fix them, specially the constructor.

I have another question. How can I make the ball bounce off the paddle with a more realistic feeling? For example I’m doing it with rand, shouldn’t the ball be affected by the direction the paddle was moving before it hit it?

Share this post


Link to post
Share on other sites
Yes it should. I'm not sure if trigonometry is involved or needed. But the easiest way to have the direction of the ball be determined by the movement of the paddle is to save the state of the paddle's movement.

Basically you save the the current position (IOW, where the paddle was when it made contact with the ball) and where its position was just before it hit the paddle. So if paddle.y = 10 (at contact) and paddle.y = 12 (just before contact), then you make the ball.y -=1 (IOW, move upwards).

I hope that was as clear as mud [grin]


*that's why n00bs shouldn't give advice. wrong was: +=1 and downwards

[Edited by - Alpha_ProgDes on June 6, 2007 3:46:53 PM]

Share this post


Link to post
Share on other sites
I wouldn't recommend placing your initialization of variables in your constructor.

Instead have an initialize function which you call which just sets all the initial values. That way if you need to reset your class, rather then destroying it and creating a new one, you can just call the initialize function and Voila everything's been restarted.

Share this post


Link to post
Share on other sites
So you are saying that I should:
original position of the paddle - position of contact

?
lol that mud has lots of detritus, I cant see well in it :)

Share this post


Link to post
Share on other sites
Quote:
Original post by neob
So you are saying that I should:
original position of the paddle - position of contact

?

yes.
Quote:
lol that mud has lots of detritus, I cant see well in it :)


I would agree with you but I don't know what detritus is [smile]

Share this post


Link to post
Share on other sites
Quote:
Original post by Dancin_Fool
I wouldn't recommend placing your initialization of variables in your constructor.

Instead have an initialize function which you call which just sets all the initial values. That way if you need to reset your class, rather then destroying it and creating a new one, you can just call the initialize function and Voila everything's been restarted.


Dont listen to him, he is blaspheming! [grin]

Seriously, use constructors, they *guarentee* that you won't forget to call them.

If you need to reset it is easy, provided your class has a reasonable overload of operator=().

myObject = Object(); // voila, fresh as new!


In this example, the Pong class needs to either deep copying the BITMAP or use a smart pointer to it.

How often do you need to reset objects anyway?

Share this post


Link to post
Share on other sites
I will use the constructor method, because after 11 points from either AI or P2 the game can close.

However the other method would be implemented as well. I have this method startNew() that after a new "goal" has been scored it resets some variables that need to be.

Share this post


Link to post
Share on other sites
i have made pong before on the calculator(very slow >.<) and the method i did to make it more realistic...
i had two variables for the vertical and horizontal motion of the ball...
then i would add those to the current coordinates each time...
coordinates are x and y... and the speed is a and b...
x=x+a
y=y+b
my paddle was 3 pixels wide so it was pretty easy to do the collision detection... but anyways...
if it hit the top of the paddle...the speed would go +1... so the more times you hit the edge the faster it will go in that direction... and bottom change the speed -1 every time... and the middle would not do anything except change the direction... so... if the ball is coming fast down to the top of the paddle... it would not change the direction of the ball... but rather slow it down...

it seemed logical to me... and for however wide the paddle is you just have to scale changes in speed accordingly...

hope it helped

Share this post


Link to post
Share on other sites
Quote:
Original post by Alpha_ProgDes
Quote:
Original post by neob
So you are saying that I should:
original position of the paddle - position of contact

?

yes.
Quote:
lol that mud has lots of detritus, I cant see well in it :)


I would agree with you but I don't know what detritus is [smile]


But if the paddle is at y300 and the ball is hit somewhere around y600...
I really dont get it

Share this post


Link to post
Share on other sites
Hi

I finished my own pong clone a few weeks ago. I used c++ and Win32, and I did some things differently to yours. Maybe you would be interested in this. This function is called whenever a new ball is needed to continue play.

void ballObject::newBall()
{
dead = false;
speed = 2.0;
xLoc = 320;
yLoc = 180;

xActual = xLoc;
yActual = yLoc;

switch(rand()%2)
{
case 0:
leftright = 1;
break;
case 1:
leftright = -1;
break;
default:
leftright = 1;
}
angle = (rand()%91) - 45;
}


xLoc and yLoc are ints used when drawing the ball's position, xActual and yActual are doubles that get passed into some trigonometry later. leftright is an int that describes whether the ball is traveling left or right across the screen. If it's -1, then the X coordinate of the ball is decreasing, and if it's 1, then the X coordinate is increasing. angle is an int that is randomly generated as 45 to -45. A negative number in angle means the Y coordinate of the ball is decreasing, and a positive number means it's increasing.

This function gets called on every game tick, (100 times a second).
void ballObject::updatePosition(paddleObject* player1,paddleObject* player2)
{
double hypotenuse = speed;
double adjacent;
double opposite;

// find out how far the ball has travelled since the last tick
opposite = (sin(angle*PI/180) * hypotenuse);
adjacent = (cos(angle*PI/180) * hypotenuse);

xActual = xActual + (adjacent*leftright);
yActual = yActual + opposite;

// collision detection for walls
if (yActual < MINY ||
yActual+16 > MAXY)
{
// bounce noise!
PlaySound(MAKEINTRESOURCE(IDW_PLIK),hClassInst,SND_RESOURCE|SND_ASYNC);

// we hit a wall, so multiply the angle by -1
angle = angle * -1;
}

// collision detection for paddles
if (xActual < MINX)
{
// hitting player2 paddle?
if (yActual > player2->yLoc &&
yActual < player2->yLoc+120 &&
leftright == -1)
{
// bounce noise!
PlaySound(MAKEINTRESOURCE(IDW_PLIK),hClassInst,SND_RESOURCE|SND_ASYNC);

// we hit the left paddle, so X now has to count up
leftright = 1;

// increase speed!
speed = speed + 0.1;

// angle deflection if the paddle is moving when the ball bounces
if (player2->goingUp)
{
angle = angle -3;
}
if(player2->goingDown)
{
angle = angle +3;
}
}

}
if (xActual+16 > MAXX)
{
// hitting player 1 paddle?
if (yActual > player1->yLoc &&
yActual < (player1->yLoc+120) &&
leftright == 1)
{
// bounce noise!
PlaySound(MAKEINTRESOURCE(IDW_PLIK),hClassInst,SND_RESOURCE|SND_ASYNC);

// we hit the right paddle, so X now has to count down
leftright = -1;

// increase speed!
speed = speed + 0.1;

// angle deflection if the paddle is moving
if (player1->goingUp)
{
angle = angle -3;
}
if(player1->goingDown)
{
angle = angle +3;
}
}
}

// check if the ball has gone out of play
if (xActual < -16 ||
xActual > 640)
{
// it has, so set the ball to dead status
dead = true;

// award a point
if (xActual < -16) { player1->score++; }
if (xActual > 640) { player2->score++; }

if (player1->score > 2)
{
player1->wins = true;
}
if (player2->score > 2)
{
player2->wins = true;
}

// set the newball timer
SetTimer(hClassWnd,
IDT_NEWBALL,
3000, // after three seconds
(TIMERPROC)NULL);
}
}


I hope you can get some inspiration from this.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this