====== glutSnake ====== ===== Description ===== glutSnake is a smallish Snake game ((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)) 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 ===== {{snippets:glutsnake-screen.jpg}} ===== Controls ===== * a - Snake turns left * d - Snake turns right * Left - Camera turns left * Right - Camera turns right * Up and Down - 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: {{snippets:glutsnake-20051022.tar.gz|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: {{snippets:glutsnake.exe.zip|glutSnake.exe.zip}}\\ I compiled it under Windows XP Pro with [[http://www.bloodshed.net/devcpp.html|Dev-C++]] following [[http://people.bath.ac.uk/ab8lam/computing/DevCpp.htm|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 #include #include #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; igetNextDirection(); 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 #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 ~~DISCUSSION~~