A Simple Pong Game in C/C++

Materials available at: https://forejune.co/cuda/

The Pong Game

OpenGL Animation

	
glutVisibilityFunc(visHandle) registers a visibility callback for the current window

When the window's visibility state changes, GLUT immediately calls the function visHandle
	GLUT_VISIBLE
	GLUT_NOT_VISIBLE
	

void visHandle(int visible)
{
   if (visible == GLUT_VISIBLE)
      timerHandle ( 0 );
   else
      ;
}
glutTimerFunc registers a timer callback function to execute once after some milliseconds

// Visibility callback
void timerHandle (int value)
{
   animate();
   glutPostRedisplay();
   // call timerHandle 25 ms later, 
   // 0 is passed to timerHandle, not used here 
   glutTimerFunc (25, timerHandle, 0);
}

Complete C/C++ Program


/*
 * pong.cpp
 * https://forejune.co/cuda
 */

#include <GL/gl.h>
#include <GL/glut.h>
#include <string>

using namespace std;

// Game variables
float ballX = 0.0;	// horizontal position of ball
float ballY = 0.0;	// vertical position of ball

float ballDX = 0.15;	// change in X of ball
float ballDY = 0.10;	// change in Y of ball

float yl = 0.0;	// left paddle position
float yr = 0.0;	// right  paddle postion

const float pW = 0.5;	// paddle width
const float pH = 3.0;	// paddle height

int scoreLeft  = 0;
int scoreRight = 0;

bool pause = false;

void drawPaddle(float x, float y)
{
  // (x, y) is the lower left vertex coordinates
  glRectf(x, y, x+pW, y+pH);
}

// draw String
void drawString(float x, float y, const string &s)
{
    glRasterPos2f(x, y);

    for (int i = 0; i < s.length(); i++)
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, s[i]);
}

void drawBall(float x, float y)
{
  glPushMatrix();
  glTranslatef(x, y, 0);
  glutSolidSphere(0.5, 16, 16);
  glPopMatrix();
}

// Initialization
void init(void)
{
    glClearColor(1, 1, 1, 0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-10, 10, -10, 10);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}


// Draw scene
void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    float x, y;		//paddle lower left corner
    glColor3f(1, 0, 0); 	//red color

    // Left paddle
    x = -9.0;
    y = yl - pH/2;
    drawPaddle(x, y);

    // Right paddle
    x = 8.5;
    y = yr - pH / 2;
    drawPaddle(x, y);

    // Ball
    glColor3f(0, 1, 0);
    drawBall(ballX, ballY);

    // Score
    char str[40];
    sprintf(str,"%d   :   %d", scoreLeft, scoreRight);
    glColor3f(0, 0, 0);  // black color
    drawString(-1.0, 9.0, str);

    glutSwapBuffers();
}

// Reset ball 
void resetBall()
{
    ballX = 0;
    ballY = 0;

    ballDX = -ballDX;
}

// Animation
void animate()
{
    if ( pause )
      return;

    // Move ball
    ballX += ballDX;
    ballY += ballDY;

    // Top-bottom collision
    if(ballY > 9.5 || ballY < -9.5)
        ballDY = -ballDY;

    // Left paddle collision
    if(ballX <= -8.25 && ballY >= yl-pH/2 &&
       ballY <= yl+pH/2)
        ballDX = -ballDX;
    else if(ballX >= 8.25 && ballY >= yr-pH/2 &&
       ballY <= yr+pH/2)	// Right paddle collision 
        ballDX = -ballDX;
   
    if(ballX < -10){	// left player misses ball
        scoreRight++;   // right player scores
        resetBall();	// start over
    } else if(ballX > 10){  // right player misses
        scoreLeft++;	// left player scores
        resetBall();	// start over
    }

    // Simple AI paddle
    if(ballY > yr)
        yr += 0.08;
    else
        yr -= 0.08;

    glutPostRedisplay();
}

// Keyboard control
void keyboard(unsigned char key,int x, int y)
{
    switch(key)
    {
        case 27:
            exit(0);
            break;

        case 'r':	// reset
            scoreLeft = scoreRight = 0;
            resetBall();
            break;

	case 'p':	// toggles pause
	    pause = pause ? false : true;
	    break;
    }
}

void specialKey(int key, int x, int y)
{
    switch(key)
    {
        case 27:
            exit(0);
            break;

	case GLUT_KEY_UP:
            yl += 0.5;
            break;

        case GLUT_KEY_DOWN:
            yl -= 0.5;
            break;

    }
}

// Visibility callback
void timerHandle ( int value )
{
   animate();
   glutPostRedisplay();
   // call timerHandle 25 ms later, 
   // 0 is passed to timerHandle, not used here 
   glutTimerFunc (25, timerHandle, 0);
}

void visHandle( int visible )
{
   if (visible == GLUT_VISIBLE)
      timerHandle ( 0 );
   else
      ;
}

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

    glutInitWindowSize(500,500);
    glutCreateWindow("Pong Game");

    glutDisplayFunc(display);
    glutVisibilityFunc(visHandle);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKey);

    init();

    glutMainLoop();

    return 0;
}

Discussions

Training a Transformer to Play Pong Game

  • Decoder-only (GPT style) Transformer

    See also Training an AI Transformer to Play Tic-Tac-Toe in C/C++

      Regard the game as a sequence of states and actions:
      	State Sn ~ Action An
      
      	a token ~ (S, A)
      
      	State  : (ballX, ballY, ballDX, ballDY, yl, yr)
      	Action : (paddle y = y + Δy) 
      	
      	During inference:
      	Given
      	  (S0 A0), (S1 A1), ......, (Sn-1, An-1)
      	The tranformer predicts the next state and action:
      	  (Sn, An)
      	
      Gather Data:
            Method 1: Rule-based expert:
      
      	  if(ballY > paddleY + d)
          	    Δy = D;
      	  else if(ballY < paddleY - d)
      	    Δy = -D;
      	  else
      	    Δy = 0;
      
      	  action = Δy;
      
      	The transformer learns to imitate the expert.	
            
             Method 2: Playing the game by humans:
      
      	 Play the game by humans and record sequences of
      	      (state, action) ~ token
      
      	Method 3: Reinforcement  Learning
      
      	  The transformer interacts with the game and optimizes rewards:
      	      +1 for hitting the ball
      	      -1 for missing
      	
  • Decision Transformer

      A Decision Transformer is a causal transformer (like GPT) that models trajectories as a sequence of 3 tokens:
      	Return: cumulative reward the agent aims to achieve 
      	State
      	Action
      
      	Example
      		Score = 9
      		State0
      		Δy = D (UP)
      
      		Score = 9
      		State1
      		Δy = 0 (STAY)
      
      		Score = 10
      		State2
      		Δy = -D (DOWN)
      
      	During training, we can mask the tokens so that the model only predicts the Action tokens 
      	
  •