捣蛋贪吃蛇

【背景】新拿到一堆各式各样的灯带,测试了很多效果,结果就是亮瞎眼…面对一堆的像素块就想到了贪吃蛇,又觉得太一般,于是加入了player2,负责捣蛋。利用Arduino, Joy Stick以及procesing/蓝牙做成了这个双人捣蛋贪吃蛇游戏。
【视频】http://v.qq.com/page/v/k/j/v0179rh9tkj.html

【介绍】蓝色代表蛇,绿色代表食物玩家1: 按下按钮游戏开始上下左右四个方向操控蛇的方向(每次转向之后要将摇杆位置归0以便下次检测)如果没能在5秒内吃到食物 食物位置将随机改变出界或碰到自己的尾巴游戏结束 红灯亮起再次按下按钮重置游戏再按下按钮新一轮游戏开始玩家2:每隔1秒有一次移动食物一格的机会 负责捣蛋!我尝试了两种玩家2的操控方法,一种是利用BLE link蓝牙模块,用现有的走你!(GoBLE)APP来控制食物的移动,但是每次移动时灯阵里的(0,0)号灯一直会变成蓝色灯,值为1,我会在下文附上所有代码,个人认为Arduino代码并没有问题,怀疑是GoBLE的库有问题,还希望大家能一起讨论一下。另一种方法是用键盘,借助processing控制食物。

IMG_6577.PNG (257.77 KB, 下载次数: 3)

下载附件

保存到相册

2016-1-4 11:55 上传

【硬件清单】必要硬件:1 x JoyStick摇杆(SKU:DFR0061)1 x NeoPixel (未上架)1 x IO传感器扩展板 V7.1 (SKU: DFR0265) 做完这个项目深深的爱上了这个扩展版可选硬件:1 x BLE-LINK 蓝牙4.0通讯模块 (SKU: TEL0073)
【接线图】键盘控制版本

keyboard.png (364.71 KB, 下载次数: 3)

下载附件

保存到相册

2016-1-4 11:56 上传

蓝牙控制版本

bluetooth.png (376.43 KB, 下载次数: 3)

下载附件

保存到相册

2016-1-4 11:56 上传

JoyStick-X PinA0JoyStick-Y PinA1
JoyStick-Z Pin13NeoPixels Shield Pin6
【代码】键盘控制版本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 <adazhao1211@gmail.com> *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_KHZ800800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)// NEO_KHZ400400 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 varsint hrow=0,hcol=0;//sets the row and col of the snake headbool game = true;//game is goodbool start = false;//start the game with truebool 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 keypressint sx=4,sy=3;//set the initial snake locationlong previousMillis = 0;//used for the game timerlong interval = 350; //how long between each update of the gameunsigned long currentMillis = 0;//used for the game timer//used for update food location every five secondsunsigned long currentfoodtime=0;long prevfoodtime=0;long foodinterval=10000;int rx,ry;//food locationint sDr=-1,sDc=0;//used to keep last direction, start off going upint 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} };//joystickint pinX = A0;int pinY = A1;int pinZ = 13;bool doMove = true;bool startGame = true;bool detect = true;//move foodint 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 keyboardif (Serial.available()){ int val = Serial.read(); changeFood(val);}}void checkJoyStick(){//check joyStickfloat 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;}复制代码Processingimport processing.serial.;Serial myPort;// Create object from Serial classvoid setup() {size(200,200); //make our canvas 200 x 200 pixels bigString portName = Serial.list()[2]; //change the 0 to a 1 or 2 etc. to match your portprint(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); }}}复制代码蓝牙控制版本在源代码基础上添加/修改以下部分#include <Adafruit_NeoPixel.h>//the library can be found at https://github.com/adafruit/Adafruit_NeoPixel#include <Metro.h>#include "GoBLE.h"//bleint buttonState[4];long prevTime = 0;long foodTime = 3000;void setup(){Goble.begin();pinMode(pinZ, INPUT);Serial.begin(115200);}void loop(){//check GoBlecheckGoBle();}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(); } }}复制代码

【语句分析】总括:Adafruit_NeoPixel strip = Adafruit_NeoPixel(40, PIN, NEO_GRB +NEO_KHZ800);初始化led阵,建立一个类型为Ada_NeoPixel,名为strip(可更改)的对象,三个参数分别为LED的总数,与Arduino连接的pin以及像素类型。这种led可以通过一个数字控制脚控制每一个单独的led,非常方便。int gameBoard[X][Y]建立一个与灯阵一致的矩阵,不同数值代表不同的物体,0为背景,-1为食物,大0的值为蛇currentMillis = millis();读取当前时间,与上次更新游戏的时间做减法,决定蛇是静止还是向前移动
贪吃蛇部分:基本逻辑是将贪食蛇看做一个数列,每一节有一个值,尾巴尖为1,依次递增,蛇头的值最大。每次蛇移动时执行moveSnake(),检测移动后蛇头的位置——食物/背景/蛇身/出界,后两种情况执行gameover(),如果是食物,给检测的位置附上蛇头+1的值,形成新的数列;如果是背景,执行removetTail(),在现有的基础上让数列里的每个数都-1,蛇的长度保持不变。如果吃到食物或距离上次进食超过5秒,执行placeFood(),随机放置食物。
玩家1部分:checkJoySitck()实时检测摇杆位置,当摇杆处于中间位置时重置摇杆,开始下一次检测。
玩家2部分:键盘版本需要打开并运行processing文件。通过Serial,processing可以将读到的键盘输入的信息实时传送给Arduino。蓝牙版本需要包含GoBLE的库,实时检测用户是否在通过蓝牙下指令,如果下了指令并且时间间隔多余1秒,执行moveFood(),移动食物

【存在问题】用蓝牙控制时(0,0)位置的灯会亮蓝色,值为1,上下右三个方向都会使(0,0)灯亮起,向左正常工作。

FullSizeRender.jpg (124.26 KB, 下载次数: 3)

下载附件

保存到相册

2016-1-4 12:00 上传

code.rar

(46.26 KB, 下载次数: 2)

2016-1-4 14:55 上传
点击文件名下载附件

标签: Arduino教程