/* 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; /* * Global game state variables */ const int Border = 1; const int BufferSize = 10; const int FPS = 30; const int width = 800; const int height = 600; const int blockSize = 25; /* * 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; } Xinfo; /* * Function to put out a message on error exits. */ void error(string str) { cerr << str << endl; exit(0); } /* * 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) { XFillRectangle(xinfo.display, xinfo.window, xinfo.gc[0], x, y, 25, 25); } Fruit() { newspot(); } void newspot() { x = (rand() % 800); y = (rand() % 600); } // ** 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) { switch(direction) { case 1: XFillRectangle(xinfo.display, xinfo.window, xinfo.gc[0], x, y, blockSize, len * blockSize); break; case 2: XFillRectangle(xinfo.display, xinfo.window, xinfo.gc[0], (x - len * blockSize), y, len * blockSize, blockSize); break; case 3: XFillRectangle(xinfo.display, xinfo.window, xinfo.gc[0], x, (y - len * blockSize), blockSize, len * blockSize); break; case 4: XFillRectangle(xinfo.display, xinfo.window, xinfo.gc[0], x, y, len * 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; } 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) { // 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() == 50 || sections.front()->gety() == 50) { 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) { printf("you're shit\n"); delete this; exit(0); } if (sections.front()->getx() < 0 || sections.front()->gety() > height) { printf("you're shit\n"); delete this; exit(0); } // ** ADD YOUR LOGIC ** // Here, you will be performing collision detection between the snake, // the fruit, and the obstacles depending on what the snake lands on. } /* * ** ADD YOUR LOGIC ** * Use these placeholder methods as guidance for implementing the snake behaviour. * You do not have to use these methods, feel free to implement your own. */ void didEatFruit() { } void didHitObstacle() { } 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; } Snake(int x, int y, Fruit * fruity): x(x), y(y), fruity(fruity) { Section * newsec = new Section(x, y, 2, 2); sections.insert(sections.begin(), newsec); direction = 0; blockSize = 25; speed = 1; } ~Snake() { for (auto each = sections.begin(); each != sections.end(); ++each) { delete *(sections.erase(each)); } } private: int x; int y; vector
sections; int blockSize; int direction; int speed; int fruit; Fruit * fruity; }; list dList; // list of Displayables Fruit fruity; Snake snake(100, 450, &fruity); /* * 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); 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 /* * Create Graphics Contexts */ int i = 0; xInfo.gc[i] = XCreateGC(xInfo.display, xInfo.window, 0, 0); XSetForeground(xInfo.display, xInfo.gc[i], BlackPixel(xInfo.display, xInfo.screen)); XSetBackground(xInfo.display, xInfo.gc[i], 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 | StructureNotifyMask); // for resize events /* * Put the window on the screen. */ XMapRaised( xInfo.display, xInfo.window ); XFlush(xInfo.display); } /* * 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; // big black rectangle to clear background // draw display list while (begin != end) { Displayable * d = *begin; d->paint(xinfo); begin++; } XFlush(xinfo.display); } 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; } } 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) { /* * ADD YOUR OWN LOGIC * This method handles animation for different objects on the screen and readies the next frame before the screen is re-painted. */ snake.move(xinfo); } // get microseconds unsigned long now() { timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000000 + tv.tv_usec; } 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); int depth = DefaultDepth(xinfo.display, DefaultScreen(xinfo.display)); // represents thing to draw Pixmap buffer = XCreatePixmap(xinfo.display, xinfo.window, w.width, w.height, depth); bool usebuffer = 1; 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) { Pixmap pixmap; // represents thing in buffer if (usebuffer) { pixmap = buffer; XSetForeground(xinfo.display, xinfo.gc[0], BlackPixel(xinfo.display, xinfo.screen)); XSetBackground(xinfo.display, xinfo.gc[0], WhitePixel(xinfo.display, xinfo.screen)); XFillRectangle(xinfo.display, pixmap, xinfo.gc[0], 0, 0, w.width, w.height); } else { pixmap = xinfo.window; // set this to the windowInfo XClearWindow(xinfo.display, pixmap); } //XSetForeground(xinfo.display, xinfo.gc[0], BlackPixel(xinfo.display, xinfo.screen)); //XSetBackground(xinfo.display, xinfo.gc[0], WhitePixel(xinfo.display, xinfo.screen)); handleAnimation(xinfo, inside); if (usebuffer) { XCopyArea(xinfo.display, pixmap, 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[]) { XInfo xInfo; initX(argc, argv, xInfo); eventLoop(xInfo); XCloseDisplay(xInfo.display); }