Public Group

# Pong AI, and some advice on the code I've managed to cobble together

This topic is 4949 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

I started working on this thing a couple of days ago. Not much planning on my part has led to a bunch of code that feels "improvised." Ugliness and redundancy are my biggest worries. I could use some advice on cleaning up the code. Plus, I need to add some AI. Right now I just got a "dumb" paddle going up and down randomly (at least 5 lines in a row). I'm almost afraid to post the code. But here it is:
#include <iostream>

using std::cout;
using std::cin;
using std::endl;

#include <iomanip>

using std::setw;

#include <ctime>
#include <cmath>
#include <conio.h>
#include <windows.h>

const int SCREEN_HEIGHT = 25;
const int SCREEN_WIDTH  = 80;
const int GAME_OBJECT_STR_LENGTH = 4;
enum { pLEFT = 10, pRIGHT = 70, pMIDDLE = 39 };
enum { pTOP = 3, pBOTTOM = 22, pCENTER = 12 };
enum DIRECTION { d_LEFT = -1, d_NONE = 0, d_RIGHT = 1, d_UP = -1, d_DOWN = 1 };
enum { LOSE_LINE_LEFT = 8, LOSE_LINE_RIGHT = 72 };

// forward declarations/prototypes
void gotoxy( int, int );
void delay( int );
void print_interface();
void ai_move( int & );

int player1score = 0;
int player2score = 0;
int speedfactor = 100;
bool flag1 = false; // a flag because I'm lazy

// game object struct
struct game_object {
char str_top[4];     // all game objects will consist of three (or less) char
char str_middle[4];  // arrays of length 3 or less
char str_bottom[4];
int oldx, oldy;
int tempx, tempy;
int x, y;
};

// engine class
class pong {

public:
void draw_object( game_object & );
void get_input( int & );
void ball_move( game_object &, game_object &, game_object & );
bool check_ball( game_object & );

} engine;

void pong::draw_object( game_object &object )
{
// delete object at old location
for ( int i = 0; i < GAME_OBJECT_STR_LENGTH - 1; i++ ) {
if ( object.str_top[ i ] )
gotoxy( object.oldx + i, object.oldy ), cout << " ";

if ( object.str_middle[ i ] )
gotoxy( object.oldx + i, object.oldy + 1 ), cout << " ";

if ( object.str_bottom[ i ] )
gotoxy( object.oldx + i, object.oldy + 2), cout << " ";
}

// draw object at new location
if ( object.str_top )
gotoxy( object.x, object.y ), cout << object.str_top;

if ( object.str_middle )
gotoxy( object.x, object.y + 1 ), cout << object.str_middle;

if ( object.str_bottom[ 0 ] )
gotoxy( object.x, object.y + 2), cout << object.str_bottom;
}

void pong::get_input( int &y )
{
if ( GetAsyncKeyState( 38 ) && y != pTOP ) // if the "up" key is pressed...
y--;

if ( GetAsyncKeyState( 40 ) && y != pBOTTOM - 2 ) // if the "down" key is pressed...
y++;
}

void pong::ball_move( game_object &ball, game_object &p1, game_object &p2 )
{
static DIRECTION Y_DIR = d_NONE;
static DIRECTION X_DIR = d_LEFT;

if ( flag1 == true ) {
Y_DIR = d_NONE;
X_DIR = rand() % 2 == 0 ? d_LEFT : d_RIGHT;
flag1 = false;
}

if ( ball.x == pLEFT + 1 ) { // if the ball hits the line...

if ( ball.y == p1.y - 1 ) { // if it hits the top of the paddle, reflect
Y_DIR = d_UP;           // along x+ and y+
X_DIR = d_RIGHT;
if ( speedfactor >= 25 )
speedfactor -= 5;
}

else if ( ball.y == p1.y ) { // if it hits the middle, reflect along
Y_DIR = d_NONE;          // x+ only
X_DIR = d_RIGHT;
if ( speedfactor >= 25 )
speedfactor -= 5;
}

else if ( ball.y == p1.y + 1 ) {
Y_DIR = d_DOWN;
X_DIR = d_RIGHT;
if ( speedfactor >= 25 )
speedfactor -= 5;
}
} else if ( ball.x == pRIGHT - 1 ) { // if the ball hits the line...

if ( ball.y == p2.y - 1  ) { // if it hits the top of the paddle, reflect
Y_DIR = d_UP;            // along x+ and y+
X_DIR = d_LEFT;
if ( speedfactor >= 25 )
speedfactor -= 5;
}

else if ( ball.y == p2.y ) { // if it hits the middle, reflect along
Y_DIR = d_NONE;          // x+ only
X_DIR = d_LEFT;
if ( speedfactor >= 25 )
speedfactor -= 5;
}

else if ( ball.y == p2.y + 1 ) {
Y_DIR = d_DOWN;
X_DIR = d_LEFT;
if ( speedfactor >= 25 )
speedfactor -= 5;
}
}

if ( ball.y == pTOP )
Y_DIR = d_DOWN;
else if ( ball.y == pBOTTOM - 2 )
Y_DIR = d_UP;

ball.oldx = ball.x;
ball.oldy = ball.y;

ball.x += X_DIR;
ball.y += Y_DIR;
}

