$USD
  • EUR€
  • £GBP
  • $USD
TUTORIALS BluetoothArduino

Trick or Snake? Classic game, new way to play!

DFRobot Jan 07 2016 770

Background
Faced with numerous led pixels, we thought of the classical game- Snake. Simply adapt this original game into a led shield, however, it is not creative enough. Thus, we made the Snake a two-player game with the help of Arduino, joy stick and keyboard/Bluetooth. The player 2 is responsible for making troubles. Trick or snake? Hope you will like it.

Introduction
   
Blue color represents the snake; green color represents the food.

Player1(JoyStick controller):
Press the button in Z-axis to start the game
The four directions of the JoyStick stands for the four direction of the snake. (release the stick after controlling to initial position in order to reset it for next control)
If you cannot eat the food within 5 second, the food will be put in another place randomly
If the snake goes out of the boundary, game is over and red light will fill the whole shield.
Press the button in Z-axis again to reset the game.
Press the button again to re-start the game.

Player2(keyboard/Bluetooth controller):
You can change the food in U/L/R/D, four directions for one unit every one second. Please in charge of making troubles!
I tried two ways of control for player 2. The first way is using BLE link Bluetooth module and the GoBLE app created by DFRobot which you can download from iphone app store or google play. However, every time I move the food, the LED at (0,0) will be lit up with blue color with the value 1. I will attach the code later. In my opinion, the problem is from GoBLE library because the keyboard version works well. The second way is using keyboard and sending data to Arduino with the help of processing.
   

Hardware list

Necessary: 
1 x JoyStick V2 (SKU:DFR0061)
1 x NeoPixel (not in the list yet)
1 x I/O Expansion Shield v7.1 (SKU: DFR0265)

Optional:
1 x BLE link (SKU: TEL0073)

Diagram Connection
Keyboard version
   
Bluetooth version
   
JoyStick-X PinA0
JoyStick-Y PinA1
JoyStick-Z Pin13
NeoPixels Shield Pin6

?Code?

Keyboard version

Arduino
/*
 *This code is loosely base off the project found here http://www.kosbie.net/cmu/fall-10/15-110/handouts/snake/snake.html
 *Created by Ada Zhao <[email protected]>
 *12/23/2015
 */

#include <Adafruit_NeoPixel.h>//the library can be found at https://github.com/adafruit/Adafruit_NeoPixel
#include <Metro.h>

#define PIN 6//data pin for NeoPixel

// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(40, PIN, NEO_GRB + NEO_KHZ800);

#define X 5//this is the depth of the field
#define Y 8//this is the length of the field

//global vars
int hrow=0,hcol=0;//sets the row and col of the snake head
bool game = true;//game is good
bool start = false;//start the game with true
bool ignoreNextTimer=false;//tells the game timer wether or not it should run
//When true the game timer will not update due to the update by a keypress
int sx=4,sy=3;//set the initial snake location
long previousMillis = 0;//used for the game timer
long interval = 350; //how long between each update of the game
unsigned long currentMillis = 0;//used for the game timer

//used for update food location every five seconds
unsigned long currentfoodtime=0;
long prevfoodtime=0;
long foodinterval=10000;
int rx,ry;//food location

int sDr=-1,sDc=0;//used to keep last direction, start off going up

int gameBoard[X][Y] = //game field, 0 is empty, -1 is food, >0 is snake
{
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0}    
};

//joystick
int pinX = A0;
int pinY = A1;
int pinZ = 13;
bool doMove = true;
bool startGame = true;
bool detect = true;

//move food
int buttonState[4];
long prevTime = 0;
long foodTime = 1000;

void setup(){
  pinMode(pinZ, INPUT);
    Serial.begin(9600);
 //snake head
    hrow=sx;
    hcol=sy;
    strip.begin();//start the NeoPixel Sheild
    strip.show(); // Initialize all pixels to 'off'
    resetGame();//clear and set the game
}

void loop(){
    currentMillis = millis();//get the current time
    //game clock
    if(currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        if (game&&start&&ignoreNextTimer==false){            
            drawBoard();
            updateGame();            
        }
        ignoreNextTimer=false;//resets the ignore bool
    }
  //check joystick
    checkJoyStick();
  //check keyboard
  if (Serial.available()){
    int val = Serial.read();
    changeFood(val);
  }
  
}


