Thread: C pong program ( need help smoothing animations )

  1. #1
    Registered User
    Join Date
    Jan 2013
    Posts
    55

    C pong program ( need help smoothing animations )

    Before I paste the code here, I would like to explain some things.

    1) I built this code of the directional arrow code mentioned in the FAQ, I deleted some of it and then added a ton more.

    2) This code works, no errors or warnings, but may not work on all compiliers. It uses getch(), which is of course, non-standard. The rest of it, assuming you use windows, should work fine on your computer.

    3) I kind of did the animations the hard manual way, and probably not the most efficient or easiest. Which led me to my problem of having flashy screen clears that make the output on the screen look rather bad. I mainly used a lot of coord variables to draw from 2 global structures and several more global arrays in order to make a similar screen to the classic pong game. It gets updated everytime the user makes the paddle go up or down(either one).

    4) I know the WaitForMultipleObjects() is semi-invalid in this context, but eventually I will add more threads to control the ball movement or anything else that deserves another thread.

    5) Press w or s to make the leftmost paddle move up and down. Press the UP or DOWN arrow key to make the rightmost paddle move. The paddle will not move if you try to move it out of its maximum range(defined at the beginning as constants).

    6) Yes, global variables are usually considered bad, but there was no way I was going to try to keep passing all of those coordinates back and forth without them ( It would probably cause me to make more mistakes anyways ).

    Any help you can give me to speed the animations up/smooth them out would be helpful. Or any improvement in general would be helpful

    Code:
    #include <stdio.h>#include <stdlib.h>
    #include <conio.h>
    #include <process.h>
    #include <windows.h>
    
    
    #define PADDLE1_X_OFFSET 14
    #define PADDLE1_Y_OFFSET 8
    
    
    #define PADDLE2_X_OFFSET 26
    #define PADDLE2_Y_OFFSET 8
    
    
    #define PADDLE_MAXUP 5
    #define PADDLE_MAXDOWN 23
    
    
    static int get_code ( void );
    
    
    void gotoxy(int x,int y);
    
    
    void PaddleInit(void);
    void GameBoxInit(void);
    
    
    void DrawPaddle1(void);
    void DrawPaddle2(void);
    void DrawGameBox(void);
    void DrawDashedLine(void);
    
    
    void Paddle1Down(void);
    void Paddle2Down(void);
    
    
    void Paddle1Up(void);
    void Paddle2Up(void);
    
    
    void cls(HANDLE hConsole);
    
    
    /* System dependent key codes */
    enum
    {
      KEY_W = 87,
      KEY_S = 83,
      KEY_w = 119,
      KEY_s = 115,
      KEY_ESC     = 27,
      ARROW_UP    = 256 + 72,
      ARROW_DOWN  = 256 + 80,
    };
    
    
    struct Ping_Pong_Paddle
    {
        COORD Top_Of_Paddle;
        COORD Bottom_Of_Paddle;
        COORD Middle;
        COORD Upper_Middle;
        COORD Lower_Middle;
    }; // Struct that holds the current location of the
         // paddle's various "pixels"
    
    
    struct Ping_Pong_Paddle Paddle1;
    struct Ping_Pong_Paddle Paddle2;
    
    
    COORD HorizontalBoxLine[44];
    COORD HorizontalBoxLineFar[44];
    COORD VerticalBoxLine[21];
    COORD VerticalBoxLineFar[21];
    COORD VerticalDashedLine[10];
    
    
    COORD coord={0,0};
    COORD home={0,0};
    
    
    static int get_code ( void )
    {
      int ch = getch();
    
    
      if ( ch == 0 || ch == 224 ) ch = 256 + getch(); else return ch;
    
    
      return ch;
    }
    
    
    void gotoxy(int x,int y)
    {
        coord.X=x;
        coord.Y=y;
        SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
    }
    
    
    void GetInput(void * param)
    {
        int ch;
    
    
        while ( ( ch = get_code() ) != KEY_ESC )
        {
            switch ( ch )
            {
                case KEY_W:
                    if (Paddle1.Top_Of_Paddle.Y != PADDLE_MAXUP)
                    {
                        Paddle1Up();
                        break;
                    }
                    break;
                case KEY_w:
                    if (Paddle1.Top_Of_Paddle.Y != PADDLE_MAXUP)
                    {
                        Paddle1Up();
                        break;
                    }
                    break;
                case KEY_S:
                    if (Paddle1.Bottom_Of_Paddle.Y != PADDLE_MAXDOWN)
                    {
                        Paddle1Down();
                        break;
                    }
                    break;
                case KEY_s:
                    if (Paddle1.Bottom_Of_Paddle.Y != PADDLE_MAXDOWN)
                    {
                        Paddle1Down();
                        break;
                    }
                    break;
                case ARROW_UP:
                    if (Paddle2.Top_Of_Paddle.Y != PADDLE_MAXUP)
                    {
                        Paddle2Up();
                        break;
                    }
                    break;
                case ARROW_DOWN:
                    if (Paddle2.Bottom_Of_Paddle.Y != PADDLE_MAXDOWN)
                    {
                        Paddle2Down();
                        break;
                    }
                    break;
            }
        }
    
    
        exit(EXIT_SUCCESS);
    }
    
    
    int main ( void )
    {
        PaddleInit();
        GameBoxInit();
    
    
        DrawGameBox();
        DrawPaddle1();
        DrawPaddle2();
    
    
    
    
        HANDLE PLAYERINPUT[1];
    
    
        PLAYERINPUT[0] = (HANDLE) _beginthread( GetInput, 0, NULL );
        if (PLAYERINPUT[0] == INVALID_HANDLE_VALUE) exit(EXIT_FAILURE);
    
    
        WaitForMultipleObjects(1, PLAYERINPUT, TRUE, INFINITE);
    
    
        return 0;
    }
    
    
    void DrawPaddle1(void)
    {
        gotoxy(Paddle1.Top_Of_Paddle.X,Paddle1.Top_Of_Paddle.Y);
        putchar('Û');
        gotoxy(Paddle1.Upper_Middle.X,Paddle1.Upper_Middle.Y);
        putchar('Û');
        gotoxy(Paddle1.Middle.X,Paddle1.Middle.Y);
        putchar('Û');
        gotoxy(Paddle1.Lower_Middle.X,Paddle1.Lower_Middle.Y);
        putchar('Û');
        gotoxy(Paddle1.Bottom_Of_Paddle.X, Paddle1.Bottom_Of_Paddle.Y);
    
    
        gotoxy(home.X,home.Y);
    
    
        return;
    }
    
    
    void DrawPaddle2(void)
    {
        gotoxy(Paddle2.Top_Of_Paddle.X,Paddle2.Top_Of_Paddle.Y);
        putchar('Û');
        gotoxy(Paddle2.Upper_Middle.X,Paddle2.Upper_Middle.Y);
        putchar('Û');
        gotoxy(Paddle2.Middle.X,Paddle2.Middle.Y);
        putchar('Û');
        gotoxy(Paddle2.Lower_Middle.X,Paddle2.Lower_Middle.Y);
        putchar('Û');
        gotoxy(Paddle2.Bottom_Of_Paddle.X, Paddle2.Bottom_Of_Paddle.Y);
    
    
        gotoxy(home.X,home.Y);
    
    
        return;
    }
    
    
    void DrawGameBox(void)
    {
        HANDLE hStdout;
        hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    
    
        cls(hStdout);
    
    
        int loopctr=0;
    
    
        for (; loopctr < 43; loopctr++ )
        {
            gotoxy(HorizontalBoxLine[0+loopctr].X, HorizontalBoxLine[0+loopctr].Y);
            putchar('Û');
            gotoxy(HorizontalBoxLineFar[0+loopctr].X, HorizontalBoxLineFar[0+loopctr].Y);
            putchar('Û');
        }
    
    
        for (loopctr = 0; loopctr < 20; loopctr++ )
        {
            gotoxy(VerticalBoxLine[0+loopctr].X, VerticalBoxLine[0+loopctr].Y);
            putchar('Û');
            gotoxy(VerticalBoxLineFar[0+loopctr].X, VerticalBoxLineFar[0+loopctr].Y);
            putchar('Û');
        }
    
    
        DrawDashedLine();
    
    
        return;
    
    
    }
    
    
    void DrawDashedLine(void)
    {
        int loopctr = 0;
    
    
        for (loopctr = 0; loopctr < 9; loopctr++)
        {
            gotoxy(VerticalDashedLine[0+loopctr].X, VerticalDashedLine[0+loopctr].Y);
            putchar('±');
        }
    
    
        return;
    }
    
    
    void Paddle1Up(void)
    {
        Paddle1.Bottom_Of_Paddle.Y--;
        Paddle1.Lower_Middle.Y--;
        Paddle1.Middle.Y--;
        Paddle1.Upper_Middle.Y--;
        Paddle1.Top_Of_Paddle.Y--;
    
    
        DrawGameBox();
    
    
        DrawPaddle1();
        DrawPaddle2();
    
    
        return;
    }
    
    
    void Paddle1Down(void)
    {
        Paddle1.Bottom_Of_Paddle.Y++;
        Paddle1.Lower_Middle.Y++;
        Paddle1.Middle.Y++;
        Paddle1.Upper_Middle.Y++;
        Paddle1.Top_Of_Paddle.Y++;
    
    
        DrawGameBox();
    
    
        DrawPaddle1();
        DrawPaddle2();
    
    
        return;
    }
    
    
    void Paddle2Up(void)
    {
        Paddle2.Bottom_Of_Paddle.Y--;
        Paddle2.Lower_Middle.Y--;
        Paddle2.Middle.Y--;
        Paddle2.Upper_Middle.Y--;
        Paddle2.Top_Of_Paddle.Y--;
    
    
        DrawGameBox();
    
    
        DrawPaddle1();
        DrawPaddle2();
    
    
        return;
    }
    
    
    void Paddle2Down(void)
    {
        Paddle2.Bottom_Of_Paddle.Y++;
        Paddle2.Lower_Middle.Y++;
        Paddle2.Middle.Y++;
        Paddle2.Upper_Middle.Y++;
        Paddle2.Top_Of_Paddle.Y++;
    
    
        DrawGameBox();
    
    
        DrawPaddle1();
        DrawPaddle2();
    
    
        return;
    }
    
    
    void PaddleInit(void)
    {
        Paddle1.Bottom_Of_Paddle.X = 2 + PADDLE1_X_OFFSET;
        Paddle1.Bottom_Of_Paddle.Y = 6 + PADDLE1_Y_OFFSET;
        Paddle1.Lower_Middle.X = 2 + PADDLE1_X_OFFSET;
        Paddle1.Lower_Middle.Y = 5 + PADDLE1_Y_OFFSET;
        Paddle1.Middle.X = 2 + PADDLE1_X_OFFSET;
        Paddle1.Middle.Y = 4 + PADDLE1_Y_OFFSET;
        Paddle1.Upper_Middle.X = 2 + PADDLE1_X_OFFSET;
        Paddle1.Upper_Middle.Y = 3 + PADDLE1_Y_OFFSET;
        Paddle1.Top_Of_Paddle.X = 2 + PADDLE1_X_OFFSET;
        Paddle1.Top_Of_Paddle.Y = 2 + PADDLE1_Y_OFFSET;
    
    
        Paddle2.Bottom_Of_Paddle.X = 16 + PADDLE2_X_OFFSET;
        Paddle2.Bottom_Of_Paddle.Y = 6 + PADDLE2_Y_OFFSET;
        Paddle2.Lower_Middle.X = 16 + PADDLE2_X_OFFSET;
        Paddle2.Lower_Middle.Y = 5 + PADDLE2_Y_OFFSET;
        Paddle2.Middle.X = 16 + PADDLE2_X_OFFSET;
        Paddle2.Middle.Y = 4 + PADDLE2_Y_OFFSET;
        Paddle2.Upper_Middle.X = 16 + PADDLE2_X_OFFSET;
        Paddle2.Upper_Middle.Y = 3 + PADDLE2_Y_OFFSET;
        Paddle2.Top_Of_Paddle.X = 16 + PADDLE2_X_OFFSET;
        Paddle2.Top_Of_Paddle.Y = 2 + PADDLE2_Y_OFFSET;
    
    
        return;
    
    
    }
    
    
    void GameBoxInit(void)
    {
        int loopctr=0;
        int DashedLineOffset=0;
    
    
        for (; loopctr < 43; loopctr++ )
        {
            HorizontalBoxLine[0+loopctr].X = 8 + loopctr;
            HorizontalBoxLine[0+loopctr].Y = 4;
            HorizontalBoxLineFar[0+loopctr].X = 8 + loopctr;
            HorizontalBoxLineFar[0+loopctr].Y = 23;
        }
    
    
        for (loopctr = 0; loopctr < 20; loopctr++ )
        {
            VerticalBoxLine[0+loopctr].X = 8;
            VerticalBoxLine[0+loopctr].Y = 4 + loopctr;
            VerticalBoxLineFar[0+loopctr].X = 50;
            VerticalBoxLineFar[0+loopctr].Y = 4 + loopctr;
        }
    
    
        for (loopctr = 0; loopctr < 9; loopctr++)
        {
            VerticalDashedLine[0+loopctr].X = 28;
            VerticalDashedLine[0+loopctr].Y = 6 + DashedLineOffset;
            DashedLineOffset += 2;
        }
    
    
        return;
    }
    
    
    void cls( HANDLE hConsole ) // Credit for this function goes to Microsoft's website's help section here :
    {                           // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682022%28v=vs.85%29.aspx
    
    
    
    
       COORD coordScreen = { 0, 0 };    // home for the cursor
       DWORD cCharsWritten;
       CONSOLE_SCREEN_BUFFER_INFO csbi;
       DWORD dwConSize;
    
    
    // Get the number of character cells in the current buffer.
    
    
       if( !GetConsoleScreenBufferInfo( hConsole, &csbi ))
       {
          return;
       }
    
    
       dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
    
    
       // Fill the entire screen with blanks.
    
    
       if( !FillConsoleOutputCharacter( hConsole,        // Handle to console screen buffer
                                        (TCHAR) ' ',     // Character to write to the buffer
                                        dwConSize,       // Number of cells to write
                                        coordScreen,     // Coordinates of first cell
                                        &cCharsWritten ))// Receive number of characters written
       {
          return;
       }
    
    
       // Get the current text attribute.
    
    
       if( !GetConsoleScreenBufferInfo( hConsole, &csbi ))
       {
          return;
       }
    
    
       // Set the buffer's attributes accordingly.
    
    
       if( !FillConsoleOutputAttribute( hConsole,         // Handle to console screen buffer
                                        csbi.wAttributes, // Character attributes to use
                                        dwConSize,        // Number of cells to set attribute
                                        coordScreen,      // Coordinates of first cell
                                        &cCharsWritten )) // Receive number of characters written
       {
          return;
       }
    
    
       // Put the cursor at its home coordinates.
    
    
       SetConsoleCursorPosition( hConsole, coordScreen );
    
    
    }

  2. #2
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Any help you can give me to speed the animations up/smooth them out would be helpful. Or any improvement in general would be helpful
    O_o

    You asked for it:

    1): Remove the global variables. Nope. Your excuse sucks. Put all the `COORD' arrays into a structure--call it `GameScreen' or similar--so you can pass that one variable.

    2): You can't do controls for multiple players with just `getch'; it will just never work correctly. You need to buffer input over a frame--as in slice of time--so that all input for both players appears to occur simultaneously.

    3): Remove the use of `WaitForMultipleObjects'. Nope. Using a thread for each game object isn't going to get you anything if you just use a single thread correctly. Save threading for when you have more sophisticated stuff to do that doesn't demand sequencing.

    4): Your screen flashes because you are clearing the entire screen and then updating the entire screen pixel-by-pixel. You need to use a technique called "double buffering". Yes. That is a technique normally applied to more sophisticated graphics, but it applies here as well. Draw your screen to a separate buffer, other than the one being displayed, so that you may call `WriteConsole' exactly once to draw the entire screen.

    5): Yeah, if you are advanced enough to do this well, you are advanced enough to start using "SDL" or something else more substantial than "WinAPI" console graphics. Nope. There is nothing wrong with console graphics. I love the console "Portal" clone. I just think you'll be a lot happier with "SDL".

    Soma

  3. #3
    Registered User
    Join Date
    Sep 2006
    Posts
    8,868
    Already mentioned, but never re-draw the entire screen. Re-draw ONLY the smallest part of the screen that is possible - the part that has actually changed. Think of a chess board. A Player moves his Knight, so you need to update the display:

    1) In your buffer, replace the old Knight's square, with a blank center. Don't even fill in the entire square - just the center portion of the square that the Knight used to cover.

    2) Print the new Knight's square with the Knight now occupying it.

    3) And swap the buffer with the one currently being displayed.

    The faster you're game moves game elements, the more important it is to change ONLY the barest minimum in the display.

  4. #4
    Registered User
    Join Date
    May 2012
    Posts
    505
    Quote Originally Posted by Adak View Post
    Already mentioned, but never re-draw the entire screen. Re-draw ONLY the smallest part of the screen that is possible - the part that has actually changed.
    He's got a choice. Either update the portions of the screen that change, and only those portions. Or regenerate the entire screen in on buffer, and write the entire buffer out to the screen, in one go. The second method is easier in the long run, but a bit harder to program the set-up.
    Either way, you've got to time your writes to reduce flicker. This can be difficult, but normally running a timer at 50 frames per second will do the trick, on a modern computer. You won't actually move the display that fast, for Pong.
    I'm the author of MiniBasic: How to write a script interpreter and Basic Algorithms
    Visit my website for lots of associated C programming resources.
    https://github.com/MalcolmMcLean


  5. #5
    Registered User migf1's Avatar
    Join Date
    May 2013
    Location
    Athens, Greece
    Posts
    385
    Quote Originally Posted by BatchProgrammer View Post
    Before I paste the code here I would like to explain some things.
    ...
    First of all, congrats for putting the effort!

    ...
    3) I kind of did the animations the hard manual way, and probably not the most efficient or easiest. Which led me to my problem of having flashy screen clears that make the output on the screen look rather bad. I mainly used a lot of coord variables to draw from 2 global structures and several more global arrays in order to make a similar screen to the classic pong game. It gets updated everytime the user makes the paddle go up or down(either one).
    ...
    The techniques already suggested in the thread are quite valid and I'll second them.

    However, it is quite easy to fix this in your existing code by just adding a couple of functions to be called before moving a paddle to its new position, namely:
    Code:
    void UndrawPaddle1( void );
    void UndrawPaddle2( void );
    and by removing a few unneeded redraws inside the routines that move the paddles.

    Here it is: C code - 459 lines - codepad

    ...
    4) I know the WaitForMultipleObjects() is semi-invalid in this context, but eventually I will add more threads to control the ball movement or anything else that deserves another thread.
    ...
    6) Yes, global variables are usually considered bad, but there was no way I was going to try to keep passing all of those coordinates back and forth without them ( It would probably cause me to make more mistakes anyways ).

    Any help you can give me to speed the animations up/smooth them out would be helpful. Or any improvement in general would be helpful
    Ok, here is an alternative implementation based on your code, using no threading, no conio.h, no globals (with one exception, justified with comments) and primitive asynchronous input via the Win32 API, so players can move their paddles "simultaneously".

    It's also a bit more structured (may be further refined quite a bit), a bit less verbose (besides the comments), and a bit more scalable.

    It's also quite possible that it contains bugs, since I wrote it just for a break at my work, so I didn't really test it.

    C code - 417 lines - codepad

    I hope you find it helpful though.

  6. #6
    Registered User
    Join Date
    Jan 2013
    Posts
    55
    Thanks alot Mfig1, the code works perfectly now! I may not be able to understand all of it yet because I'm a beginner at the WINAPI, but it will be helpful to me to be able to figure out your code and see what I can do with it! I'm making a ball for the pong game too, I will post my code for it once I get it done. I'm also going to add a scoring system. If I have any questions about changes I could make to your code, I'll probably ask them here. There was one thing I did change in your code though.

    Code:
    putchar('U');
    was changed to...

    Code:
    putchar('Û');
    This makes a big difference, because instead of the box being U's the box looks like it is made up of solid lines. Here are some samples from the program with the Û instead of the U.

    ███████████████████████████████████████████

    or if it was U it would be...

    UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU

    It really does make a big difference in the look and feel.

    Thanks for all your help though, I appreciate it!

  7. #7
    Registered User
    Join Date
    Sep 2006
    Posts
    8,868
    FYI, your char set probably includes special box drawing char's just for this purpose - in narrow, half height, and full sizes. In the narrow box drawing char's, you can also choose either single or double line chars, for the border.

    You don't have to settle for a rude or crude box, in the terminal.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Double-linked list error in image smoothing program
    By mwhar in forum C Programming
    Replies: 4
    Last Post: 04-27-2011, 06:56 PM
  2. Smoothing programme
    By lasher_a2 in forum C Programming
    Replies: 8
    Last Post: 03-25-2010, 05:01 PM
  3. Making Animations
    By bumfluff in forum Game Programming
    Replies: 6
    Last Post: 10-29-2006, 08:10 AM
  4. adding animations in C++
    By skillmaster in forum C++ Programming
    Replies: 4
    Last Post: 01-04-2006, 02:28 PM
  5. Anyone know of a tutorial on using animations in dx9?
    By ApocalypticTime in forum Game Programming
    Replies: 0
    Last Post: 03-20-2003, 05:24 PM

Tags for this Thread