Play snake using mbed! A snake-like game that runs on the memoryLCD display on Happy Gecko.

Dependencies:   mbed MemoryLCD

Hungry Gecko Game on Memory LCD

This game is meant to run on a Silicon labs EFM32 Happy Gecko Starter Kit, and demonstrate the use of the Memory LCD. User controls the push buttons on the kit PB1, and PB0 to let the gecko turn left and right respectively in order to eat the food. User should avoid running into the body of the gecko, otherwise game is over. It is allowed to hit the wall and go from the other side of it. Have fun!

Information

All examples in this repo are considered EXPERIMENTAL QUALITY, meaning this code has been created as one-off proof-of-concept and is suitable as a demonstration for experimental purposes only. This code will not be regularly maintained by Silicon Labs and there is no guarantee that these projects will work across all environments, SDK versions and hardware.

Libraries

gecko.h

This class creates a gecko object, draws it on the memory LCD according to its movement, and keeps track of the length of the gecko

gecko.h

#include "LS013B7DH03.h"
#include "settings.h"
#include "asymmetricPart.h"
 
class Gecko{
  private:
      uint8_t _position[MAXLENGTH];
      uint8_t _last;
      uint8_t _length;
 
      asymmetricPart _head;
 
      void drawPart(silabs::LS013B7DH03 &display, uint8_t x, uint8_t y) const;
      void removePart(silabs::LS013B7DH03 &display, uint8_t x, uint8_t y) const;
    public:
        Gecko();
 
        /* Moves the snake and redraws it on the display */
        void move(silabs::LS013B7DH03 &display, Direction dir);
 
        /* Redraw the entire snake */
        void draw(silabs::LS013B7DH03 &display) const;
 
        /* Check if the snake has collides with itself */
        bool selfCollision() const;
 
        /* Increases the length of the snake by one STEPSIZE x STEPSIZE tile */
        void increaseLength(silabs::LS013B7DH03 &display, Direction dir);
 
        /* Checks if the head of the snake occupies a STEP */
        bool headOccupiesTile(uint8_t x, uint8_t y) const;
 
        /* Chech if the snake occupies a STEPSIZE x STEPSIZE tile */
        bool occupiesTile(uint8_t x, uint8_t y) const;
 
        /* Get coordinates */
        uint8_t getX(uint8_t indx) const;
        uint8_t getY(uint8_t indx) const;
};

food.h

This class creates a food object, displays it on memory LCD and updates its location every time it is eaten by gecko.

food.h

#include "settings.h"
#include "LS013B7DH03.h"
 
class Gecko; // Forward declaration
#ifndef FOOD_H_
#define FOOD_H_
/* Pixel map for the food
 * 4 most significant bits y-crd, 4 least significant bits x-crd
 * The upper left corner of the part is asigned coordinates (x,y)=(0,0)
 */
class Food{
public:
    Food();
    bool isEaten(Gecko &gck);
    void reset(silabs::LS013B7DH03 &display, const Gecko &gck);
    void draw(silabs::LS013B7DH03 &display);
 
private:
    uint8_t x;
    uint8_t y;
    void remove(silabs::LS013B7DH03 &display);
};
 
#endif /* FOOD_H_ */

settings.h

This file defines the settings including the wall location, board height and width, and enum for gecko movements.

settings.h

#ifndef SETTINGS_H_
#define SETTINGS_H_
#include "LCDSettings.h"
 
#define MAXLENGTH 255
#define STEPSIZE 7
 
/*  Define board limits, note that the display height and display width is 128
 * This particlular choice leads to 255 STEPSIZE x STEPSIZE tiles. Thus,
 * the tile number can be stored in 1 uint8_t
 * */
#define TOPEDGE 2
#define BOARD_HEIGHT 15
#define BOARD_WIDTH 17
#define BOARDERWIDTH (DISPLAY_WIDTH - STEPSIZE*BOARD_WIDTH)
 
/* Define allowed direction to move */
typedef enum{
    LEFT=0, RIGHT, UP, DOWN
} Direction;
 
#endif /* SETTINGS_H_ */

asymmetricPart.h

This class creates the object of the asymmetric part of the gecko body, and updates its location on the memory LCD.

asymmetricPart.h

#include "settings.h"
#include "LS013B7DH03.h"
 
/* Bitfield containing the position of the top left corner of the part and the orientation
 * The minimum number of bits to store x, y and orientation is 10 bits (there are 255 tiles).
 * Since this will be expanded to 16 bits the x and y values are stored in 7 bits each.
 * */
 