bool pong::check_ball( game_object &ball )
{
if ( ball.x == LOSE_LINE_RIGHT ) {
player1score++;
if ( speedfactor - 1 < 100 )
speedfactor += 2;
flag1 = true;
return true;
}

if ( ball.x == LOSE_LINE_LEFT ) {
player2score++;
if ( speedfactor - 1 < 100 )
speedfactor += 2;
flag1 = true;
return true;
}

return false;
}

int main()
{

// ball initialization
ball.x = pMIDDLE, ball.y = pCENTER;
ball.oldx = ball.x, ball.oldy = ball.y;

ball.str_top[ 0 ] = '\0', ball.str_top[ 1 ] = '\0',
ball.str_top[ 2 ] = '\0';

ball.str_middle[ 0 ] = 219, ball.str_middle[ 1 ] = '\0',
ball.str_middle[ 2 ] = '\0';

ball.str_bottom[ 0 ] = '\0', ball.str_bottom[ 1 ] = '\0',
ball.str_bottom[ 1 ] = '\0';

while( 1 ) {
delay( speedfactor );
print_interface();
engine.draw_object( ball );

if ( kbhit() ) {
}

if ( engine.check_ball( ball ) ) {

ball.x = pMIDDLE, ball.y = pCENTER;
ball.oldx = ball.x, ball.oldy = ball.y;

gotoxy( pMIDDLE - 10, pCENTER - 1 );
cout << "                                     ";

gotoxy( pMIDDLE - 10, pCENTER );
cout << "Oops! Score is " << player1score << " - " << player2score << endl;

gotoxy( pMIDDLE - 10, pCENTER + 1 );
cout << "                                     ";

delay( 3000 );
system( "cls" );
}
}
}

// a couple of functions for utility purposes
void gotoxy( int x, int y )
{
COORD pos;
HANDLE screen = GetStdHandle( STD_OUTPUT_HANDLE );

pos.X = x;
pos.Y = y;

SetConsoleCursorPosition( screen, pos );
}

void delay( int milliseconds )
{
time_t now = clock();
time_t end = now + milliseconds;

while( now = clock() < end );
}

void print_interface()
{
gotoxy( 0, 0 );
cout << "Player 1: " << player1score << "\tPlayer 2: " << player2score
<< "\t\tGamespeed is currently: "
<< setw( 2 ) << abs( speedfactor - 100 ) << "/80\n\n"
<< "        _________________________________________________________________\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |                                |                                |\n"
<< "       |________________________________|________________________________|\n";
}

void ai_move( int &y )
{
static DIRECTION dir = d_UP;
static int dcounter = 0;

if ( y == pTOP )
dir = d_DOWN;
else
if ( y == pBOTTOM - 2 )
dir = d_UP;
else
if ( dcounter > 5 )
dir = rand() % 2 == 0 ? d_UP : d_DOWN;
y += dir;
dcounter++;
if ( dcounter > 6 )
dcounter = 0;
}


