[应用教程]捣蛋贪吃蛇 精华

查看: 15855|回复: 26

[应用教程] 捣蛋贪吃蛇

[复制链接]
【背景】
新拿到一堆各式各样的灯带,测试了很多效果,结果就是亮瞎眼面对一堆的像素块就想到了贪吃蛇,又觉得太一般,于是加入了player2,负责捣蛋。利用Arduino, Joy Stick以及procesing/蓝牙做成了这个双人捣蛋贪吃蛇游戏。

【视频】





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


【硬件清单】
必要硬件:
1 x JoyStick摇杆(SKU:DFR0061)
1 x NeoPixel (未上架)
1 x IO传感器扩展板 V7.1 (SKU: DFR0265) 做完这个项目深深的爱上了这个扩展版
可选硬件:

【接线图】
键盘控制版本
keyboard.png

蓝牙控制版本
bluetooth.png

JoyStick-X PinA0
JoyStick-Y PinA1
JoyStick-Z Pin13
NeoPixels Shield Pin6

【代码】
键盘控制版本
Arduino
  1. /*
  2. *This code is loosely base off the project found here http://www.kosbie.net/cmu/fall-10/15-110/handouts/snake/snake.html
  3. *Created by Ada Zhao <adazhao1211@gmail.com>
  4. *12/23/2015
  5. */

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

  8. #define PIN 6//data pin for NeoPixel

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

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

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

  29. //used for update food location every five seconds
  30. unsigned long currentfoodtime=0;
  31. long prevfoodtime=0;
  32. long foodinterval=10000;
  33. int rx,ry;//food location

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

  35. int gameBoard[X][Y] = //game field, 0 is empty, -1 is food, >0 is snake
  36. {
  37.         {0,0,0,0,0,0,0,0},
  38.         {0,0,0,0,0,0,0,0},
  39.         {0,0,0,0,0,0,0,0},
  40.         {0,0,0,0,0,0,0,0},
  41.         {0,0,0,0,0,0,0,0}        
  42. };

  43. //joystick
  44. int pinX = A0;
  45. int pinY = A1;
  46. int pinZ = 13;
  47. bool doMove = true;
  48. bool startGame = true;
  49. bool detect = true;

  50. //move food
  51. int buttonState[4];
  52. long prevTime = 0;
  53. long foodTime = 1000;

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

  64. void loop(){
  65.         currentMillis = millis();//get the current time
  66.         //game clock
  67.         if(currentMillis - previousMillis >= interval) {
  68.                 previousMillis = currentMillis;
  69.                 if (game&&start&&ignoreNextTimer==false){                        
  70.                         drawBoard();
  71.                         updateGame();                        
  72.                 }
  73.                 ignoreNextTimer=false;//resets the ignore bool
  74.         }
  75.   //check joystick
  76.         checkJoyStick();
  77.   //check keyboard
  78.   if (Serial.available()){
  79.     int val = Serial.read();
  80.     changeFood(val);
  81.   }
  82.   
  83. }


  84. void checkJoyStick(){
  85.   //check joyStick
  86.   float valX = analogRead(pinX);
  87.   float valY = analogRead(pinY);
  88.   int valZ = digitalRead(pinZ);
  89.    
  90.     //four directions
  91.                 if (valX <= 100 && valY <= 616 && valY >= 416 && doMove){//move left
  92.                         if (game&&start){
  93.                                 moveSnake(0,-1);
  94.                                 ignoreNextTimer=true;
  95.                         }
  96.       doMove = false;               
  97.                 }
  98.                 else if (valX >= 923 && valY >= 416 && valY <= 616 && doMove){//move right
  99.                         if (game&&start){
  100.                                 moveSnake(0,1);
  101.                                 ignoreNextTimer=true;
  102.                         }
  103.       doMove = false;

  104.                 }
  105.                 else if (valY >= 923 && valX >= 411 && valX <= 611 && doMove){//move up
  106.                         if (game&&start){
  107.                                 moveSnake(-1,0);
  108.                                 ignoreNextTimer=true;
  109.                         }
  110.       doMove = false;

  111.                 }
  112.                 else if (valY <= 100 && valX >= 411 && valX <= 611 && doMove){//move down
  113.                         if (game&&start){
  114.                                 moveSnake(1,0);
  115.                                 ignoreNextTimer=true;
  116.                         }
  117.       doMove = false;

  118.                 }
  119.                 else if (valX <= 530 && valX >= 500 && valY <= 540 && valY >= 500 && doMove == false){
  120.     //reset joyStick
  121.                   doMove = true;
  122.                 }

  123.     //start or reset game by pressing the Z button
  124.     if (startGame && valZ == 0 && detect){
  125.       start = true;
  126.       drawBoard();
  127.       detect = false;
  128.       startGame = false;
  129.     }
  130.     if (valZ == 1 && detect == false){
  131.       detect = true;
  132.     }
  133.     if (valZ == 0 && startGame == false && detect){
  134.       resetGame();
  135.       detect = false;
  136.       startGame = true;
  137.     }
  138.    
  139. }


  140. void changeFood(int foodDir){
  141.       if(currentMillis-prevTime>=foodTime){
  142.         //player 2 can move the food every three seconds
  143.         gameBoard[rx][ry] = 0;
  144.         switch (foodDir){
  145.           case 1:
  146.             rx --;
  147.           break;
  148.           case 4:
  149.             ry ++;         
  150.           break;
  151.           case 2:
  152.             rx ++;
  153.           break;
  154.           case 3:
  155.             ry --;
  156.           break;
  157.           }
  158.           if (gameBoard[rx][ry] != 0 || rx < 0 || rx > X-1 || ry < 0 || ry > Y-1){
  159.             switch (foodDir){
  160.               case 1:
  161.                 rx ++;
  162.               break;
  163.               case 4:
  164.                 ry --;         
  165.               break;
  166.               case 2:
  167.                 rx --;
  168.               break;
  169.               case 3:
  170.                 ry ++;
  171.               break;
  172.               }
  173.           }else{
  174.          Serial.println("reset");
  175.          gameBoard[rx][ry] = -1;
  176.          val = 0;
  177.          drawBoard();
  178.          prevTime = millis();      
  179.           }
  180.       }
  181. }

  182. void updateGame(){
  183.   if (game && start){
  184.       moveSnake(sDr,sDc);
  185.       //If the snake hasn't get the food within an interval, then replace the food to another place.
  186.       currentfoodtime=millis();
  187.       if(currentfoodtime-prevfoodtime>=foodinterval){
  188.         if(gameBoard[rx][ry]==-1){
  189.           gameBoard[rx][ry]=0;
  190.         }
  191.         placeFood();
  192.         drawBoard();
  193.       }
  194.   }
  195.   if (game && start){
  196.     drawBoard();//update the screen
  197.   }
  198. }

  199. void resetGame(){
  200.         resetBoard();
  201.         sDr=-1;
  202.         sDc=0;
  203.         loadSnake();
  204.         placeFood();
  205.         findSnakeHead();//find where the snake is starting from
  206.         game=true;
  207.         start=false;
  208.         ignoreNextTimer=false;
  209.         drawBoard();
  210. }


  211. void placeFood(){

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

  214.     while(gameBoard[rx][ry]>0){
  215.       rx = random(0,X-1);
  216.       ry = random(0,Y-1);
  217.     }
  218.     gameBoard[rx][ry]=-1;
  219.     prevfoodtime=millis();
  220. }

  221. void loadSnake(){
  222.         gameBoard[sx][sy]=1;
  223. }

  224. void resetBoard(){
  225.         for(int x=0;x<X;x++){
  226.                 for(int y =0;y< Y;y++){
  227.                         gameBoard[x][y]=0;
  228.                 }
  229.                
  230.         }
  231.         loadSnake();
  232. }

  233. void gameOver(){
  234.         game = false;
  235.         start = false;

  236.         for(int light=0;light<40;light += 4){
  237.                 for(int i =0;i< strip.numPixels();i++){
  238.                         strip.setPixelColor(i,strip.Color(light,0,0));
  239.                 }
  240.                 strip.show();
  241.                 delay(25);
  242.         }
  243. for(int light=39;light >= 0;light --){
  244.     for(int i =0;i< strip.numPixels();i++){
  245.       strip.setPixelColor(i,strip.Color(light,0,0));
  246.     }
  247.     strip.show();
  248.     delay(25);
  249.   }
  250. }

  251. void moveSnake(int row, int col){//row and col
  252.         
  253.         sDr = row;
  254.         sDc = col;

  255.         int new_r=0,new_c=0;
  256.         new_r=hrow+row;
  257.         new_c=hcol+col;
  258.         if (new_r>=X||new_r<0||new_c>=Y||new_c<0){
  259.                 gameOver();
  260.         }
  261.         else if(gameBoard[new_r][new_c]>0){
  262.                 gameOver();
  263.         }
  264.         else if (gameBoard[new_r][new_c]==-1){
  265.                 gameBoard[new_r][new_c] = 1+gameBoard[hrow][hcol];
  266.                 hrow=new_r;
  267.                 hcol=new_c;
  268.                 placeFood();
  269.                 drawBoard();
  270.         }
  271.         else{
  272.                 gameBoard[new_r][new_c] = 1+gameBoard[hrow][hcol];
  273.                 hrow=new_r;
  274.                 hcol=new_c;
  275.                 removeTail();
  276.                 drawBoard();
  277.         }
  278.         
  279. }

  280. void removeTail(){
  281.         for (int x=0;x<X;x++){
  282.                 for (int y=0;y<Y;y++){
  283.                         if(gameBoard[x][y]>0){
  284.                                 gameBoard[x][y]--;
  285.                         }
  286.                 }
  287.         }
  288. }

  289. void drawBoard(){
  290.         clear_dsp();

  291.         for (int x=0;x<X;x++){
  292.                 for (int y=0;y<Y;y++){
  293.                         if(gameBoard[x][y]==-1){ //food
  294.                                 strip.setPixelColor(SetElement(x,y),strip.Color(0,20,0));
  295.                         }
  296.                         
  297.                         else if(gameBoard[x][y]==0){
  298.                                 strip.setPixelColor(SetElement(x,y),strip.Color(0,0,0));
  299.                         }
  300.                         else{
  301.                                 strip.setPixelColor(SetElement(x,y),strip.Color(0,0,10));
  302.                         }
  303.                         
  304.                 }
  305.         }
  306.         strip.show();
  307. }

  308. void findSnakeHead(){
  309.         hrow=0;//clearing out old location
  310.         hcol=0;//clearing out old location
  311.         
  312.         for (int x=0;x<X;x++){
  313.                 for (int y=0;y<Y;y++){
  314.                         if (gameBoard[x][y]>gameBoard[hrow][hcol]){
  315.                                 hrow=x;
  316.                                 hcol=y;
  317.                         }
  318.                 }
  319.         }
  320. }

  321. void clear_dsp(){
  322.         for(int i =0;i< strip.numPixels();i++){
  323.                 strip.setPixelColor(i,strip.Color(0,0,0));
  324.         }
  325.         strip.show();
  326. }

  327. uint16_t SetElement(uint16_t row, uint16_t col){
  328.         //array[width * row + col] = value;
  329.         return Y * row+col;
  330. }
