You are here: start » Snippets » glutSnake

glutSnake

Description

glutSnake is a smallish Snake game 1) developed as a first introduction into OpenGL programming.

There are still several bugs in it, but at least you can move the snake around, eat apples, grow and die when biting yourself. The walls are buggy, I wonder why… Ah well ;-)

Screenshot

glutsnake-screen.jpg

Controls

  • A - Snake turns left
  • D - Snake turns right
  • - Camera turns left
  • - Camera turns right
  • and - Tilts camera
  • P - Pauses the game
  • Q - Quits the game
  • M - Prints an ASCII representation of the current game field to stdout

Download

Get the source tarball here: glutSnake-20051022.tar.gz
Uncompress it, then do a make and after the compile finished just start up the executable.

Alternatively, here is a precompiled and zipped .exe for the win32 users: glutSnake.exe.zip
I compiled it under Windows XP Pro with Dev-C++ following this instructions.

Annotation

The code was hacked together in around 14 hours on a rainy weekend, so it is probably ugly as hell. I'm also not sure about the non-existance of memory leaks, and I'm not sure either I will ever improve it, as the whole coding session indeed was just to make some first steps in OpenGL and GLUT and to refresh my C++ knowledge in order to prepair for a course in university.

The field is too big, the graphics suck and the handling could be improved as well. Now that we both agree on the fact that this game is stupid AND sucks, let's continue with the code…

Details

glutSnake.cpp

#include <iostream>
#include <cstdlib>
#include <GL/glut.h>
 
#include "GlutSnakeConstants.h"
#include "HistoryQueue.h"
 
using namespace std;
 
static int win;
 
double anglex = 25.0;
double angley = 0.0;
double camerax = 0.0;
double cameraz = 0.0;
 
// position of the snake's head
int posx = 0;
int posz = 0;
 
// current direction
int curDirection = DIRECTION_NORTH;
bool dirChanged = false;
 
// true if game initialised and started
bool started = false;
 
// movement history
HistoryQueue *movementHistory = new HistoryQueue();
 
// length
int curLength = 5;
 
// game over?
bool gameOver = false;
 
// paused
bool pausedGame = false;
 
// game grid
char gameGrid[100][100];
 
// position of the current apple
int appleX = 0;
int appleZ = 0;
 
// score
int score = 0;
 
// *** Prototypes
 
void initOpenGL(void);
void handleKeyboard(unsigned char key, int x, int y);
void handleSpecialKeys(int key, int x, int y);
void handleResize(int width, int height);
void handleAutomaticMovement(int value);
void display(void);
void paintSnake(int length);
void paintGrid(void);
void paintWall(void);
void paintApple(void);
void setCamera(void);
void buildSnakeSegment(void);
void buildGrid(void);
void moveForward(void);
 
void resetGameGrid(void);
void printGameGrid(void);
void addSnakePosition(int x, int z);
void addSnakeHeadPosition(int x, int z);
void addApplePosition(int x, int z);
bool isCollision(int x, int z);
bool foundApple(int x, int z);
 
void handleGameOver(void);
void handleQuit(void);
 
void generateNewApple(void);
 
/**
 * Some initialisation stuff for the OpenGL engine
 */
void initOpenGL() {
    //glShadeModel(GL_SMOOTH);
    glClearColor(0.0f, 0.0f, 0.0f, 0.2f);
 
    glEnable(GL_DEPTH_TEST);
    //glDepthFunc(GL_LEQUAL);
    glClearDepth(1.0f);
 
    //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    //glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
 
    //glEnable(GL_COLOR_MATERIAL);
    //glEnable(GL_LINE_SMOOTH);
    //glEnable(GL_POLYGON_SMOOTH);
 
    //glEnable(GL_BLEND);
    //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE);
 
    //glDepthMask(GL_FALSE);
 
    //glEnable(GL_LIGHTING);
    //glEnable(GL_LIGHT0);
 
    if (DEBUG) cout << "OpenGL initialised." << endl;
}
 