Please excuse any n00bish-ness. I started off ok. I had an engine class, but after some time it just devolved into a collection of public functions, pointless to have as a class. I can make this hideous beast work, but I want to make it work right. Hints, tips, AI ideas, anything will be appreciated. Thanks in advance. The main thing is AI, though. Edit: Oh yeah, and it's an ASCII pong, but you already knew that [looksaround]

##### Share on other sites
Putting everything into a class is not necessarily a good thing - it doesn't make your code more 'object oriented.' Some functions are naturally suited to being public globals. So don't worry about that.

My issues with your code would be:

1. Personally, I tend to group all my includes together and put them in alphabetical order (when dependencies are not a problem).
2. Those enums need commenting.
3. 'flag1' is a terrible variable name. You could change it, or you could fix up the comment to tell us something useful.
4. You say that game objects will require arrays of length 3 or less, yet your arrays are of length 4. I think you want to change the comment to say 'strings of three characters or less, not including the null terminator.'
5. What's 'tempx' and 'tempy'?
6. Using commas like that is a fairly nasty idea, because you're doing two things with one statement. It looks like an attempt to avoid using curly braces after the if - that's a really bad habit to get into, because at first glance there is only one thing being done by that line of code, a misleading way to leave things. Add curly braces and split the gotoxy/cout into seperate statements.
7. It's often a good idea to bracket out seperate clauses in an if statement to avoid any potential problems with operator precedence (the get_input function's ifs are what I'm looking at).
8. Don't you want to be copying paddle2.y to paddle2.oldy regardless of whether the user has hit a key, because your AI is going to be moving the paddle?
9. gotoxy, delay, and ai_move need commenting.

Other than that, it's a fairly nice system you've got there.

##### Share on other sites
I agree with superpig's comments. I also wrote a pong clone as my first game, if you want the source code it's here:

http://www.freewebs.com/crazyballsgame/

It uses directdraw, but the AI could be used in a text-game. Basically, check whether the ball is higher or lower than the AI paddle, and move the paddle a set number of spaces up or down accordingly.

If you scroll down the page ^ there's english underneath.