复制代码
Processing
  1. import processing.serial.*;

  2. Serial myPort;  // Create object from Serial class

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

  10. void draw() {
  11. }

  12. void keyPressed() {
  13.   if (key == CODED) {
  14.     if (keyCode == UP) {
  15.       myPort.write(1);
  16.     } else if (keyCode == DOWN) {
  17.       myPort.write(2);
  18.     } else if (keyCode == LEFT) {
  19.       myPort.write(3);
  20.     } else {
  21.       myPort.write(4);
  22.     }
  23.   }
  24. }
复制代码
蓝牙控制版本
在源代码基础上添加/修改以下部分
  1. #include <Adafruit_NeoPixel.h>//the library can be found at https://github.com/adafruit/Adafruit_NeoPixel
  2. #include <Metro.h>
  3. #include "GoBLE.h"

  4. //ble
  5. int buttonState[4];
  6. long prevTime = 0;
  7. long foodTime = 3000;

  8. void setup()
  9. {
  10.   Goble.begin();
  11.   pinMode(pinZ, INPUT);
  12. Serial.begin(115200);
  13. }

  14. void loop()
  15. {
  16.   //check GoBle
  17.   checkGoBle();
  18. }

  19. void checkGoBle ()
  20. {
  21.     if(Goble.available()){
  22.     buttonState[SWITCH_UP]     = Goble.readSwitchUp();
  23.     buttonState[SWITCH_DOWN]   = Goble.readSwitchDown();
  24.     buttonState[SWITCH_LEFT]   = Goble.readSwitchLeft();
  25.     buttonState[SWITCH_RIGHT]  = Goble.readSwitchRight();
  26.    
  27.     for (int i = 1; i < 5; i++) {
  28.       if (buttonState[i] == PRESSED)   
  29.       {
  30.         int foodDir = i;
  31.         Serial.println(foodDir);
  32.         changeFood(foodDir);
  33.       }
  34.     }
  35.   }  
  36. }

  37. void changeFood(int foodDir){
  38.       if(currentMillis-prevTime>=foodTime)
  39.       {
  40.         Serial.println("moving food!!!");
  41.         gameBoard[rx][ry] = 0;
  42.         switch (foodDir){
  43.           case 1:
  44.             rx --;
  45.           break;
  46.           case 2:
  47.             ry ++;         
  48.           break;
  49.           case 3:
  50.             rx ++;
  51.           break;
  52.           case 4:
  53.             ry --;
  54.           break;
  55.           }
  56.           if (gameBoard[rx][ry] != 0){
  57.             switch (foodDir){
  58.               case 1:
  59.                 rx ++;
  60.               break;
  61.               case 2:
  62.                 ry --;         
  63.               break;
  64.               case 3:
  65.                 rx --;
  66.               break;
  67.               case 4:
  68.                 ry ++;
  69.               break;
  70.               }
  71.           }else{
  72.          Serial.println("reset");
  73.          gameBoard[rx][ry] = -1;
  74.          drawBoard();
  75.          prevTime = millis();      
  76.           }
  77.       }
  78. }