/**
 * Reset the game grid - everything empty besides walls
 */
void resetGameGrid(void) {
	int x, z;
 
	for (x=0; x<100; x++) {
 
		for (z=0; z<100; z++) {
			gameGrid[x][z] = ' ';
		}
	}
 
	for (x=0; x<100; x++) {
		gameGrid[0][x] = 'w';
		gameGrid[98][x] = 'w';
		gameGrid[x][0] = 'w';
		gameGrid[x][98] = 'w';
	}	
}
 
/**
 * Print out the game grid
 */
void printGameGrid(void) {
	int x, z;
 
	for (z=0; z<100; z++) {
		for (x=0; x<100; x++) {
			cout << gameGrid[x][z];
		}
		cout << endl;
	}
}
 
/**
 * Add snake segment to game grid 
 */
void addSnakePosition(int x, int z) {
	gameGrid[x + 49][z + 49] = 's';
}
 
/**
 * Add snake head to game grid
 */
void addSnakeHeadPosition(int x, int z) {
	gameGrid[x + 49][z + 49] = 'h';
}
 
/**
 * Add apple to game grid
 */
void addApplePosition(int x, int z) {
	gameGrid[x + 49][z + 49] = 'a';
}
 
/**
 * Spawn a new apple 
 */
void generateNewApple() {
	int aX, aZ;
	int randX, randZ;
 
	do {
		srand(time(NULL));
 
		randX = ((int)(rand() % 99));
		randZ = ((int)(rand() % 99));
		aX = randX - 49;
		aZ = randZ - 49;
	} while (isCollision(aX, aZ));
 
	appleX = aX;
	appleZ = aZ;
 
	paintApple();
	addApplePosition(appleX, appleZ);
	if (DEBUG) cout << "Apple: (" << appleX << ", " << appleZ << ") - (" << randX << ", " << randZ << ")" << endl;
}
 
/**
 * Returns true if given coordinates are already taken by wall or snake
 */
bool isCollision(int x, int z) {
	char point;
 
	point = gameGrid[x + 49][z + 49];
 
	if ((point == 'w') || (point == 's')) return true;
	else return false;
}
 
/**
 * Returns true if given coordinates are taken by apple
 */
bool foundApple(int x, int z) {
	char point;
 
	point = gameGrid[x + 49][z + 49];
 
	if (point == 'a') return true;
	else return false;
}
 
/**
 * Keyboard handler
 */
void handleKeyboard(unsigned char key, int x, int y) {
	if (key == 'q') {
		glutDestroyWindow(win);
		handleQuit();
		exit(0);
	} else if (key == 'p') {
		pausedGame = !pausedGame;
	} else if (key == 'a') {
		if (!dirChanged) {
			curDirection--;
			while (curDirection < 0) curDirection += 4;
			dirChanged = true;
		}
	} else if (key == 'd') {
		if (!dirChanged) {
			curDirection++;
			while (curDirection > 3) curDirection -= 4;
			dirChanged = true;
		}
	} else if (key == 'm') {
		printGameGrid();
	}
}
 
/**
 * Keyboard handler (special keys)
 */
void handleSpecialKeys(int key, int x, int y) {
	if (key == GLUT_KEY_UP) {
		if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) cameraz += 0.2;
		else anglex += 5.0;
		glutPostRedisplay();
	} else if (key == GLUT_KEY_DOWN) {
		if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) cameraz -= 0.2;
		else anglex -= 5.0;
		glutPostRedisplay();
	} else if (key == GLUT_KEY_LEFT) {
		if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) camerax += 0.2;
		else angley -= 5.0;
		glutPostRedisplay();
	} else if (key == GLUT_KEY_RIGHT) {
		if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) camerax -= 0.2;
		else angley += 5.0;
		glutPostRedisplay();
	}
}
 