struct positionAndDirection{
    uint8_t x:7;
    uint8_t y:7;
    uint8_t direction:2;
};
 
class asymmetricPart{
private:
    positionAndDirection _posAndDir;
    const uint8_t *_px_map;
    uint8_t _nPix;
 
    /* Private member functions */
    void draw(silabs::LS013B7DH03 &display, uint8_t color) const;
public:
    asymmetricPart();
    asymmetricPart(uint8_t x, uint8_t y, Direction dir,  const uint8_t *px_map, uint8_t nPix);
 
    /* Set all member variables */
    void init(uint8_t x, uint8_t y, Direction dir,  const uint8_t *px_map, uint8_t nPix);
 
    /* Draw the part on the screen */
    void draw(silabs::LS013B7DH03 &display) const {draw(display, Black);};
 
    /* Erase the part from the screen */
    void remove(silabs::LS013B7DH03 &display) const {draw(display, White);};
 
    /* Get member variables */
    uint8_t getX() const {return _posAndDir.x;};
    uint8_t getY() const {return _posAndDir.y;};
    uint8_t getDir() const {return _posAndDir.direction;};
 
    /* Set member variables */
    void setX(uint8_t x) {_posAndDir.x = x&0x7F;};
    void setY(uint8_t y) {_posAndDir.y = y&0x7F;};
    void setDir(Direction dir) {_posAndDir.direction = static_cast<int>(dir)&0x0003;};
};

Full Example

mian.cpp

#include "LS013B7DH03.h"
#include "gecko.h"
#include "food.h"
#include "settings.h"
 
/**************************** Define I/O **************************************/
 
InterruptIn in(SW1);
InterruptIn inB1(SW0);
#define SCK     PE12
#define MOSI    PE10
 
DigitalOut CS(PA10);
DigitalOut EXTCOM(PF3);
DigitalOut DISP(PA8);
 
SPI displaySPI(MOSI, NC, SCK);
silabs::LS013B7DH03 display(&displaySPI, &CS, &EXTCOM);
 
/**************************** Define Timers ***********************************/
 
LowPowerTicker ticker;
 
/**************************** Global variables ********************************/
 
/* Flag that is set to true when the display is refreshed */
volatile bool refreshed = false;
 
/* Flag that is set to true by the ticker. Makes the gecko move at regular time intervals */
volatile bool updateDisplay = true;
 
/* A flag that ensures the controller to only read one click per frame */
volatile bool PBenabled = true;
 
/* Direction in which the gecko moves */
Direction dir = UP;
 
uint8_t score = 0;
 
/**************************** Define callback handlers ************************/
void tickerCallback(void);
 
/* Push button handlers */
void in_handler_B0();
void in_handler_B1();
 
/* Define game modes */
typedef enum {
    PLAY, STOP
} Modes;
 
/* Set the game mode */
Modes mode = PLAY;
 
void in_handler_B0() {
    /* Only change the direction if push button is enabled */
    if (PBenabled)
    {
        switch (dir) {
        case (UP):
                dir = LEFT;
            break;
        case (DOWN):
                dir = RIGHT;
            break;
        case (RIGHT):
                dir = UP;
            break;
        case (LEFT):
                dir = DOWN;
            break;
        }
        PBenabled = false;
    }
}
 
void in_handler_B1() {
    /* Only change the direction if push button is enabled */
    if (PBenabled)
    {
        switch (dir) {
        case UP:
            dir = RIGHT;
            break;
        case DOWN:
            dir = LEFT;
            break;
        case RIGHT:
            dir = DOWN;
            break;
        case LEFT:
            dir = UP;
            break;
        }
        PBenabled = false;
    }
}
 
 
/* Callback functions */
void tickerCallback(void) {
    updateDisplay = true;
 
    /* Enable push buttons if the display is refreshed */
    PBenabled = refreshed;
}
 
 
void refreshCallback(void) {
    refreshed = true;
}
 
/**************************** Fill the boarder ********************************/
 
