Snake

Dependencies:   microbit

Fork of microbit_snake by Mohammad Khan

A Snake Game with animation, score and continuous play

Steps:

  • Switch On.
  • Snake animation plays.
  • Game starts.
  • Press buttons to move the snake left, right, up and down.
  • Food appears as a dot on the display.
  • Try eating the food by manoeuvring Snake to run over it.
  • Snake grows as it eats food.
  • As it grows it becomes hard to avoid snake biting itself.
  • Game Over.
  • Snake is flashed and then score is scrolled.
  • Game restarts after replaying animation.

Controls:

  • Left Button (A)
    • If snake moving UP/DOWN moves snake to LEFT.
    • If snake moving LEFT moves snake to DOWN.
    • If snake moving RIGHT moves snake to UP.
  • Right Button (B)
    • If snake moving UP/DOWN moves snake to RIGHT.
    • If snake moving LEFT moves snake to UP.
    • If snake moving RIGHT moves snake to DOWN.

How it looks:

https://www.youtube.com/watch?v=unEs3NOvIKA

Binary to play with

/media/uploads/mazimkhan/microbit_snake_nrf51_microbit.hex

Revision:
0:af1bb8b895c7
Child:
2:38060465e019
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Sun Apr 10 19:50:16 2016 +0100
@@ -0,0 +1,502 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2016 British Broadcasting Corporation.
+This software is provided by Lancaster University by arrangement with the BBC.
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+#include "MicroBit.h"
+#include "DigitalOut.h"
+
+#define SNAKE_HEAD_PIXEL_BRIGHTNESS     25
+#define SNAKE_REST_PIXEL_BRIGHTNESS     15
+#define SNAKE_FOOD_PIXEL_BRIGHTNESS     255
+
+template <class T> class Node{
+public:
+    Node(T data): d(data), next(NULL), prev(NULL) {}
+    
+    Node * getNext(){
+        return next;
+    }
+    Node * getPrev(){
+        return prev;
+    }
+    void detach () {
+        if (prev){
+            prev->next = next;
+        }
+        if (next){
+            next->prev = prev;
+        }
+        
+        next = NULL;
+        prev = NULL;
+    } 
+    void append(Node * node) {
+        Node * n = this;
+        while (n->getNext()){
+            n = n->getNext();
+        }
+        n->next = node;
+        if (node){
+            node->prev = n;
+        }
+    }
+public:
+    T d;
+private:
+    Node * next;
+    Node * prev;
+};
+
+template <class T> class List {
+public:
+    List(): head(NULL), tail(NULL){
+    }
+    void prepend(Node<T> * node){
+        if (node){
+            if (head == NULL && tail == NULL) {
+                head = tail = node;
+            } else {
+                node->append(head);
+                head = node;
+            }
+        }
+    }
+    Node<T> * removeTail(){
+        Node<T> * node = tail;
+        if (tail) {
+            tail = tail->getPrev();
+            node->detach();
+        }
+        return node;
+    }   
+    Node<T> * getHead(){
+        return head;
+    }
+    Node<T> * getTail(){
+        return tail;
+    } 
+    void cleanup(){
+        Node<T> * node = tail;
+        while(node){
+            Node<T> * temp = node;
+            node = node->getPrev();
+            
+            temp->detach();
+            delete temp;
+        }
+        head = tail = NULL;
+    }
+private:
+    Node<T> *   head;
+    Node<T> *   tail;
+};
+
+class Dimension
+{
+public:
+    Dimension (int start, int end) {
+        this->start = start;
+        this->end = end;
+        this->cur = start;
+    }
+    
+    Dimension (int start, int end, int cur) {
+        this->start = start;
+        this->end = end;
+        this->cur = cur;
+    }
+    
+    void operator ++(int){
+        cur++;
+        if (cur > end){
+            cur = start;
+        }
+    }
+    
+    void operator --(int){
+        cur--;
+        if (cur < start){
+            cur = end;
+        }
+    }
+    
+    int operator -(int i){
+        int r = cur - i;
+        if (r < start){
+            r = end;
+        }
+        return r;
+    }
+    
+    operator int(){
+        return cur;
+    }
+private:
+    int start;
+    int end;
+    int cur;
+};
+
+
+class SnakeBone {
+public:
+    SnakeBone(int startx, int endx, int curx, int starty, int endy, int cury):
+        x(startx, endx, curx), y(starty, endy, cury)
+        {
+        }
+        
+    Dimension getX(){
+        return x;
+    }
+    
+    Dimension getY(){
+        return y;
+    }
+
+private:
+    Dimension x;
+    Dimension y;
+};
+
+
+class Snake {
+public:
+    enum Direction {
+        UP,
+        DOWN,
+        LEFT,
+        RIGHT
+    };
+
+    Snake (): d(UP){
+        bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, 2, 0, 4, 2)));
+    }
+    
+    Node<SnakeBone> * getHead(){
+        return bones.getHead();
+    }
+    Dimension getHeadX(){
+        return _getNodeX(bones.getHead());
+    }
+    Dimension getHeadY(){
+        return _getNodeY(bones.getHead());
+    }
+    Dimension getTailX(){
+        return _getNodeX(bones.getTail());
+    }
+    Dimension getTailY(){
+        return _getNodeY(bones.getTail());
+    }
+    
+    void grow(){
+        // Find next position to place new head
+        Dimension nextX = getHeadX();
+        Dimension nextY = getHeadY();
+        
+        switch (d){
+            case UP: {
+                nextY--;
+            }
+            break;
+            case DOWN: {
+                nextY++;
+            }
+            break;
+            case LEFT: {
+                nextX--;
+            }
+            break;
+            case RIGHT: {
+                nextX++;
+            }
+            break;
+        }
+        
+        // New bone at head 
+        bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, nextX, 0, 4, nextY)));            
+    }
+    
+    void reduce(){
+        Node<SnakeBone> * tail = bones.removeTail();
+        printf ("\r\nNew tail x = %d y = %d\r\n", bones.getTail()->d.getX(), bones.getTail()->d.getY());
+        if (tail){
+            delete tail;
+        }
+    }
+    
+    void left(){
+        switch (d){
+            case UP:
+            case DOWN: {
+                d = LEFT;
+            }
+            break;
+            case LEFT:  {
+                d = DOWN;
+            }
+            break;
+            case RIGHT: {
+                d = UP;
+            }
+            break;
+        }
+    }
+    
+    void right(){
+        switch (d){
+            case UP:
+            case DOWN: {
+                d = RIGHT;
+            }
+            break;
+            case LEFT: {
+                d = UP;
+            }
+            break;
+            case RIGHT: {
+                d = DOWN;
+            }
+            break;
+        }
+    }
+    
+    void reset(){
+        bones.cleanup();
+        bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, 2, 0, 4, 2)));
+        d = UP;
+    }
+    
+    Direction getDirection(){
+        return d;
+    }
+    
+private:
+    Dimension _getNodeX(Node<SnakeBone> * node){
+        if (node){
+            return node->d.getX();
+        }
+        return Dimension(-1, -1, -1);
+    }
+    
+    Dimension _getNodeY(Node<SnakeBone> * node){
+        if (node){
+            return node->d.getY();
+        }
+        return Dimension(-1, -1, -1);
+    }
+
+    List<SnakeBone> bones;
+    Direction d;
+};
+
+class Game {
+public:
+    Game(MicroBit & ubit): uBit(ubit), foodx(-1), foody(-1), image(5,5),
+        buttonPressed(false) {
+        uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE);
+        uBit.display.print(image);
+    }
+    
+    void left(){
+        if (!buttonPressed){
+            buttonPressed = true;
+            snake.left();
+        }
+    }
+    
+    void right(){
+        if (!buttonPressed){
+            buttonPressed = true;
+            snake.right();
+        }
+    }
+    
+    bool move(){
+        // Display food
+        if (foodx != -1 && foody != -1){
+            image.setPixelValue(foodx, foody, 255);
+            uBit.display.print(image);
+        }
+        
+        snake.grow();
+        
+        Dimension nextX = snake.getHeadX();
+        Dimension nextY = snake.getHeadY();
+        printf ("\r\nnextx = %d nexty = %d\r\n", (int)nextX, (int)nextY);
+        
+        // Check snake bite
+        if (image.getPixelValue(nextX, nextY) == 15){
+            // Game Over
+            return false;
+        }   
+
+        // check if we grew by eating.
+        if (nextX == foodx && nextY == foody){
+            // food gone
+            foodx = -1;
+            foody = -1;
+            
+            // Since we have grown to the head. no need to delete tail.
+        } else {
+            // Switch off end.
+            int endx = snake.getTailX();
+            int endy = snake.getTailY();
+            printf ("\r\nendx = %d endy = %d\r\n", (int)endx, (int)endy);
+            if (endx != -1 && endy != -1){
+                image.setPixelValue(endx, endy, 0);
+            }
+            snake.reduce();
+        }
+        
+        // Turn On head.
+        image.setPixelValue(nextX, nextY, 15);
+        uBit.display.print(image);
+        buttonPressed = false;
+        return true;
+    }
+    
+    bool isGoodFood(int x, int y){
+        bool ret = image.getPixelValue(x, y) == 0;
+        
+        // also don't accept food in the direction of movement
+        if (ret){
+            switch(snake.getDirection()){
+                case Snake::UP:
+                case Snake::DOWN: {
+                    ret = x != snake.getHeadX(); 
+                }
+                break;
+                case Snake::LEFT:
+                case Snake::RIGHT: {
+                    ret = y != snake.getHeadY();
+                }
+                break;
+            }
+        }
+        return ret;
+    }
+    
+    void play(){
+        while (true) {
+            if (!move()){
+                showGameOver();
+                break;
+            }
+            
+            // Put food
+            if (foodx == -1 && foody == -1){
+                while (true){
+                    int x = uBit.random(4);
+                    int y = uBit.random(4);
+                    
+                    if (isGoodFood(x, y)){
+                        foodx = x;
+                        foody = y;
+                        break;
+                    }
+                }
+            }
+            uBit.sleep(500);
+        }
+    }
+    void showGameOver(){
+        // switch off food
+        if (foodx != -1 && foody != -1){
+            image.setPixelValue(foodx, foody, 0);
+            uBit.display.print(image);
+        }
+        
+        // change brightness to 255 as pixels with custom brightness
+        // are not affected by display.setBrightness(). Possible Bug!!!
+        Node<SnakeBone> * bone = snake.getHead();
+        while (bone){
+            image.setPixelValue(bone->d.getX(), bone->d.getY(), 255);
+            bone = bone->getNext();
+        }
+        // Flash snake
+        uBit.display.print(image);
+        bool toggle = false;
+        for (int i = 0; i < 10; i++, toggle = !toggle){
+            if (toggle)
+                uBit.display.setBrightness(255);
+            else
+                uBit.display.setBrightness(15);
+            uBit.sleep(500);
+        }
+    }
+    
+    void reset(){
+        // cleanup snake and state
+        snake.reset();
+        
+        foodx = -1;
+        foody = -1;
+        image = MicroBitImage(5, 5);
+        uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE);
+        uBit.display.print(image);
+    }
+    
+    
+private:
+    MicroBit & uBit;
+    int foodx;
+    int foody;
+    Snake snake;
+    MicroBitImage image;
+    
+    // Sync button press and movement. Don't let quick button presses
+    // change snake direction twice/thrice/... before moving. As a 
+    // double press can reverse the snake on its own and Game Over!!!
+    bool buttonPressed; 
+};
+
+
+MicroBit uBit;
+Game game(uBit);
+
+
+void onButtonA(MicroBitEvent){
+    game.left();
+}
+
+void onButtonB(MicroBitEvent){
+    game.right();
+}
+
+int main()
+{
+    // Initialise the micro:bit runtime.
+    uBit.init();
+    
+    uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButtonA);
+    uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonB);
+
+    // Continous play    
+    while (true)
+    {
+        game.play();
+        
+        uBit.display.print(MicroBitImage(5,5));
+        uBit.display.setBrightness(255);
+        uBit.display.scroll("URBITTEN"); // don't mind spaces in a scrolling display.
+        
+        game.reset();
+    }
+}
\ No newline at end of file