void checkJoyStick(){
  //check joyStick
  float valX = analogRead(pinX);
  float valY = analogRead(pinY);
  int valZ = digitalRead(pinZ);
    
    //four directions
        if (valX <= 100 && valY <= 616 && valY >= 416 && doMove){//move left
            if (game&&start){
                moveSnake(0,-1);
                ignoreNextTimer=true;
            }
      doMove = false;        
        }
        else if (valX >= 923 && valY >= 416 && valY <= 616 && doMove){//move right
            if (game&&start){
                moveSnake(0,1);
                ignoreNextTimer=true;
            }
      doMove = false;

        }
        else if (valY >= 923 && valX >= 411 && valX <= 611 && doMove){//move up
            if (game&&start){
                moveSnake(-1,0);
                ignoreNextTimer=true;
            }
      doMove = false;

        }
        else if (valY <= 100 && valX >= 411 && valX <= 611 && doMove){//move down
            if (game&&start){
                moveSnake(1,0);
                ignoreNextTimer=true;
            }
      doMove = false;

        }
        else if (valX <= 530 && valX >= 500 && valY <= 540 && valY >= 500 && doMove == false){
    //reset joyStick
          doMove = true;
        }

    //start or reset game by pressing the Z button
    if (startGame && valZ == 0 && detect){
      start = true;
      drawBoard();
      detect = false;
      startGame = false;
    }
    if (valZ == 1 && detect == false){
      detect = true;
    }
    if (valZ == 0 && startGame == false && detect){
      resetGame();
      detect = false;
      startGame = true;
    }
   
}


void changeFood(int foodDir){
      if(currentMillis-prevTime>=foodTime){
        //player 2 can move the food every three seconds
        gameBoard[rx][ry] = 0;
        switch (foodDir){
          case 1:
            rx --;
          break;
          case 4:
            ry ++;          
          break;
          case 2:
            rx ++;
          break;
          case 3:
            ry --;
          break;
          }
          if (gameBoard[rx][ry] != 0 || rx < 0 || rx > X-1 || ry < 0 || ry > Y-1){
            switch (foodDir){
              case 1:
                rx ++;
              break;
              case 4:
                ry --;          
              break;
              case 2:
                rx --;
              break;
              case 3:
                ry ++;
              break;
              }
          }else{
         Serial.println("reset"); 
         gameBoard[rx][ry] = -1; 
         val = 0;
         drawBoard();
         prevTime = millis();       
          }
      }
}

void updateGame(){
  if (game && start){
      moveSnake(sDr,sDc);
      //If the snake hasn't get the food within an interval, then replace the food to another place.
      currentfoodtime=millis();
      if(currentfoodtime-prevfoodtime>=foodinterval){
        if(gameBoard[rx][ry]==-1){
          gameBoard[rx][ry]=0;
        }
        placeFood();
        drawBoard();
      }
  }
  if (game && start){
    drawBoard();//update the screen
  }
}

void resetGame(){
    resetBoard();
    sDr=-1;
    sDc=0;
    loadSnake();
    placeFood();
    findSnakeHead();//find where the snake is starting from
    game=true;
    start=false;
    ignoreNextTimer=false;
    drawBoard();
}


void placeFood(){

  rx = random(0,X-1);
  ry = random(0,Y-1);

    while(gameBoard[rx][ry]>0){
      rx = random(0,X-1);
      ry = random(0,Y-1);
    }
    gameBoard[rx][ry]=-1;
    prevfoodtime=millis();
}

void loadSnake(){
    gameBoard[sx][sy]=1;
}

void resetBoard(){
    for(int x=0;x<X;x++){
        for(int y =0;y< Y;y++){
            gameBoard[x][y]=0;
        }
        
    }
    loadSnake();
}

void gameOver(){
    game = false;
    start = false;

    for(int light=0;light<40;light += 4){
        for(int i =0;i< strip.numPixels();i++){
            strip.setPixelColor(i,strip.Color(light,0,0));
        }
        strip.show();
        delay(25);
    }
 for(int light=39;light >= 0;light --){
    for(int i =0;i< strip.numPixels();i++){
      strip.setPixelColor(i,strip.Color(light,0,0));
    }
    strip.show();
    delay(25);
  }
}

void moveSnake(int row, int col){//row and col
    
    sDr = row;
    sDc = col;

    int new_r=0,new_c=0;
    new_r=hrow+row;
    new_c=hcol+col;
    if (new_r>=X||new_r<0||new_c>=Y||new_c<0){
        gameOver();
    }
    else if(gameBoard[new_r][new_c]>0){
        gameOver();
    }
    else if (gameBoard[new_r][new_c]==-1){
        gameBoard[new_r][new_c] = 1+gameBoard[hrow][hcol];
        hrow=new_r;
        hcol=new_c;
        placeFood();
        drawBoard();
    }
    else{
        gameBoard[new_r][new_c] = 1+gameBoard[hrow][hcol];
        hrow=new_r;
        hcol=new_c;
        removeTail();
        drawBoard();
    }
    
}

void removeTail(){
    for (int x=0;x<X;x++){
        for (int y=0;y<Y;y++){
            if(gameBoard[x][y]>0){
                gameBoard[x][y]--;
            }
        }
    }
}

void drawBoard(){
    clear_dsp();

    for (int x=0;x<X;x++){
        for (int y=0;y<Y;y++){
            if(gameBoard[x][y]==-1){ //food
                strip.setPixelColor(SetElement(x,y),strip.Color(0,20,0));
            }
            
            else if(gameBoard[x][y]==0){
                strip.setPixelColor(SetElement(x,y),strip.Color(0,0,0));
            }
            else{
                strip.setPixelColor(SetElement(x,y),strip.Color(0,0,10));
            }
            
        }
    }
    strip.show();
}