void fillBoarder(silabs::LS013B7DH03 &display){
    display.fill(0, 0, DISPLAY_WIDTH, TOPEDGE*STEPSIZE, Black);
 
    /* Fill right edge */
    display.fill(BOARD_WIDTH*STEPSIZE + BOARDERWIDTH/2, TOPEDGE*STEPSIZE + BOARDERWIDTH/2, 1, BOARD_HEIGHT*STEPSIZE, Black);
    for (uint8_t i=0;i<BOARD_HEIGHT;i++){
        for (uint8_t j=0;j<(DISPLAY_WIDTH-BOARD_WIDTH*STEPSIZE - BOARDERWIDTH/2);j++){
            display.pixel(BOARD_WIDTH*STEPSIZE + BOARDERWIDTH/2 +j, (i+TOPEDGE)*STEPSIZE + BOARDERWIDTH/2+j, Black);
        }
    }
 
    /* Fill bottom edge */
    display.fill(BOARDERWIDTH/2, (BOARD_HEIGHT+TOPEDGE)*STEPSIZE + BOARDERWIDTH/2, BOARD_WIDTH*STEPSIZE, 1, Black);
 
    for (uint8_t i=0;i<=BOARD_WIDTH;i++){
        for (uint8_t j=0;j<(DISPLAY_WIDTH-BOARD_WIDTH*STEPSIZE - BOARDERWIDTH/2);j++){
            display.pixel(i*STEPSIZE + BOARDERWIDTH/2 +j, (BOARD_HEIGHT+TOPEDGE)*STEPSIZE + BOARDERWIDTH/2+j, Black);
        }
    }
 
    /* Fill left edge */
    display.fill(BOARDERWIDTH/2-1, TOPEDGE*STEPSIZE + BOARDERWIDTH/2, 1, BOARD_HEIGHT*STEPSIZE, Black);
    for (uint8_t i=0;i<BOARD_HEIGHT;i++){
        for (uint8_t j=0;j<(DISPLAY_WIDTH-BOARD_WIDTH*STEPSIZE - BOARDERWIDTH/2 - 1);j++){
            display.pixel(j, (i+TOPEDGE)*STEPSIZE + BOARDERWIDTH/2+j, Black);
        }
    }
 
    /* Fill top edge */
    display.fill(BOARDERWIDTH/2, TOPEDGE*STEPSIZE + BOARDERWIDTH/2 - 1, BOARD_WIDTH*STEPSIZE, 1, Black);
 
    for (uint8_t i=0;i<=BOARD_WIDTH;i++){
        for (uint8_t j=0;j<(DISPLAY_WIDTH-BOARD_WIDTH*STEPSIZE - BOARDERWIDTH/2 - 1);j++){
            display.pixel(i*STEPSIZE + BOARDERWIDTH/2 +j, TOPEDGE*STEPSIZE + j, Black);
        }
    }
 
}
 
/**************************** MAIN ********************************************/
int main() {
 
    /* Initialize pushbutton handlers */
    in.fall(in_handler_B0);
    inB1.fall(in_handler_B1);
 
    /* Enable the LCD */
    DISP = 1;
 
    /* Start generating the 3Hz call */
    ticker.attach(&tickerCallback, 0.3333f);
 
    /* Reset the LCD to a blank state. (All white) */
    refreshed = false;
    if (display.clearImmediate(refreshCallback) == LS013B7DH03_OK){
        while (refreshed == false) sleep();
    }
 
    fillBoarder(display);
    refreshed = false;
    if (display.update(refreshCallback) == LS013B7DH03_OK)
    {
        while (refreshed == false) sleep();
    }
    Gecko gck;
    Food fd;
    gck.draw(display);
    fd.draw(display);
 
    /* Push update to the display */
    refreshed = false;
    if (display.update(refreshCallback) == LS013B7DH03_OK)
    {
        while (refreshed == false) sleep();
    }
    display.foreground(White);
    display.background(Black);
    display.locate(4,0);
    display.printf("Score: ");
 
    display.locate(11,0);
    display.printf("%d", score);
 
    /* Main loop */
    while (1) {
        sleep();
        if (updateDisplay && refreshed && (mode==PLAY)) {
            updateDisplay = false;
 
            gck.move(display, dir);
 
            if (fd.isEaten(gck))
            {
                fd.reset(display, gck);
                gck.increaseLength(display, dir);
 
                /* Redraw gecko */
                gck.draw(display);
                /* Update the score */
                score++;
                display.locate(11,0);
                display.printf("%d", score);
            }
 
            /* Update display */
            refreshed = false;
            display.update(refreshCallback);
 
 
            if (gck.selfCollision()) {
                mode = STOP;
                gck.move(display, dir);
                display.locate(3, 6);
                display.printf("GAME OVER!");
                refreshed = false;
                display.update(refreshCallback);
            }
        }
    }
}