/**
 * Resize handler
 */
void handleResize(int width, int height) {
	if (height == 0) height = 1;
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 200.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glutPostRedisplay();
}
 
/**
 * Automatic movement handler
 */
void handleAutomaticMovement(int value) {
	moveForward();
	glutPostRedisplay();
	if (!gameOver) glutTimerFunc(150, handleAutomaticMovement, 0);
}
 
/**
 * Moves the snake forward one entity
 */
void moveForward() {
	// Did the snake collide with something?
	if (isCollision(posx, posz)) gameOver = true;
	if (gameOver) handleGameOver();
 
	if ((!gameOver) && (!pausedGame)) {
		// move in direction
		switch (curDirection) {
			case DIRECTION_NORTH : posz += 1; movementHistory->prepend(DIRECTION_NORTH); break;
			case DIRECTION_WEST  : posx -= 1; movementHistory->prepend(DIRECTION_WEST); break;
			case DIRECTION_SOUTH : posz -= 1; movementHistory->prepend(DIRECTION_SOUTH); break;
			case DIRECTION_EAST  : posx += 1; movementHistory->prepend(DIRECTION_EAST); break;
		}
		if (DEBUG) cout << "Pos: (" << posx << ", " << posz << ") - ";
 
		// add direction to the movement history
		if (DEBUG) movementHistory->printQueue();
 
		// allow new changes in direction again
		dirChanged = false;
	}
 
	if (foundApple(posx, posz)) {
		curLength++;
		score += 10;
		generateNewApple();
		if (DEBUG) cout << "Found apple!" << endl;
	}
 
	// repaint
	glutPostRedisplay();
 
	// reset map
	resetGameGrid();
 
	if (!started) {
		generateNewApple();
		started = true;
	}
}	
 
/**
 * Print game grid and game over message
 */
void handleGameOver() {
	cout << "GAME OVER." << endl;
	cout << "Final length: " << curLength << endl;
	cout << (curLength - 5) << " apples eaten." << endl;
	cout << "Score: " << score << endl;
}
 
/**
 * Print game grid and score
 */
void handleQuit() {
	cout << "GAME OVER." << endl;
	cout << "Final length: " << curLength << endl;
	cout << (curLength - 5) << " apples eaten." << endl;
	cout << "Score: " << score << endl;
}
 
/**
 * Display scene
 */
void display() {
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
	glLoadIdentity();
	setCamera();
	paintGrid();
	paintWall();
	paintApple();
	paintSnake(curLength);
 
	glutSwapBuffers();
}
 
/**
 * Paint the apple
 */
void paintApple() {
	glColor3f(1.0, 0.0, 0.0);
 
	glPushMatrix();
	glTranslatef(1.0 * appleX, 0.0, 1.0 * appleZ);
	glCallList(SNAKE_SEGMENT_LIST);
	addApplePosition(appleX, appleZ);
	glPopMatrix();
}
 
/**
 * Paints 'length' snake segments
 * 
 * @param length The count of the segments to paint
 */
