Snake

Dependencies:   microbit

Fork of microbit_snake by Mohammad Khan

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /*
00002 The MIT License (MIT)
00003 Copyright (c) 2016 British Broadcasting Corporation.
00004 This software is provided by Lancaster University by arrangement with the BBC.
00005 Permission is hereby granted, free of charge, to any person obtaining a
00006 copy of this software and associated documentation files (the "Software"),
00007 to deal in the Software without restriction, including without limitation
00008 the rights to use, copy, modify, merge, publish, distribute, sublicense,
00009 and/or sell copies of the Software, and to permit persons to whom the
00010 Software is furnished to do so, subject to the following conditions:
00011 The above copyright notice and this permission notice shall be included in
00012 all copies or substantial portions of the Software.
00013 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00014 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00015 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
00016 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00017 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
00018 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
00019 DEALINGS IN THE SOFTWARE.
00020 */
00021 
00022 #include "MicroBit.h"
00023 #include "DigitalOut.h"
00024 
00025 #define SNAKE_HEAD_PIXEL_BRIGHTNESS     150
00026 #define SNAKE_BONE_PIXEL_BRIGHTNESS     15
00027 #define SNAKE_FOOD_PIXEL_BRIGHTNESS     255
00028 
00029 template <class T> class Node{
00030 public:
00031     Node(T data): d(data), next(NULL), prev(NULL) {}
00032     
00033     Node * getNext(){
00034         return next;
00035     }
00036     Node * getPrev(){
00037         return prev;
00038     }
00039     void detach () {
00040         if (prev){
00041             prev->next = next;
00042         }
00043         if (next){
00044             next->prev = prev;
00045         }
00046         
00047         next = NULL;
00048         prev = NULL;
00049     } 
00050     void append(Node * node) {
00051         Node * n = this;
00052         while (n->getNext()){
00053             n = n->getNext();
00054         }
00055         n->next = node;
00056         if (node){
00057             node->prev = n;
00058         }
00059     }
00060 public:
00061     T d;
00062 private:
00063     Node * next;
00064     Node * prev;
00065 };
00066 
00067 template <class T> class List {
00068 public:
00069     List(): head(NULL), tail(NULL){
00070     }
00071     void prepend(Node<T> * node){
00072         if (node){
00073             if (head == NULL && tail == NULL) {
00074                 head = tail = node;
00075             } else {
00076                 node->append(head);
00077                 head = node;
00078             }
00079         }
00080     }
00081     Node<T> * removeTail(){
00082         Node<T> * node = tail;
00083         if (tail) {
00084             tail = tail->getPrev();
00085             node->detach();
00086             if (tail == NULL){
00087                 head = NULL;
00088             }
00089         }
00090         return node;
00091     }   
00092     Node<T> * getHead(){
00093         return head;
00094     }
00095     Node<T> * getTail(){
00096         return tail;
00097     } 
00098     void cleanup(){
00099         Node<T> * node = tail;
00100         while(node){
00101             Node<T> * temp = node;
00102             node = node->getPrev();
00103             
00104             temp->detach();
00105             delete temp;
00106         }
00107         head = tail = NULL;
00108     }
00109 private:
00110     Node<T> *   head;
00111     Node<T> *   tail;
00112 };
00113 
00114 class Dimension
00115 {
00116 public:
00117     Dimension (int start, int end) {
00118         this->start = start;
00119         this->end = end;
00120         this->cur = start;
00121     }
00122     
00123     Dimension (int start, int end, int cur) {
00124         this->start = start;
00125         this->end = end;
00126         this->cur = cur;
00127     }
00128     
00129     void operator ++(int){
00130         cur++;
00131         if (cur > end){
00132             cur = start;
00133         }
00134     }
00135     
00136     void operator --(int){
00137         cur--;
00138         if (cur < start){
00139             cur = end;
00140         }
00141     }
00142     
00143     int operator -(int i){
00144         int r = cur - i;
00145         if (r < start){
00146             r = end;
00147         }
00148         return r;
00149     }
00150     
00151     operator int(){
00152         return cur;
00153     }
00154 private:
00155     int start;
00156     int end;
00157     int cur;
00158 };
00159 
00160 
00161 class SnakeBone {
00162 public:
00163     SnakeBone(int startx, int endx, int curx, int starty, int endy, int cury):
00164         x(startx, endx, curx), y(starty, endy, cury)
00165         {
00166         }
00167         
00168     Dimension getX(){
00169         return x;
00170     }
00171     
00172     Dimension getY(){
00173         return y;
00174     }
00175 
00176 private:
00177     Dimension x;
00178     Dimension y;
00179 };
00180 
00181 
00182 class Snake {
00183 public:
00184     enum Direction {
00185         UP,
00186         DOWN,
00187         LEFT,
00188         RIGHT
00189     };
00190 
00191     Snake (): d(UP){
00192         bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, 2, 0, 4, 2)));
00193     }
00194     
00195     Node<SnakeBone> * getHead(){
00196         return bones.getHead();
00197     }
00198     Dimension getHeadX(){
00199         return _getNodeX(bones.getHead());
00200     }
00201     Dimension getHeadY(){
00202         return _getNodeY(bones.getHead());
00203     }
00204     Dimension getTailX(){
00205         return _getNodeX(bones.getTail());
00206     }
00207     Dimension getTailY(){
00208         return _getNodeY(bones.getTail());
00209     }
00210     
00211     void grow(){
00212         // Find next position to place new head
00213         Dimension nextX = getHeadX();
00214         Dimension nextY = getHeadY();
00215         
00216         switch (d){
00217             case UP: {
00218                 nextY--;
00219             }
00220             break;
00221             case DOWN: {
00222                 nextY++;
00223             }
00224             break;
00225             case LEFT: {
00226                 nextX--;
00227             }
00228             break;
00229             case RIGHT: {
00230                 nextX++;
00231             }
00232             break;
00233         }
00234         
00235         // New bone at head 
00236         bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, nextX, 0, 4, nextY)));            
00237     }
00238     
00239     void reduce(){
00240         Node<SnakeBone> * tail = bones.removeTail();
00241         if (tail){
00242             delete tail;
00243         }
00244     }
00245     
00246     void left(){
00247         switch (d){
00248             case UP:
00249             case DOWN: {
00250                 d = LEFT;
00251             }
00252             break;
00253             case LEFT:  {
00254                 d = DOWN;
00255             }
00256             break;
00257             case RIGHT: {
00258                 d = UP;
00259             }
00260             break;
00261         }
00262     }
00263     
00264     void right(){
00265         switch (d){
00266             case UP:
00267             case DOWN: {
00268                 d = RIGHT;
00269             }
00270             break;
00271             case LEFT: {
00272                 d = UP;
00273             }
00274             break;
00275             case RIGHT: {
00276                 d = DOWN;
00277             }
00278             break;
00279         }
00280     }
00281     
00282     void reset(){
00283         bones.cleanup();
00284         bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, 2, 0, 4, 2)));
00285         d = UP;
00286     }
00287     
00288     Direction getDirection(){
00289         return d;
00290     }
00291     
00292 private:
00293     Dimension _getNodeX(Node<SnakeBone> * node){
00294         if (node){
00295             return node->d.getX();
00296         }
00297         return Dimension(-1, -1, -1);
00298     }
00299     
00300     Dimension _getNodeY(Node<SnakeBone> * node){
00301         if (node){
00302             return node->d.getY();
00303         }
00304         return Dimension(-1, -1, -1);
00305     }
00306 
00307     List<SnakeBone> bones;
00308     Direction d;
00309 };
00310 
00311 class Game {
00312 public:
00313     Game(MicroBit & ubit): uBit(ubit), foodx(-1), foody(-1), image(5,5), score(0),
00314         buttonPressed(false) {
00315         uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE);
00316         uBit.display.print(image);
00317     }
00318     
00319     void left(){
00320         if (!buttonPressed){
00321             buttonPressed = true;
00322             snake.left();
00323         }
00324     }
00325     
00326     void right(){
00327         if (!buttonPressed){
00328             buttonPressed = true;
00329             snake.right();
00330         }
00331     }
00332     
00333     bool move(){
00334         // Dim old head
00335         Dimension x = snake.getHeadX();
00336         Dimension y = snake.getHeadY();
00337         image.setPixelValue(x, y, SNAKE_BONE_PIXEL_BRIGHTNESS);
00338         
00339         snake.grow();
00340         
00341         Dimension nextX = snake.getHeadX();
00342         Dimension nextY = snake.getHeadY();
00343         
00344         // check if we grew by eating.
00345         if (nextX == foodx && nextY == foody){
00346             // food gone
00347             foodx = -1;
00348             foody = -1;
00349             score++;
00350             
00351             // Since we have grown to the head. no need to delete tail.
00352         } else {
00353             // Switch off end.
00354             int endx = snake.getTailX();
00355             int endy = snake.getTailY();
00356             if (endx != -1 && endy != -1){
00357                 image.setPixelValue(endx, endy, 0);
00358             }
00359             snake.reduce();
00360         }
00361         
00362         // Check snake bite
00363         if (image.getPixelValue(nextX, nextY) == 15){
00364             // Game Over
00365             return false;
00366         }   
00367 
00368         // Turn On head.
00369         image.setPixelValue(nextX, nextY, SNAKE_HEAD_PIXEL_BRIGHTNESS);
00370         uBit.display.print(image);
00371         buttonPressed = false;
00372         return true;
00373     }
00374     
00375     bool isGoodFood(int x, int y){
00376         bool ret = image.getPixelValue(x, y) == 0;
00377         
00378         // also don't accept food in the direction of movement
00379         if (ret){
00380             switch(snake.getDirection()){
00381                 case Snake::UP:
00382                 case Snake::DOWN: {
00383                     ret = x != snake.getHeadX(); 
00384                 }
00385                 break;
00386                 case Snake::LEFT:
00387                 case Snake::RIGHT: {
00388                     ret = y != snake.getHeadY();
00389                 }
00390                 break;
00391             }
00392         }
00393         return ret;
00394     }
00395     
00396     void play(){
00397         while (true) {
00398             if (!move()){
00399                 showGameOver();
00400                 break;
00401             }
00402             
00403             // Put food
00404             if (foodx == -1 && foody == -1){
00405                 while (true){
00406                     int x = uBit.random(5);
00407                     int y = uBit.random(5);
00408                     
00409                     if (isGoodFood(x, y)){
00410                         foodx = x;
00411                         foody = y;
00412                         image.setPixelValue(foodx, foody, 255);
00413                         uBit.display.print(image);
00414                         break;
00415                     }
00416                 }
00417             }
00418             uBit.sleep(500);
00419         }
00420     }
00421     
00422     void animateSnake(){
00423 #define ANIMATION_SPEED 50
00424         // UP 2
00425         foodx = 2;foody = 1;
00426         move();
00427         uBit.sleep(ANIMATION_SPEED);
00428         
00429         foodx = 2;foody = 0;
00430         move();
00431         uBit.sleep(ANIMATION_SPEED);
00432         
00433         // LEFT
00434         foodx = 1;foody = 0;
00435         left();
00436         move();
00437         uBit.sleep(ANIMATION_SPEED);
00438         
00439         // DOWN
00440         foodx = 1;foody = 1;
00441         left();
00442         move();
00443         uBit.sleep(ANIMATION_SPEED);
00444         
00445         foodx = 1;foody = 2;
00446         move();
00447         uBit.sleep(ANIMATION_SPEED);
00448         move();
00449         uBit.sleep(ANIMATION_SPEED);
00450         move();
00451         uBit.sleep(ANIMATION_SPEED);
00452         
00453         for (int i = 0; i < 4; i++){
00454             // LEFT
00455             left();
00456             move();
00457             uBit.sleep(ANIMATION_SPEED);
00458             
00459             // UP
00460             right();
00461             move();
00462             uBit.sleep(ANIMATION_SPEED);
00463             
00464             move();
00465             uBit.sleep(ANIMATION_SPEED);
00466             move();
00467             uBit.sleep(ANIMATION_SPEED);
00468             move();
00469             uBit.sleep(ANIMATION_SPEED);
00470             
00471             // LEFT
00472             left();
00473             move();
00474             uBit.sleep(ANIMATION_SPEED);
00475             
00476             // DOWN
00477             left();
00478             move();
00479             uBit.sleep(ANIMATION_SPEED);
00480             
00481             move();
00482             uBit.sleep(ANIMATION_SPEED);
00483             move();
00484             uBit.sleep(ANIMATION_SPEED);
00485             move();
00486             uBit.sleep(ANIMATION_SPEED);
00487         }
00488         
00489         // Back to the centre
00490         left();
00491         move();
00492         uBit.sleep(ANIMATION_SPEED);
00493         
00494         left();
00495         move();
00496         uBit.sleep(ANIMATION_SPEED);
00497         
00498         move();
00499         uBit.sleep(ANIMATION_SPEED);
00500         move();
00501         uBit.sleep(ANIMATION_SPEED);
00502         
00503         // eat the tail
00504         while(snake.getHead()){
00505             image.setPixelValue(snake.getTailX(), snake.getTailY(), 0);
00506             uBit.display.print(image);
00507             snake.reduce();
00508             uBit.sleep(ANIMATION_SPEED);
00509         }
00510         
00511         reset();
00512     }
00513     
00514     void showGameOver(){
00515         // switch off food
00516         if (foodx != -1 && foody != -1){
00517             image.setPixelValue(foodx, foody, 0);
00518             uBit.display.print(image);
00519         }
00520         
00521         // change brightness to 255 as pixels with custom brightness
00522         // are not affected by display.setBrightness(). Possible Bug!!!
00523         Node<SnakeBone> * bone = snake.getHead();
00524         while (bone){
00525             image.setPixelValue(bone->d.getX(), bone->d.getY(), SNAKE_FOOD_PIXEL_BRIGHTNESS);
00526             bone = bone->getNext();
00527         }
00528         // Flash snake
00529         uBit.display.print(image);
00530         bool toggle = false;
00531         for (int i = 0; i < 10; i++, toggle = !toggle){
00532             if (toggle)
00533                 uBit.display.setBrightness(255);
00534             else
00535                 uBit.display.setBrightness(SNAKE_BONE_PIXEL_BRIGHTNESS);
00536             uBit.sleep(500);
00537         }
00538         uBit.display.print(MicroBitImage(5,5));
00539         uBit.display.setBrightness(255);
00540         uBit.display.scroll("SCORE-"); // don't mind spaces in a scrolling display.
00541         uBit.display.scroll(score, 150);
00542     }
00543     
00544     void reset(){
00545         // cleanup snake and state
00546         snake.reset();
00547         
00548         foodx = -1;
00549         foody = -1;
00550         score = 0;
00551         image = MicroBitImage(5, 5);
00552         uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE);
00553         image.setPixelValue(snake.getHeadX(), snake.getHeadY(), SNAKE_HEAD_PIXEL_BRIGHTNESS);
00554         uBit.display.print(image);
00555     }
00556     
00557     
00558 private:
00559     MicroBit & uBit;
00560     int foodx;
00561     int foody;
00562     Snake snake;
00563     MicroBitImage image;
00564     int score;
00565     
00566     // Sync button press and movement. Don't let quick button presses
00567     // change snake direction twice/thrice/... before moving. As a 
00568     // double press can reverse the snake on its own and Game Over!!!
00569     bool buttonPressed; 
00570 };
00571 
00572 
00573 MicroBit uBit;
00574 Game game(uBit);
00575 
00576 
00577 void onButtonA(MicroBitEvent){
00578     game.left();
00579 }
00580 
00581 void onButtonB(MicroBitEvent){
00582     game.right();
00583 }
00584 
00585 
00586 int main()
00587 {
00588     // Initialise the micro:bit runtime.
00589     uBit.init();
00590     
00591     uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButtonA);
00592     uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonB);
00593 
00594     // Continous play    
00595     while (true){
00596         game.animateSnake();
00597         game.play();
00598         game.reset();
00599     }
00600 }