##### Share on other sites
Thanks for the tips, superpig. Using your suggestions, and some from PMs and other forums (and of course, looking at other people's code), I think I have managed to clean it up and make it much prettier and easier to understand. As far as the AI, I can't believe how simple it was. I should have been able to figure it out on my own. To the AP, I took a look at your game. It was nice. I like the AI, even if it was a bit easy. Thanks for the good example.

// ASCII Pong// Programmed by Muhammad al-Haider// pong.cpp#include <conio.h>#include <cmath>#include <ctime>#include <iomanip>#include <iostream>#include <windows.h>using namespace std;/*** global variables ***/const int str_length = 4; // length for game object stringsint p1score = 0;int p2score = 0;int speed = 100; // gamespeed (length of delay for the main game loop)bool randomize = true; // flag to randomize the direction the ball is moving/*** enumerations ***/enum { pLEFT = 10, pRIGHT = 70, pMIDDLE = 39 };    // horizontal(x) positions enum  { pTOP = 3, pBOTTOM = 22, pCENTER = 12 };    // vertical(y) positions   enum { LOSE_LINE_LEFT = 8, LOSE_LINE_RIGHT = 72 }; // the ball is out if it crosses                                                   // one of these lines enum DIRECTION { d_LEFT = -1, d_FLAT = 0, d_RIGHT = 1, // Direction an object is                 d_UP = -1, d_DOWN = 1 };              // travelling/*** structs ***/struct game_object {    char    szTop[ str_length ];  // all game objects will consist of three (or less)    char szMiddle[ str_length ];  // char strings of length 3 or less    char szBottom[ str_length ];    COORD old;    COORD current; };/*** prototypes/forward declarations ***/void print_title();void draw_object( game_object & );void draw_interface();void print_point_message();void ball_move( game_object &, COORD, COORD );void get_input( short & );void ai_move( short &, game_object &, short );bool check_ball( game_object & );void gotoxy( int, int );void delay( int );/******************************************************************************** Main                                                                         ********************************************************************************/int main(){    srand( time( NULL ) ); // seed random number generator        /*** initialize game_objects ***/        game_object paddle1, paddle2, ball;    short dir; // direction the ball is traveling    /**********    | paddle1 |    **********/    paddle1.old.X = paddle1.current.X = pLEFT;    paddle1.old.Y = paddle1.current.Y = pCENTER;        // top string    paddle1.szTop[ 0 ] = '|';    paddle1.szTop[ 1 ] = 0;    paddle1.szTop[ 2 ] = 0;        // middle string    paddle1.szMiddle[ 0 ] = '|';    paddle1.szMiddle[ 1 ] = 0;    paddle1.szMiddle[ 2 ] = 0;        // bottom string    paddle1.szBottom[ 0 ] = '|';    paddle1.szBottom[ 1 ] = 0;    paddle1.szBottom[ 2 ] = 0;        /**********    | paddle2 |    **********/    paddle2.old.X = paddle2.current.X = pRIGHT;    paddle2.old.Y = paddle2.current.Y = pCENTER;        // top string    paddle2.szTop[ 0 ] = '|';    paddle2.szTop[ 1 ] = 0;    paddle2.szTop[ 2 ] = 0;        // middle string    paddle2.szMiddle[ 0 ] = '|';    paddle2.szMiddle[ 1 ] = 0;    paddle2.szMiddle[ 2 ] = 0;        // bottom string    paddle2.szBottom[ 0 ] = '|';    paddle2.szBottom[ 1 ] = 0;    paddle2.szBottom[ 2 ] = 0;        /*******    | ball |    *******/    ball.old.X = ball.current.X = pMIDDLE;    ball.old.Y = ball.current.Y = pCENTER;        // top string    ball.szTop[ 0 ] = 0;    ball.szTop[ 1 ] = 0;    ball.szTop[ 2 ] = 0;        // middle string    ball.szMiddle[ 0 ] = 219;    ball.szMiddle[ 1 ] = 0;    ball.szMiddle[ 2 ] = 0;        // bottom string    ball.szBottom[ 0 ] = 0;    ball.szBottom[ 1 ] = 0;    ball.szBottom[ 2 ] = 0;        print_title();    while( 1 ) {        delay( speed );        draw_interface();        draw_object( paddle1 );        draw_object( paddle2 );        draw_object( ball );                paddle1.old.Y = paddle1.current.Y;        paddle2.old.Y = paddle2.current.Y;                // determine whether ball is moving left or right        if ( ball.old.X < ball.current.X )            dir = d_RIGHT;        if ( ball.old.X > ball.current.X )            dir = d_LEFT;                // move the game objects        if ( kbhit() ) get_input( paddle1.current.Y );        ai_move( paddle2.current.Y, ball, dir );        ball_move( ball, paddle1.current, paddle2.current );                if ( check_ball( ball ) ) { // if one of the players misses the ball                        // reset the game objects            paddle1.old.X = paddle1.current.X = pLEFT;            paddle1.old.Y = paddle1.current.Y = pCENTER;            paddle2.old.X = paddle2.current.X = pRIGHT;            paddle2.old.Y = paddle2.current.Y = pCENTER;            ball.old.X = ball.current.X = pMIDDLE;            ball.old.Y = ball.current.Y = pCENTER;            randomize = true; // randomize horizontal direction of the ball                        print_point_message(); // print a message that the score has changed            delay( 3000 ); // wait three seconds            system( "cls" ); // clear the board and continue        }    }}/******************************************************************************** "Drawing" Functions                                                          ********************************************************************************//*** prints the title and some short information ***/void print_title(){    cout << "\n\n\n\n\n\n\n\n\n\t\t\tASCII Pong version 1.0\n\t\t\tby Muhammad al-Haider"         << "\n\t\t\tskulldrudgery@hotmail.com";    delay( 4000 );    system( "cls" );}/*** prints an object to the screen and clears the object's former position ***/void draw_object( game_object &object ){    // delete object at old location    for ( int i = 0; i < str_length - 1; i++ ) {        if ( object.szTop[ i ] )            gotoxy( object.old.X + i, object.old.Y ), cout << " ";            if ( object.szMiddle[ i ] )            gotoxy( object.old.X + i, object.old.Y + 1 ), cout << " ";                if ( object.szBottom[ i ] )            gotoxy( object.old.X + i, object.old.Y + 2), cout << " ";    }        // draw object at new location    if ( object.szTop[ 0 ] )        gotoxy( object.current.X, object.current.Y ), cout << object.szTop;        if ( object.szMiddle[ 0 ] )        gotoxy( object.current.X, object.current.Y + 1 ), cout << object.szMiddle;            if ( object.szBottom[ 0 ] )        gotoxy( object.current.X, object.current.Y + 2), cout << object.szBottom;}/*** prints the "interface" of the game: the board, score and gamespeed ***/void draw_interface(){    gotoxy( 0, 0 ); // reset the cursor, to begin printing from upper-left corner        cout << "        Player 1: " << setw( 2 ) << p1score << "  Player 2: " << setw( 2 )         << p2score << "\t    Gamespeed is currently: " << setw( 2 ) << abs( speed - 100 )         << "/80\n\n";             cout << "        _________________________________________________________________\n"         << "       |                                |                                |\n"         << "   A   |                                |                                |\n"         << "       |                                |                                |\n"         << "   S   |                                |                                |\n"         << "       |                                |                                |\n"         << "   C   |                                |                                |\n"         << "       |                                |                                |\n"         << "   I   |                                |                                |\n"         << "       |                                |                                |\n"         << "   I   |                                |                                |\n"         << "       |                                |                                |\n"         << "       |                                |                                |\n"         << "   P   |                                |                                |\n"         << "       |                                |                                |\n"         << "   O   |                                |                                |\n"         << "       |                                |                                |\n"         << "   N   |                                |                                |\n"         << "       |                                |                                |\n"         << "   G   |                                |                                |\n"         << "       |________________________________|________________________________|\n";}/*** prints a message that the score has changed ***/void print_point_message(){    gotoxy( pMIDDLE - 10, pCENTER - 1 );    cout << "                         ";        gotoxy( pMIDDLE - 10, pCENTER + 1 );    cout << "                         ";        gotoxy( pMIDDLE - 10, pCENTER );    cout << "Oops! Score is " << p1score << " - " << p2score;}    /******************************************************************************** "Moving" Functions                                                           ********************************************************************************//*** moves the ball and reflects it off the surfaces (paddles and borders) ***/void ball_move( game_object &ball, COORD p1, COORD p2 ){    static DIRECTION Y_DIR = d_FLAT; // y direction of the ball    static DIRECTION X_DIR = d_LEFT; // x direction of the ball        // if the randomize flag is set, randomly choose directions and reset flag    if ( randomize ) {        X_DIR = rand() % 100 < 50 ? d_LEFT : d_RIGHT;        Y_DIR = rand() % 100 < 50 ? d_UP : d_DOWN;        randomize = false;    }        // if the ball reaches the x coordinates (+1) of the left paddle...    if ( ball.current.X == pLEFT + 1 ) {        // ...check if any part of the paddle would "hit" it, if it hits the...        if ( ball.current.Y == p1.Y - 1 ) { // ...top, reflect along y+ and x+            Y_DIR = d_UP;            X_DIR = d_RIGHT;            if ( speed >= 25 ) speed -= 3;        } else            if ( ball.current.Y == p1.Y ) { // ...middle, reflect along x+ only                Y_DIR = d_FLAT;                X_DIR = d_RIGHT;                if ( speed >= 25 ) speed -= 3;            } else                if ( ball.current.Y == p1.Y + 1 ) { // ...bottom, reflect along y- and x+                    Y_DIR = d_DOWN;                    X_DIR = d_RIGHT;                    if ( speed >= 25 ) speed -= 3; // speed increases each time the ball is hit                }    } // end if ( ball.current.X == pLEFT + 1 )        // if the ball reaches the x coordinates (-1) of the right paddle...    if ( ball.current.X == pRIGHT - 1 ) {                // ...check if any part of the paddle would "hit" it, if it hits the...        if ( ball.current.Y == p2.Y - 1 ) { // ...top, reflect along y+ and x-            Y_DIR = d_UP;            X_DIR = d_LEFT;            if ( speed >= 25 ) speed -= 3;        } else            if ( ball.current.Y == p2.Y ) { // ...middle, reflect along x- only                Y_DIR = d_FLAT;                X_DIR = d_LEFT;                if ( speed >= 25 ) speed -= 3;            } else                if ( ball.current.Y == p2.Y + 1 ) { // ...bottom, reflect along y- and x-                    Y_DIR = d_DOWN;                    X_DIR = d_LEFT;                    if ( speed >= 25 ) speed -= 3;                }    } // end if ( ball.current.Y == p2.current.Y - 1 )        // if the ball hits the top border...    if ( ball.current.Y == pTOP ) Y_DIR = d_DOWN; // reflect along y-        // if the ball hits the bottom border...    if ( ball.current.Y == pBOTTOM - 2 ) Y_DIR = d_UP; // reflect along y+        // update the old coordinates to the current ones    ball.old.X = ball.current.X;    ball.old.Y = ball.current.Y;        // update the coordinates of the ball according to the directions the it's moving    ball.current.X += X_DIR;    ball.current.Y += Y_DIR;}/*** moves the computer paddle according to the ai routine ***/void ai_move( short &y, game_object &ball, short h_direction ){       static DIRECTION dir = d_UP; // vertical direction    static int dcounter = 0; // number of lines moved in one direction        // if the ball is close to the right paddle and is traveling that way...    if ( ball.current.X > 60 && h_direction == d_RIGHT ) {        dcounter = 0; // reset counter                if ( y > ball.current.Y ) // if the paddle is lower than the ball, move up            y--;                else if ( y < ball.current.Y ) // if higher, move down            y++;    } else if ( h_direction == d_RIGHT ) {        // if the ball is not close, the paddle moves randomly            if ( y == pTOP ) dir = d_DOWN;        else            if ( y == pBOTTOM - 2 ) dir = d_UP;            else // if the dcounter is more than five, change to a random direction                 if ( dcounter == 8 ) dir = rand() % 2 == 0 ? d_UP : d_DOWN;            dcounter++;        y += dir;            // reset the counter for the number of lines moved        if ( dcounter > 8 ) dcounter = 0;    }}/******************************************************************************** Input Functions                                                              ********************************************************************************//*** get direction input from player, and change y coordinate appropriately ***/void get_input( short &y ){           if ( GetAsyncKeyState( 38 ) && y != pTOP ) y--; // "up" key, ignore if paddle is at top    if ( GetAsyncKeyState( 40 ) && y != pBOTTOM - 2 ) y++; // "down" key, ignore if paddle is                                                           // at bottom (- 2 to account for                                                           // paddle length)}/******************************************************************************** Winning Condition Functions                                                  ********************************************************************************//*** check if the ball has gone "out" ***/bool check_ball( game_object &ball ){    if ( ball.current.X == LOSE_LINE_RIGHT ) { // if player 2 misses the ball...        p1score++; // ...player one scores        if ( speed - 1 < 100 ) speed += 2; // if the game speed is over 0, decrease by 2        randomize = true; // choose a random direction for the ball ( left or right)        return true; // the ball was missed    }        if ( ball.current.X == LOSE_LINE_LEFT ) { // if player 1 misses the ball...        p2score++; // ...player two scores        if ( speed - 1 < 100 ) speed += 2; // if the game speed is over 0, decrease by 2        randomize = true; // choose a random direction for the ball ( left or right)        return true; // the ball was missed    }        return false; // the ball was not missed}/******************************************************************************** Utility Functions                                                            ********************************************************************************/// move the cursor to the given coordinatesvoid gotoxy( int x, int y ){    COORD pos;    HANDLE screen = GetStdHandle( STD_OUTPUT_HANDLE );    pos.X = x;    pos.Y = y;    SetConsoleCursorPosition( screen, pos );}// delay for a given number of milliseconds - busy waitinginline void delay( int m ){    for ( time_t end = clock() + m; clock() < end; );}

@ superpig feel the impotent force of my ratings++ on you

1. 1
2. 2
3. 3
Rutin
15
4. 4
khawk
13
5. 5
frob
12

• 9
• 11
• 11
• 23
• 12
• ### Forum Statistics

• Total Topics
633662
• Total Posts
3013231
×