void paintSnake(int length) {
	int i, direction;
	int segmentX, segmentZ;
	double x, z;
	double r, g, b;
	double colorStepR, colorStepB, colorStepG;
	double startR, startG, startB;
	double endR, endG, endB;
	bool paintIt;
 
	startR = 0.0; startG = 0.0; startB = 1.0;
	endR = 0.0; endG = 0.0; endB = 0.5;
 
	colorStepR = (endR - startR) / length;
	colorStepG = (endG - startG) / length;
	colorStepB = (endB - startB) / length;
 
	r = startR; g = startG; b = startB; 
	segmentX = posx; segmentZ = posz;
 
	glColor4f(r, g, b, 1.0);
	glPushMatrix();
 
	// paint the head of the snake
	glTranslatef(1.0 * posx, 0.0, 1.0 * posz);
	glCallList(SNAKE_SEGMENT_LIST);
	addSnakeHeadPosition(posx, posz);
 
	for (i=1; i<length; i++) { 
		paintIt = false;
 
		// change color
		r += colorStepR; g += colorStepG; b += colorStepB;
 
		// get direction
		direction = movementHistory->getNextDirection();
		x = 0.0; z = 0.0;
		// determine which coordinates to use for next segment according to saved direction
		switch (direction) {
			case DIRECTION_NORTH : z = -1.0; segmentZ -= 1; paintIt = true; break;
			case DIRECTION_WEST  : x = +1.0; segmentX += 1; paintIt = true; break;
			case DIRECTION_SOUTH : z = +1.0; segmentZ += 1; paintIt = true; break;
			case DIRECTION_EAST  : x = -1.0; segmentX -= 1; paintIt = true; break;
		}
 
		// Paint the snake segment if it is already displayable
		if (paintIt) {
			glColor4f(r, g, b, 1.0);
			glTranslatef(x, 0.0, z);
			glCallList(SNAKE_SEGMENT_LIST);
			addSnakePosition(segmentX, segmentZ);
		}
	}
	movementHistory->clipRest();
	movementHistory->reset();
	glPopMatrix();
}
 
/**
 * Paint a grid
 */
void paintGrid() {
	glCallList(GRID_LIST);
}
 
/** 
 * Paint the walls
 */
void paintWall() {
 
	glColor3f(0.5, 0.0, 0.0);
 
	//glLoadIdentity();
	glPushMatrix();
	glTranslatef(0.0, 0.0, 49.0);
	glCallList(WALL_SEGMENT_LIST);
	glPopMatrix();
 
	glPushMatrix();	
	glTranslatef(0.0, 0.0, -49.0);
	glCallList(WALL_SEGMENT_LIST);
	glPopMatrix();
 
	glPushMatrix();	
	glTranslatef(-49.0, 0.0, 0.0);
	glRotatef(90.0, 0.0, 1.0, 0.0);
	glCallList(WALL_SEGMENT_LIST);	
	glPopMatrix();
 
	glPushMatrix();	
	glTranslatef(49.0, 0.0, 0.0);
	glRotatef(90.0, 0.0, 1.0, 0.0);
	glCallList(WALL_SEGMENT_LIST);	
	glPopMatrix();
 
	glPopMatrix();
}
 
/**
 * Set camera
 */
void setCamera() {
	glTranslatef(0.0, 0.0, -25.0);
 
	glTranslatef(camerax, 0.0, cameraz);
 
	glRotatef(anglex, 1.0, 0.0, 0.0);
	glRotatef(angley, 0.0, 1.0, 0.0);
}	
 
/**
 * Build a single snake segment
 */
void buildSnakeSegment() {
	glNewList(SNAKE_SEGMENT_LIST, GL_COMPILE);
		glBegin(GL_QUADS);
			// front
			glVertex3f(-0.4, -0.4, 0.4);
			glVertex3f(0.4, -0.4, 0.4);
			glVertex3f(0.4, 0.4, 0.4);
			glVertex3f(-0.4, 0.4, 0.4);
			// left side
			glVertex3f(-0.4, -0.4, -0.4);
			glVertex3f(-0.4, -0.4, 0.4);
			glVertex3f(-0.4, 0.4, 0.4);
			glVertex3f(-0.4, 0.4, -0.4);
			// back
			glVertex3f(-0.4, 0.4, -0.4);
			glVertex3f(0.4, 0.4, -0.4);
			glVertex3f(0.4, -0.4, -0.4);
			glVertex3f(-0.4, -0.4, -0.4);
			// right side
			glVertex3f(0.4, 0.4, -0.4);
			glVertex3f(0.4, 0.4, 0.4);
			glVertex3f(0.4, -0.4, 0.4);
			glVertex3f(0.4, -0.4, -0.4);
			// top
			glVertex3f(-0.4, 0.4, 0.4);
			glVertex3f(0.4, 0.4, 0.4);
			glVertex3f(0.4, 0.4, -0.4);
			glVertex3f(-0.4, 0.4, -0.4);
			// bottom
			glVertex3f(-0.4, -0.4, 0.4);
			glVertex3f(0.4, -0.4, 0.4);
			glVertex3f(0.4, -0.4, -0.4);
			glVertex3f(-0.4, -0.4, -0.4);
		glEnd();
	glEndList();
}
 
