捣蛋贪吃蛇

2016-01-045.9万
精华项目
AI 快速预览详细 收起
捣蛋贪吃蛇是一个基于Arduino、Joy Stick和processing/蓝牙的双人游戏。玩家1通过摇杆控制蛇的方向,玩家2每隔1秒移动食物一格负责捣蛋。项目使用了JoyStick摇杆、NeoPixel灯带、IO传感器扩展板等硬件。代码中解决了具体的报错问题,如(0,0)号灯一直变蓝的问题。这款游戏适合青少年编程启蒙和亲子共学,能够提升孩子的逻辑思维能力和动手实践能力。
【背景】
新拿到一堆各式各样的灯带,测试了很多效果,结果就是亮瞎眼面对一堆的像素块就想到了贪吃蛇,又觉得太一般,于是加入了player2,负责捣蛋。利用Arduino, Joy Stick以及procesing/蓝牙做成了这个双人捣蛋贪吃蛇游戏。

【视频】

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


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

【接线图】
键盘控制版本
捣蛋贪吃蛇图2

蓝牙控制版本
捣蛋贪吃蛇图3

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 https://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)灯亮起,向左正常工作。
捣蛋贪吃蛇图4


IMG_6595.JPG (145.24 KB, 下载次数: 12801)

捣蛋贪吃蛇 · Arduino+Joy Stick+Processing/蓝牙双人游戏_image_3.webp

创作许可协议

本项目采用 None(不开放任何权利,保留所有权利) 进行许可。

评论(23)
派大星ym
派大星ym
////
派大星ym
派大星ym
学习了学习了
20060606
20060606
goble的库确实有问题
lilei8488
lilei8488
好棒
shijinxianzhe
shijinxianzhe
楼主真厉害!
D.Rainbow
D.Rainbow
好!真棒。 膜拜一下
why
why
期待更多精彩作品
qq913622228180
qq913622228180
好蛇源自贪吃蛇
AdaZhao
AdaZhao
作者
回复qq913622228180
为什么听起来像个蛇肉馆
小色叮铛
小色叮铛
不错喔
qq913622228180
qq913622228180
这个好玩耶!!!nice!!楼主
free
free
挺好玩的,如{果有个外形就更好了
qq865650214158
qq865650214158
赞一个!
visionsl
visionsl
LZ好厉害
孙毅
孙毅
强悍!!等视频 啊。。。
AdaZhao
AdaZhao
作者
回复孙毅
已经加上了
qing1987
qing1987
牛哄哄
苦海
苦海
求加一个程序框图
AdaZhao
AdaZhao
作者
回复苦海
嗯 好的 等我忙完手头的东西
hnyzcj
hnyzcj
又见贪食蛇
hnyzcj
hnyzcj
楼主在LED矩阵从哪里获得?
AdaZhao
AdaZhao
作者
回复hnyzcj
某宝 :D
丄帝De咗臂
丄帝De咗臂
40个点有些少啊
qq815076543322
qq815076543322
我想看更牛掰的效果