复制代码


【语句分析】
总括:
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文件。通过Serialprocessing可以将读到的键盘输入的信息实时传送给Arduino
蓝牙版本需要包含GoBLE的库,实时检测用户是否在通过蓝牙下指令,如果下了指令并且时间间隔多余1秒,执行moveFood(),移动食物


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

code.rar (46.26 KB, 下载次数: 15)
IMG_6595.JPG

dsweiliang  版主

发表于 2016-1-4 13:37:56

牛逼
回复 支持 反对

使用道具 举报

luna  管理员

发表于 2016-1-4 14:10:39

楼主是软件硬件都皆会啊~坐等更多作品
回复 支持 反对

使用道具 举报

Ash  管理员

发表于 2016-1-4 15:44:53

楼主想看有动态效果哒~
回复 支持 反对

使用道具 举报

大连林海  初级技神

发表于 2016-1-4 15:50:19

我想看更牛掰的效果
回复 支持 反对

使用道具 举报

丄帝De咗臂  版主

发表于 2016-1-4 19:47:58

40个点有些少啊
回复 支持 反对

使用道具 举报

hnyzcj  超级版主

发表于 2016-1-4 20:16:33

楼主在LED矩阵从哪里获得?
回复 支持 反对

使用道具 举报

hnyzcj  超级版主