/**
 * Build the grid
 */
void buildGrid() {
	int i;
 
	glNewList(GRID_LIST, GL_COMPILE);
		glColor4f(0.0, 0.0, 0.0, 1.0);
		glBegin(GL_QUADS);
			glVertex3f(-101.0, -0.5, -101.0);
			glVertex3f(101.0, -0.5, -101.0);
			glVertex3f(101.0, -0.5, 101.0);
			glVertex3f(-101.0, -0.5, 101.0);
		glEnd();
		glColor3f(0.0, 0.5, 0.0);
		glLineWidth(1.0);
		glBegin(GL_LINES);
			for (i=0; i<101; i++) {
				// horizontal
				glVertex3f(-50.5, -1.0, (-50.5 + (i * 1.0)));
				glVertex3f(50.5, -1.0, (-50.5 + (i * 1.0)));
				// vertical
				glVertex3f((-50.5 + (i * 1.0)), -1.0, -50.5);
				glVertex3f((-50.5 + (i * 1.0)), -1.0, 50.5);
			}
		glEnd();
	glEndList();
}
 
/**
 * Build a wall segment
 */
void buildWallSegment() {
	glNewList(WALL_SEGMENT_LIST, GL_COMPILE);
		glBegin(GL_QUADS);
			// front
			glVertex3f(-50.5, -0.5, 0.5);
			glVertex3f(50.5, -0.5, 0.5);
			glVertex3f(50.5, 0.5, 0.5);
			glVertex3f(-50.5, 0.5, 0.5);
			// left side
			glVertex3f(-50.5, -0.5, -0.5);
			glVertex3f(-50.5, -0.5, 0.5);
			glVertex3f(-50.5, 0.5, 0.5);
			glVertex3f(-50.5, 0.5, -0.5);
			// back
			glVertex3f(-50.5, 0.5, -0.5);
			glVertex3f(50.5, 0.5, -0.5);
			glVertex3f(50.5, -0.5, -0.5);
			glVertex3f(-50.5, -0.5, -0.5);
			// right side
			glVertex3f(50.5, 0.5, -0.5);
			glVertex3f(50.5, 0.5, 0.5);
			glVertex3f(50.5, -0.5, 0.5);
			glVertex3f(50.5, -0.5, -0.5);
			// top
			glVertex3f(-50.5, 0.5, 0.5);
			glVertex3f(50.5, 0.5, 0.5);
			glVertex3f(50.5, 0.5, -0.5);
			glVertex3f(-50.5, 0.5, -0.5);
			// bottom
			glVertex3f(-50.5, -0.5, 0.5);
			glVertex3f(50.5, -0.5, 0.5);
			glVertex3f(50.5, -0.5, -0.5);
			glVertex3f(-50.5, -0.5, -0.5);
		glEnd();
	glEndList();
}
 
/**
 * Main function
 */
int main(int argc, char **argv) {
 
	movementHistory = new HistoryQueue();
 
	// init glut
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
 
	// prepare the window
	glutInitWindowSize(640, 480);
	glutInitWindowPosition(200, 200);
 
	// init the rest of OpenGL
	initOpenGL();
 
	// create the window
	win = glutCreateWindow("glutSnake");
 
	// build stuff
	buildSnakeSegment();
	buildGrid();
	buildWallSegment();
 
	// callbacks
	glutReshapeFunc(handleResize);
	glutDisplayFunc(display);
	glutKeyboardFunc(handleKeyboard);
	glutSpecialFunc(handleSpecialKeys);
	glutIdleFunc(display);
	glutTimerFunc(250, handleAutomaticMovement, 0);
 
	// enter main loop
	glutMainLoop();
 
	// exit
	exit(0);	
}

