Programming Pong in C and OpenGL – Part VI

The latest version of my sample clone with scoreboard, etc.

The latest version of my sample clone!

Here’s the last part of my multi-part series on programming your very own pong clone in c and opengl!  It’s been a long time coming, so I hope you enjoy.  There’s lots of improvements in this version including a completely custom old-style “LED Alarm Clock” scoreboard, flashes on bounce, funky changing colors, improved collision detection, and a round ball! ;)

Here’s the code!


#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include <OpenAL/al.h>
#include <GLUT/glut.h>

#define PADDLE_MOVE_SPEED 350
#define CLEAR_COLOR_DECAY 2.0
#define BALL_RADIUS 10
#define BALL_FACETS 60
#define START_SPEED 200

int lastFrameTime = 0, windowWidth = 0, windowHeight = 0, now = 0;
float midWidth = 0, midHeight = 0;
float elapsedTime = 0, elapsedMilliSeconds = 0;
char player1Move = 0, player2Move = 0;
float player1V = 0, player2V = 0;

float PI = 3.14159265358979323846264;

//scores
int player1Score = 0, player2Score = 0;

//the following variables calculate a random starting projectile trajectory
float randomDirection;
long longMax = 0x7fffffff;	//max of longs 2^31-1 (2^(n-1)-1 for 2's complement) float trajectory[2] = {0, 0};

float projMove[2] = {0, 0};
double trajectory[2] = {0, 0};
short moveSpeed = START_SPEED;

//glitz and glamour
float player1ColorDirection[3];
float player2ColorDirection[3];
float player1Color[3];
float player2Color[3];
float colorMoveSpeed = 2.0;
double vectorLength1 = 0;
double vectorLength2 = 0;

float ballColorDirection[3];
float ballColor[3];
double ballVectorLength = 0;

//background explosion color
float clearColor[3];

int i = 0;

//scoreboard
char player1Num1[7] = {1, 1, 1, 0, 1, 1, 1};	//digital display set to zero, (each segment is enabled or disabled)
char player1Num2[7] = {1, 1, 1, 0, 1, 1, 1};
char player2Num1[7] = {1, 1, 1, 0, 1, 1, 1};	//digital display set to zero, (each segment is enabled or disabled)
char player2Num2[7] = {1, 1, 1, 0, 1, 1, 1};

//digit representations
char digit0[7] = {1, 1, 1, 0, 1, 1, 1};
char digit1[7] = {0, 0, 1, 0, 0, 0, 1};
char digit2[7] = {0, 1, 1, 1, 1, 1, 0};
char digit3[7] = {0, 1, 1, 1, 0, 1, 1};
char digit4[7] = {1, 0, 1, 1, 0, 0, 1};
char digit5[7] = {1, 1, 0, 1, 0, 1, 1};
char digit6[7] = {1, 1, 0, 1, 1, 1, 1};
char digit7[7] = {0, 1, 1, 0, 0, 0, 1};
char digit8[7] = {1, 1, 1, 1, 1, 1, 1};
char digit9[7] = {1, 1, 1, 1, 0, 1, 1};

void setSpaceEffect(void) {
for (i=0; i < 3; i++) {
player1ColorDirection[i] = (float) random() / (float) 0x7fffffff; //random vector
player2ColorDirection[i] = (float) random() / (float) 0x7fffffff;
ballColorDirection[i] = (float) random() / (float) 0x7fffffff;
}
//figure out vector lenngth
for (i=0; i < 3; i++) {
vectorLength1 += player1ColorDirection[i] * player1ColorDirection[i];
vectorLength2 += player2ColorDirection[i] * player2ColorDirection[i];
ballVectorLength += ballColorDirection[i] * ballColorDirection[i];
}
sqrt(vectorLength1);
sqrt(vectorLength2);
sqrt(ballVectorLength);
//normalize vector
for (i=0; i < 3; i++) {
player1ColorDirection[i] /= vectorLength1;
player2ColorDirection[i] /= vectorLength2;
ballColorDirection[i] /= ballVectorLength;
}
}