发表于 2016-1-4 20:16:45

又见贪食蛇
回复 支持 反对

使用道具 举报

苦海  初级技匠

发表于 2016-1-4 23:38:58

求加一个程序框图
回复 支持 反对

使用道具 举报

qing1987  见习技师

发表于 2016-1-5 09:46:07

牛哄哄
回复 支持 反对

使用道具 举报

AdaZhao  初级技师
 楼主|

发表于 2016-1-5 12:27:51

Ash 发表于 2016-1-4 15:44
楼主想看有动态效果哒~

有视频的 我在文中标注一下
回复 支持 反对

使用道具 举报

AdaZhao  初级技师
 楼主|

发表于 2016-1-5 12:28:23

hnyzcj 发表于 2016-1-4 20:16
楼主在LED矩阵从哪里获得?

某宝 :D
回复 支持 反对

使用道具 举报

AdaZhao  初级技师
 楼主|

发表于 2016-1-5 12:29:08

苦海 发表于 2016-1-4 23:38
求加一个程序框图

嗯 好的 等我忙完手头的东西
回复 支持 反对

使用道具 举报

孙毅  版主

发表于 2016-1-5 12:55:11

强悍!!等视频 啊。。。
回复 支持 反对

使用道具 举报

AdaZhao  初级技师
 楼主|

发表于 2016-1-5 13:12:15

孙毅 发表于 2016-1-5 12:55
强悍!!等视频 啊。。。

已经加上了
回复 支持 反对

使用道具 举报

visionsl  高级技师

发表于 2016-1-5 19:13:00

LZ好厉害
回复 支持 反对

使用道具 举报

Jason_G  高级技师

发表于 2016-1-6 00:04:46

赞一个!
回复 支持 反对

使用道具 举报

free  高级技师

发表于 2016-1-6 10:05:43

挺好玩的,如{果有个外形就更好了
回复 支持 反对

使用道具 举报

Geemi  版主

发表于 2016-1-6 16:46:41

这个好玩耶!!!nice!!楼主
回复 支持 反对

使用道具 举报

小色叮铛  见习技师

发表于 2016-1-8 21:28:10

不错喔
回复 支持 反对

使用道具 举报

Geemi  版主

发表于 2016-1-11 10:13:27

好蛇源自贪吃蛇

点评

楼主,厉害  发表于 2016-8-9 20:35
回复 支持 反对

使用道具 举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
wifi气象站

硬件清单

btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2019 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail