/* CS 349 A1 Skeleton Code - Snake - - - - - - - - - - - - - - - - - - - - - - Commands to compile and run: g++ -o snake snake.cpp -L/usr/X11R6/lib -lX11 -lstdc++ ./snake Note: the -L option and -lstdc++ may not be needed on some machines. */ #include #include #include #include #include #include #include #include /* * Header files for X functions */ #include #include using namespace std; // Globals for xlib Colormap colours; XColor xcolour; char black[] = "rgb:00/00/00"; char white[] = "rgb:ff/ff/ff"; char lblue[] = "rgb:00/ff/ff"; char orange[] = "rgb:FF/45/00"; /* * Global game state variables */ const int Border = 1; const int BufferSize = 10; int FPS = 30; const int width = 800; const int height = 600; const int blockSize = 25; int velocity = 5; bool paused = true; bool starting = true; bool dead = false; /* * Information to draw on the window. */ typedef struct XInfo { Display * display; int screen; Window window; GC gc[3]; int width; // size of window int height; Pixmap buffer; } Xinfo; // get microseconds unsigned long now() { timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000000 + tv.tv_usec; } /* * Function to put out a message on error exits. */ void error(string str) { cerr << str << endl; exit(0); } // change the drawing coloursvoid changecolour(char colour[]) void changecolour(XInfo & xinfo, char colour[]) { XParseColor(xinfo.display, colours, colour, &xcolour); XAllocColor(xinfo.display, colours, &xcolour); XSetForeground(xinfo.display, xinfo.gc[0], xcolour.pixel); XFreeColormap(xinfo.display, colours); } /* * An abstract class representing displayable things. */ class Displayable { public: virtual void paint(XInfo & xinfo) = 0; }; class Fruit : public Displayable { public: virtual void paint(XInfo & xinfo) { changecolour(xinfo, orange); XFillArc(xinfo.display, xinfo.buffer, xinfo.gc[0], x, y, 25, 25, 0, 360 * 64); } Fruit() { newspot(); } int getx() { return x; } int gety() { return y; } void newspot() { x = (rand() % 32) * 25; y = (rand() % 24) * 25; } // ** ADD YOUR LOGIC ** /* * The fruit needs to be re-generated at new location every time a snake eats it. See the assignment webpage for more details. */ private: int x; int y; }; class Section: public Displayable { public: virtual void paint(XInfo & xinfo) { changecolour(xinfo, black); switch(direction) { case 1: XFillRectangle(xinfo.display, xinfo.buffer, xinfo.gc[0], x, y, blockSize, (len + 1) * blockSize); break; case 2: XFillRectangle(xinfo.display, xinfo.buffer, xinfo.gc[0], (x - len * blockSize), y, (1 + len) * blockSize, blockSize); break; case 3: XFillRectangle(xinfo.display, xinfo.buffer, xinfo.gc[0], x, (y - len * blockSize), blockSize, ((1 + len) * blockSize)); break; case 4: XFillRectangle(xinfo.display, xinfo.buffer, xinfo.gc[0], x, y, (len + 1) * blockSize, blockSize); break; } } void increment(int x) { len += x; } void decrement() { --len; } bool empty() { return (!len); } void grow() { switch(direction) { case 1: y -= blockSize; break; case 2: x += blockSize; break; case 3: y += blockSize; break; case 4: x -= blockSize; break; } ++len; } int getx() { return x; } int gety() { return y; } int getdir() { return direction; } int getlen() { return len; } Section(int x, int y, int dir, int len = 1): x(x), y(y), direction(dir), len(len) { blockSize = 25; } private: int x; int y; int len; int blockSize; int direction; }; class Snake : public Displayable { public: virtual void paint(XInfo & xinfo) { for (Section * each: sections) { each->paint(xinfo); } } void move(XInfo & xinfo) { if ((now() - lastmove < speed) || paused) { return; } // turn if a turn happens if (direction) { Section * newsec = new Section(sections.front()->getx(), sections.front()->gety(), direction, 0); direction = 0; sections.insert(sections.begin(), newsec); } // move 1 block forwards sections.front()->grow(); // see if fruit was eaten if (sections.front()->getx() == fruity->getx() && sections.front()->gety() == fruity->gety()) { fruit = 1; } // add fruit, remove 1 from end, delete if empty sections.back()->increment(fruit); if (fruit) fruity->newspot(); fruit = 0; sections.back()->decrement(); if (sections.back()->empty()) { delete sections.back(); sections.pop_back(); } if (sections.front()->getx() > width || sections.front()->gety() < 0) { dead = true; paused = true; } if (sections.front()->getx() < 0 || sections.front()->gety() > height) { dead = true; paused = true; } if (sections.size() <= 3) goto SKIP; for (auto each = sections.begin() + 1; each != sections.end(); ++each) { int tempdir = (*each)->getdir(); int frontx = sections.front()->getx(); int fronty = sections.front()->gety(); int eachx = (*each)->getx(); int eachy = (*each)->gety(); int eachlen = (*each)->getlen(); switch(tempdir) { case 1: if (frontx == eachx && (fronty >= eachy && fronty <= (eachy + blockSize * eachlen))) { dead = true; paused = true; } break; case 2: if ((frontx >= (eachx - eachlen * blockSize) && frontx <= eachx) && fronty == eachy) { dead = true; paused = true; } break; case 3: if (frontx == eachx && (fronty <= eachy && fronty >= (eachy - blockSize * eachlen))) { dead = true; paused = true; } break; case 4: if ((frontx <= (eachx - eachlen * blockSize) && frontx >= eachx) && fronty == eachy) { dead = true; paused = true; } break; } } SKIP: lastmove = now(); } void moveup() { if (direction == 4 || direction == 2 || direction == 0) direction = 1; } void moveright() { if (direction == 1 || direction == 3 || direction == 0) direction = 2; } void movedown() { if (direction == 4 || direction == 2 || direction == 0) direction = 3; } void moveleft() { if (direction == 1 || direction == 3 || direction == 0) direction = 4; } void changespeed(unsigned long x) { speed = 250000 / x; } void reset(int fast) { int size = sections.size(); for (int i = 0; i < size; i++) { delete sections.at(i); sections.erase(sections.begin() + i); } Section * newsec = new Section(x, y, 2, 2); sections.insert(sections.begin(), newsec); direction = 0; blockSize = 25; speed = 250000 / fast; lastmove = 0; } Snake(int x, int y, Fruit * fruity, unsigned long fast): x(x), y(y), fruity(fruity) { Section * newsec = new Section(x, y, 2, 2); sections.insert(sections.begin(), newsec); direction = 0; blockSize = 25; speed = 250000 / fast; lastmove = 0; } ~Snake() { int size = sections.size(); for (int i = 0; i < size; i++) { delete sections.at(i); sections.erase(sections.begin() + i); } } private: int x; int y; vector
sections; int blockSize; int direction; unsigned long speed; int fruit; Fruit * fruity; unsigned long lastmove; }; list dList; // list of Displayables Fruit fruity; Snake snake(100, 450, &fruity, velocity); /* * Initialize X and create a window */ void initX(int argc, char * argv[], XInfo & xInfo) { XSizeHints hints; unsigned long white, black; /* * Display opening uses the DISPLAY environment variable. * It can go wrong if DISPLAY isn't set, or you don't have permission. */ xInfo.display = XOpenDisplay(""); if (!xInfo.display) { error("Can't open display."); } /* * Find out some things about the display you're using. */ xInfo.screen = DefaultScreen(xInfo.display); colours = DefaultColormap(xInfo.display, xInfo.screen); white = XWhitePixel(xInfo.display, xInfo.screen); black = XBlackPixel(xInfo.display, xInfo.screen); hints.x = 100; hints.y = 100; hints.width = 800; hints.height = 600; hints.flags = PPosition | PSize; xInfo.window = XCreateSimpleWindow ( xInfo.display, // display where window appears DefaultRootWindow( xInfo.display ), // window's parent in window tree hints.x, hints.y, // upper left corner location hints.width, hints.height, // size of the window Border, // width of window's border black, // window border colour white ); // window background colour XSetStandardProperties ( xInfo.display, // display containing the window xInfo.window, // window whose properties are set "animation", // window's title "Animate", // icon's title None, // pixmap for the icon argv, argc, // applications command line args &hints ); // size hints for the window int depth = DefaultDepth(xInfo.display, DefaultScreen(xInfo.display)); xInfo.buffer = XCreatePixmap(xInfo.display, xInfo.window, hints.width, hints.height, depth); /* * Create Graphics Contexts */ int i = 0; xInfo.gc[i] = XCreateGC(xInfo.display, xInfo.window, 0, 0); XSetBackground(xInfo.display, xInfo.gc[0], WhitePixel(xInfo.display, xInfo.screen)); XSetFillStyle(xInfo.display, xInfo.gc[i], FillSolid); XSetLineAttributes(xInfo.display, xInfo.gc[i], 1, LineSolid, CapButt, JoinRound); XSelectInput(xInfo.display, xInfo.window, ButtonPressMask | KeyPressMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask); // for resize events /* * Put the window on the screen. */ XMapRaised( xInfo.display, xInfo.window ); XFlush(xInfo.display); } void splash(XInfo & xinfo) { changecolour(xinfo, white); XFillRectangle(xinfo.display, xinfo.buffer, xinfo.gc[0], 100, 100, 600, 400); changecolour(xinfo, black); XFontStruct * font = XLoadQueryFont(xinfo.display, "12x24"); XSetFont(xinfo.display, xinfo.gc[0], font->fid); XDrawString(xinfo.display, xinfo.buffer, xinfo.gc[0], 350, 200, "Snake", 5); XDrawString(xinfo.display, xinfo.buffer, xinfo.gc[0], 340, 240, "tsdedhar", 8); XDrawString(xinfo.display, xinfo.buffer, xinfo.gc[0], 340, 280, "20621325", 8); XDrawString(xinfo.display, xinfo.buffer, xinfo.gc[0], 265, 320, "Use arrow keys to turn", 22); XDrawString(xinfo.display, xinfo.buffer, xinfo.gc[0], 200, 360, "Collect fruit to grow your snake", 32); } void endgame(XInfo & xinfo) { changecolour(xinfo, white); XFillRectangle(xinfo.display, xinfo.buffer, xinfo.gc[0], 100, 100, 600, 400); changecolour(xinfo, black); XFontStruct * font = XLoadQueryFont(xinfo.display, "12x24"); XSetFont(xinfo.display, xinfo.gc[0], font->fid); XDrawString(xinfo.display, xinfo.buffer, xinfo.gc[0], 347, 200, "Game Over!", 10); XDrawString(xinfo.display, xinfo.buffer, xinfo.gc[0], 340, 240, "Press r to restart", 18); XDrawString(xinfo.display, xinfo.buffer, xinfo.gc[0], 342, 280, "Press q to quit", 15); } /* * Function to repaint a display list */ void repaint(XInfo & xinfo) { list::const_iterator begin = dList.begin(); list::const_iterator end = dList.end(); // get height and width of window (might have changed since last repaint) XWindowAttributes windowInfo; XGetWindowAttributes(xinfo.display, xinfo.window, &windowInfo); unsigned int height = windowInfo.height; unsigned int width = windowInfo.width; changecolour(xinfo, lblue); XFillRectangle(xinfo.display, xinfo.buffer, xinfo.gc[0], 0, 0, width, height); changecolour(xinfo, black); // draw display list while (begin != end) { Displayable * d = *begin; d->paint(xinfo); begin++; } if (starting) splash(xinfo); if (dead) { endgame(xinfo); } } void handleKeyPress(XInfo & xinfo, XEvent & event) { KeySym key; char text[BufferSize]; int i = XLookupString ( (XKeyEvent *)&event, // the keyboard event text, // buffer when text will be written BufferSize, // size of the text buffer &key, // workstation-independent key symbol NULL ); if (i == 1) { printf("Got key press -- %c\n", text[0]); switch(text[0]) { case 'q': error("Terminating normally."); break; case ' ': paused = !paused; starting = false; break; case 'r': snake.reset(velocity); paused = true; starting = true; dead = false; break; } } if (key == XK_Up) { snake.moveup(); } if (key == XK_Right) { snake.moveright(); } if (key == XK_Down) { snake.movedown(); } if (key == XK_Left) { snake.moveleft(); } } void handleAnimation(XInfo & xinfo, int inside) { snake.move(xinfo); } void eventLoop(XInfo & xinfo) { // Add stuff to paint to the display list dList.push_front(&snake); dList.push_front(&fruity); XEvent event; unsigned long lastRepaint = 0; int inside = 0; XWindowAttributes w; XGetWindowAttributes(xinfo.display, xinfo.window, &w); while(1) { if (XPending(xinfo.display) > 0) { XNextEvent(xinfo.display, &event); switch(event.type) { case KeyPress: handleKeyPress(xinfo, event); break; case EnterNotify: inside = 1; break; case LeaveNotify: inside = 0; break; } } unsigned long end = now(); if (end - lastRepaint > 1000000 / FPS) { handleAnimation(xinfo, inside); repaint(xinfo); XCopyArea(xinfo.display, xinfo.buffer, xinfo.window, xinfo.gc[0], 0, 0 , w.width, w.height, 0, 0); XFlush(xinfo.display); lastRepaint = now(); } if (XPending(xinfo.display) == 0) { usleep(1000000 / FPS - (end - lastRepaint)); } } } /* * Start executing here. * First initialize window. * Next loop responding to events. * Exit forcing window manager to clean up - cheesy, but easy. */ int main (int argc, char * argv[]) { if (argc == 1) { } else if (argc == 3) { FPS = atoi(argv[1]); velocity = atoi(argv[2]); if (atoi(argv[2]) < 1 || atoi(argv[2]) > 10) goto BADARGS; } else { BADARGS: printf("Incorrect arguments supplied\n"); exit(0); } snake.changespeed(velocity); XInfo xInfo; initX(argc, argv, xInfo); eventLoop(xInfo); XCloseDisplay(xInfo.display); }