void calculateColor(float color[3], float colorDirection[3]) {
//caculate the next color based on the direction and elapsed time values
for (i=0; i < 3; i++) {
color[i] += colorDirection[i] * colorMoveSpeed * elapsedTime;
if (color[i] > 1) {
color[i] = 1;
colorDirection[i] *= -1;
}
else if (color[i] < 0) {
color[i] = 0;
colorDirection[i] *= -1;
}
}
}

void calculateClearColor(float color[3]) {
for (i=0; i < 3; i++) {
color[i] -= CLEAR_COLOR_DECAY * elapsedTime;
if(color[i] < 0)
color[i] = 0;
}
}

void setClearColor(float color[3]) {
for (i=0; i < 3; i++) {
color[i] = 1.0;
}
}

void resetProjectile(void) {
//set starting random trajectory
randomDirection = 2 * PI * (float) random() / (float) 0x7fffffff;
trajectory[0] = (float) cos(randomDirection);
trajectory[1] = (float) sin(randomDirection);
projMove[0] = midWidth;
projMove[1] = midHeight;
}

/*draw a digital (a la clock style) digit some place specified by x and y
*the enabled array is which segments of the display are enabled (counted starting from top left in a snake/8 fasion)
*
* 1 -
* 0| |2
* 3 -
* 4| |6
* 5 -
*/
void drawDigit(char enabled[7], float x, float y) {
glPushMatrix();
glTranslatef(x, y, 0);	//position digit to be drawn
//draw digital number (a la digital clock) segment by segment
glColor3f(1.0, 1.0, 1.0);
//draw segment 5
if(enabled[5]) {
glBegin(GL_QUADS);
glVertex2f(0,0);
glVertex2f(10,0);
glVertex2f(10,2);
glVertex2f(0,2);
glEnd();
}

//draw segment 3
if(enabled[3]) {
glBegin(GL_QUADS);
glVertex2f(0,9);
glVertex2f(10,9);
glVertex2f(10,11);
glVertex2f(0,11);
glEnd();
}

//draw segment 1
if(enabled[1]) {
glBegin(GL_QUADS);
glVertex2f(0,18);
glVertex2f(10,18);
glVertex2f(10,20);
glVertex2f(0,20);
glEnd();
}

//draw segment 4
if(enabled[4]) {
glBegin(GL_QUADS);
glVertex2f(0,2);
glVertex2f(2,2);
glVertex2f(2,9);
glVertex2f(0,9);
glEnd();
}

//draw segment 0
if(enabled[0]) {
glBegin(GL_QUADS);
glVertex2f(0,11);
glVertex2f(2,11);
glVertex2f(2,20);
glVertex2f(0,20);
glEnd();
}

//draw segment 6
if(enabled[6]) {
glBegin(GL_QUADS);
glVertex2f(8,2);
glVertex2f(10,2);
glVertex2f(10,9);
glVertex2f(8,9);
glEnd();
}

//draw segment 2
if(enabled[2]) {
glBegin(GL_QUADS);
glVertex2f(8,11);
glVertex2f(10,11);
glVertex2f(10,20);
glVertex2f(8,20);
glEnd();
}

glPopMatrix();
}

void setDigitEnablePattern(char enabled[7], char digit) {

switch(digit) {
case 0:
for(i=0; i<7; i++) {
enabled[i] = digit0[i];
}
break;
case 1:
for(i=0; i<7; i++) {
enabled[i] = digit1[i];
}
break;
case 2:
for(i=0; i<7; i++) {
enabled[i] = digit2[i];
}
break;
case 3:
for(i=0; i<7; i++) {
enabled[i] = digit3[i];
}
break;
case 4:
for(i=0; i<7; i++) {
enabled[i] = digit4[i];
}
break;
case 5:
for(i=0; i<7; i++) {
enabled[i] = digit5[i];
}
break;
case 6:
for(i=0; i<7; i++) {
enabled[i] = digit6[i];
}
break;
case 7:
for(i=0; i<7; i++) {
enabled[i] = digit7[i];
}
break;
case 8:
for(i=0; i<7; i++) {
enabled[i] = digit8[i];
}
break;
case 9:
for(i=0; i<7; i++) {
enabled[i] = digit9[i];
}
break;
}
}