HistoryQueue.h

#include "HistoryQueueNode.h"
 
class HistoryQueue {
 
private:
	HistoryQueueNode *head, *tail, *currentNode;
 
public:
	HistoryQueue(void);
 
	void prepend(int direction);
 
	int getNextDirection();
 
	void clipRest();
 
	void reset();
 
	bool isEmpty();
 
	void printQueue();
}
;

HistoryQueue.cpp

#include <iostream>
 
#include "HistoryQueue.h"
#include "GlutSnakeConstants.h"
 
using namespace std;
 
HistoryQueue::HistoryQueue() {
	head = tail = NULL;
	reset();
}
 
void HistoryQueue::prepend(int direction) {
	HistoryQueueNode *node = new HistoryQueueNode();
	node->setDirection(direction);
 
	if (head == NULL) {
		head = node;
		tail = node;
	} else {
		node->setNext(head);
		head = node;
	}
}
 
int HistoryQueue::getNextDirection() {
	int direction;
 
	if (currentNode == NULL) return DIRECTION_UNDEF;
 
	direction = currentNode->getDirection();
	currentNode = currentNode->getNext();
	return direction;
}
 
void HistoryQueue::clipRest() {
	HistoryQueueNode *node, *temp;
 
	if (currentNode == NULL) return;
	node = currentNode->getNext();
	while (node != NULL) {
		temp = node;
		node = node->getNext();
		delete(temp);
	}
	tail = currentNode;
	currentNode->setNext(0);
}
 
void HistoryQueue::reset() {
	currentNode = head;
}
 
bool HistoryQueue::isEmpty() {
	if (head == NULL) return true;
	else return false;
}
 
void HistoryQueue::printQueue() {
	HistoryQueueNode *node;
 
	node = head;
 
	cout << "[";
	while (node != NULL) {
		cout << " " << node->getDirection();
		node = node->getNext();
	}
	cout << " ]" << endl;
}

HistoryQueueNode.h

class HistoryQueueNode {
 
private:
	int direction;
	HistoryQueueNode* next;
 
public:
	HistoryQueueNode(void);
 
	void setDirection(int dir);
 
	int getDirection();
 
	void setNext(HistoryQueueNode* node);
 
	HistoryQueueNode* getNext();
}
;

HistoryQueueNode.cpp

#include "HistoryQueueNode.h"
#include "GlutSnakeConstants.h"
 
using namespace std;
 
HistoryQueueNode::HistoryQueueNode() {
	direction = DIRECTION_UNDEF;
	next = 0;
}
 
void HistoryQueueNode::setDirection(int dir) {
	direction = dir;
}
 
int HistoryQueueNode::getDirection() {
	return direction;
}
 
void HistoryQueueNode::setNext(HistoryQueueNode* node) {
	next = node;
}
 
HistoryQueueNode* HistoryQueueNode::getNext() {
	return next;
}

GlutSnakeConstants.h

#define DEBUG 0
 
#define SNAKE_SEGMENT_LIST 1
#define GRID_LIST 2
#define WALL_SEGMENT_LIST 3
 
#define DIRECTION_NORTH 0
#define DIRECTION_WEST 1
#define DIRECTION_SOUTH 2
#define DIRECTION_EAST 3
#define DIRECTION_UNDEF -1
1) You know, that old scheme: Snake runs around in an closed area, hunts down apples, grows with each eaten apple and dies a horrible death when running heads first into a wall or itself

Discussion

Enter your comment (wiki syntax is allowed):
SMPOQ
snippets/glutsnake.txt · Last modified: 2008/03/26 12:35 (external edit)