void findSnakeHead(){
    hrow=0;//clearing out old location
    hcol=0;//clearing out old location
    
    for (int x=0;x<X;x++){
        for (int y=0;y<Y;y++){
            if (gameBoard[x][y]>gameBoard[hrow][hcol]){
                hrow=x;
                hcol=y;
            }
        }
    }
}

void clear_dsp(){
    for(int i =0;i< strip.numPixels();i++){
        strip.setPixelColor(i,strip.Color(0,0,0));
    }
    strip.show();
}

uint16_t SetElement(uint16_t row, uint16_t col){
    //array[width * row + col] = value;
    return Y * row+col;
}

Processing
import processing.serial.*;

Serial myPort;  // Create object from Serial class

void setup() 
{
  size(200,200); //make our canvas 200 x 200 pixels big
  String portName = Serial.list()[2]; //change the 0 to a 1 or 2 etc. to match your port
  print(portName);
  myPort = new Serial(this, portName, 9600);
}

void draw() {
}

void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      myPort.write(1);
    } else if (keyCode == DOWN) {
      myPort.write(2);
    } else if (keyCode == LEFT) {
      myPort.write(3);
    } else {
      myPort.write(4);
    }
  }
}

Bluetooth version

Add/modify the following code
#include <Adafruit_NeoPixel.h>//the library can be found at https://github.com/adafruit/Adafruit_NeoPixel
#include <Metro.h>
#include "GoBLE.h"

//ble
int buttonState[4];
long prevTime = 0;
long foodTime = 3000;

void setup()
{
  Goble.begin();
  pinMode(pinZ, INPUT);
Serial.begin(115200);
}

void loop()
{
  //check GoBle
  checkGoBle();
}

void checkGoBle ()
{
    if(Goble.available()){
    buttonState[SWITCH_UP]     = Goble.readSwitchUp();
    buttonState[SWITCH_DOWN]   = Goble.readSwitchDown();
    buttonState[SWITCH_LEFT]   = Goble.readSwitchLeft();
    buttonState[SWITCH_RIGHT]  = Goble.readSwitchRight();
    
    for (int i = 1; i < 5; i++) {
      if (buttonState[i] == PRESSED)   
      {
        int foodDir = i;
        Serial.println(foodDir);
        changeFood(foodDir);
      }
    }
  }  
}

void changeFood(int foodDir){
      if(currentMillis-prevTime>=foodTime)
      {
        Serial.println("moving food!!!");
        gameBoard[rx][ry] = 0;
        switch (foodDir){
          case 1:
            rx --;
          break;
          case 2:
            ry ++;          
          break;
          case 3:
            rx ++;
          break;
          case 4:
            ry --;
          break;
          }
          if (gameBoard[rx][ry] != 0){
            switch (foodDir){
              case 1:
                rx ++;
              break;
              case 2:
                ry --;          
              break;
              case 3:
                rx --;
              break;
              case 4:
                ry ++;
              break;
              }
          }else{
         Serial.println("reset"); 
         gameBoard[rx][ry] = -1; 
         drawBoard();
         prevTime = millis();       
          }
      }
}

Code Analysis
In general:
Adafruit_NeoPixel strip = Adafruit_NeoPixel(40, PIN, NEO_GRB + NEO_KHZ800);
Used to initialize the led matrix by creating an object named strip (can be changes) under the class Adafruit_NeoPixel. Declare how many LEDs we have, which pin it is attached to as well.
int gameBoard[X][Y]
create a matrix that matches the led matrix. Different values represent different objects: 0 is for background; -1 is for food; positive integers are for the snake.
currentMillis = millis();
get the current time. Current time minus the last movement time. If the interval is greater than what we want, we move the snake and update game. 

Snake part:
The basic logic is to regard the snake as an array. Every LED of the snake has a value: the tail is 1. The closer it is to the head, the greater value it will has. The snake head has the greatest value in the whole matrix. Every time we update the game, we run the moveSnake() function. It tests the new position of snake head, whether it is food or background or snake body or out of boundary. If it is one of the last two conditions, it runs the gameover() function. If the new position is the food, we give that position the value of snakehead+1 to create a new array. If the position is background, we run removeTail() function to maintain the length of the snake. If the snake eats the food or it has been 5 seconds since last “meal”, run the placeFood() function to place the food in somewhere else randomly.

Player1 part: 
Run the checkJoyStick() function to check its positon.

Player2 part:
Run the processing file if you want to try the keyboard version.
Include the GoBLE library and run the checkGoBLE() function if you want to try the Bluetooth version.

Existing Problem
When controlled with BLE, the light at (0,0) position will be lit up with blue color, with the value 1. Only the left direction works well.

REVIEW