void drawScore(char player1, char player2) {

if(player1 < 10) {
setDigitEnablePattern(player1Num1, 0);
setDigitEnablePattern(player1Num2, player1);
}
else if (player1 < 100) {
setDigitEnablePattern(player1Num1, player1 / 10);
setDigitEnablePattern(player1Num2, player1 - ((player1/10) * 10));
}
else {	//max out at 99
setDigitEnablePattern(player1Num1, 9);
setDigitEnablePattern(player1Num2, 9);
}

if(player2 < 10) {
setDigitEnablePattern(player2Num1, 0);
setDigitEnablePattern(player2Num2, player2);
}
else if (player2 < 100) {
setDigitEnablePattern(player2Num1, player2 / 10);
setDigitEnablePattern(player2Num2, player2 - ((player2/10) * 10));
}
else {	//max out at 99
setDigitEnablePattern(player2Num1, 9);
setDigitEnablePattern(player2Num2, 9);
}

drawDigit(player1Num1, (windowWidth/4)-15, 20);
drawDigit(player1Num2, (windowWidth/4)+5, 20);
drawDigit(player2Num1, ((windowWidth/4)*3)-15, 20);
drawDigit(player2Num2, ((windowWidth/4)*3)+5, 20);

}

void display(void)
{
//get window parameters
windowWidth = glutGet(GLUT_WINDOW_WIDTH);
windowHeight = glutGet(GLUT_WINDOW_HEIGHT);

midWidth = windowWidth/2;
midHeight = windowHeight/2;

//start timing code
if (lastFrameTime == 0) {
lastFrameTime = glutGet(GLUT_ELAPSED_TIME);
resetProjectile();	//since this is the fist time, this is actually a set :P
player1V = midHeight;
player2V = midHeight;
//initialize the space effect colors
setSpaceEffect();
glClearColor(0.0, 0.0, 0.0, 0.0);	//set clear color to black
}

now = glutGet(GLUT_ELAPSED_TIME);
elapsedMilliSeconds = now - lastFrameTime;
elapsedTime = elapsedMilliSeconds/1000.0f;
lastFrameTime = now;

calculateClearColor(clearColor);
glClearColor(clearColor[0], clearColor[1], clearColor[2], 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glShadeModel(GL_FLAT);

//draw score
drawScore(player1Score, player2Score);

//calculate move distances
switch (player1Move) {
case 1:		//move up
player1V += PADDLE_MOVE_SPEED * elapsedTime;
break;
case -1:	//move up
player1V -= PADDLE_MOVE_SPEED * elapsedTime;
break;
default:	//do nothing
break;
}
//check out of bounds
if (player1V > windowHeight-50) {
//bounce back
player1Move = -1;
}
if (player1V < 0+50) {
//bounce back other way
player1Move = 1;
}

switch (player2Move) {
case 1:		//move up
player2V += PADDLE_MOVE_SPEED * elapsedTime;
break;
case -1:	//move up
player2V -= PADDLE_MOVE_SPEED * elapsedTime;
break;
default:	//do nothing
break;
}
//check out of bounds
if (player2V > windowHeight-50) {
//bounce back
player2Move = -1;
}
if (player2V < 0+50) {
//bounce back other way
player2Move = 1;
}

//create and position player 1
glPushMatrix();

calculateColor(player1Color, player1ColorDirection);
glColor3f(player1Color[0], player1Color[1], player1Color[2]);	//player 1 color

glTranslatef(30.0f, player1V, 0.0f);

glBegin(GL_QUADS);
glVertex2f(-10, -50);
glVertex2f(-10, 50);
glVertex2f(10, 50);
glVertex2f(10, -50);
glEnd();

glPopMatrix();

//create and position player 2
glPushMatrix();

calculateColor(player2Color, player2ColorDirection);
glColor3f(player2Color[0], player2Color[1], player2Color[2]);	//player 2 color

glTranslatef(windowWidth-30, player2V, 0.0f);

glBegin(GL_QUADS);
glVertex2f(-10, -50);
glVertex2f(-10, 50);
glVertex2f(10, 50);
glVertex2f(10, -50);
glEnd();

glPopMatrix();

/***************************************
*	Projectile CODE!!!
*
***************************************/

//calculate move distances
projMove[0] += moveSpeed * trajectory[0] * elapsedTime;
projMove[1] += moveSpeed * trajectory[1] * elapsedTime;

//create and move projectile
glPushMatrix();

calculateColor(ballColor, ballColorDirection);
glColor3f(ballColor[0], ballColor[1], ballColor[2]);	//player 1 color

glTranslatef(projMove[0], projMove[1], 0.0f);

glBegin(GL_TRIANGLE_FAN);
for(i=0; i < 360; i += 360 / BALL_FACETS) {
glVertex2f(sin(i * PI / 180) * BALL_RADIUS, cos(i * PI / 180) * BALL_RADIUS);
}
glEnd();

glPopMatrix();

//set bounds for projectile
if (projMove[0] > (windowWidth)) {	//player 1 scored - out of bounds right
player1Score++;
moveSpeed = START_SPEED;
printf("Player 1 Scored!  Score: %f ", player1Score);
resetProjectile();
}

else if (projMove[0] < (0)) {		//player 2 scored - out of bounds left
player2Score++;
moveSpeed = START_SPEED;
resetProjectile();
}

else if (projMove[1] > (windowHeight-10)) {	//out of bounds top
trajectory[1] *= -1;
projMove[1] -= 0.5;
}

else if (projMove[1] < (10)) {	//out of bounds bottom
trajectory[1] *= -1;
projMove[1] += 0.5;
}

//collision detection code
else if (projMove[0] < 50 &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; projMove[0] > 49) {		//intersecting with line of left paddle
if (projMove[1] <= player1V + 50 &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; projMove[1] >= player1V-50) {
trajectory[0] *= -1;
moveSpeed += 30;
setClearColor(clearColor);
projMove[0] += 0.5;
}
}

else if (projMove[0] > windowWidth-50 &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; projMove[0] < windowWidth-49) {		//intersecting with line of right paddle
if (projMove[1] <= player2V + 50 &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; projMove[1] >= player2V-50) {
trajectory[0] *= -1;
moveSpeed += 30;
setClearColor(clearColor);
projMove[0] -= 0.5;
}
}

/******************************
*  END PROJECTILE CODE
*
******************************/

glutSwapBuffers();

}

void reshape(int width, int height)
{
glViewport(0, 0, width, height);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, width, 0, height);
glMatrixMode(GL_MODELVIEW);
}

void idle(void)
{
glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y) {
switch (key) {
case 'w':
//do something
player1Move=1;
glutPostRedisplay();
break;
case 's':
//do something
player1Move=-1;
glutPostRedisplay();
break;
case 'o':
//do something
player2Move=1;
glutPostRedisplay();
break;
case 'l':
//do something
player2Move=-1;
glutPostRedisplay();
break;
default:
//some other key
break;
}

}

void keyboardup(unsigned char key, int x, int y) {
switch (key) {
case 'w':
//do something
player1Move=0;
glutPostRedisplay();
break;
case 's':
player1Move=0;
glutPostRedisplay();
break;
case 'o':
player2Move=0;
glutPostRedisplay();
break;
case 'l':
player2Move=0;
glutPostRedisplay();
break;
default:
//some other key
break;
}

}

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

glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(500, 500);

glutCreateWindow("Tim GL");

glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutKeyboardUpFunc(keyboardup);
glutIdleFunc(idle);

glutMainLoop();
return EXIT_SUCCESS;
}

I delayed so long because I wanted to refactor this code to better separate components and functions, but I didn’t get the time to do so. (That’s why I should do it that way from the start!) You can get a direct copy of the binary here.

Share this Post!
  • Digg
  • Reddit
  • del.icio.us
  • Facebook
  • Technorati
  • Propeller
  • NewsVine
  • Google Bookmarks
  • StumbleUpon
  • Ma.gnolia
  • YahooMyWeb
  • Netvouz
  • blogmarks

Tags: , , , , , , ,

2 Responses to “Programming Pong in C and OpenGL – Part VI”

  1. Lionel Barret says:

    I am no expert in C but no struct and a lot of magic integers ?
    The digit/enabled stuff is also not very straight…

    I suppose the code could have another round of refactoring.

    But hey, it is a good start.

  2. Definitely. I cringed every time I added a global variable, but it was a late night experiment and I kept putting it off :/ . Maybe in the next version ;)

Leave a Reply