مشروعات تطبيقية · الدرس 4.2

لعبة الثعبان بالتحكم عبر الميل

العودة إلى الدروس

فكرة اللعبة

سنستخدم MAX7219 و MPU6050 لصنع لعبة الثعبان بالتحكم عبر الجاذبية؛ ميل اللوحة يمينًا ويسارًا يغير اتجاهه نحو الطعام.

المكونات المطلوبة

يتحرك الثعبان باستمرار. تناول الثمرة يزيد طوله وسرعته. تنتهي اللعبة إذا لمس جسمه.

الثعبان والثمرة الوامضة على وحدة مصفوفة الصمامات الباعثة للضوء (MAX7219 Display Module)
الثعبان والثمرة الوامضة على وحدة مصفوفة الصمامات الباعثة للضوء (MAX7219 Display Module)

التوصيل

مخطط توصيل MPU6050 والمصفوفة
مخطط توصيل MPU6050 والمصفوفة

ركّب لوحة التوسعة فوق UNO.

توصيل وحدتي GY521 و MAX7219
توصيل وحدتي GY521 و MAX7219

عرض الثعبان

تأكد من توفر مكتبتي LedControl و Wire في بيئة Arduino IDE قبل تشغيل المثال. لخطوات إضافة مكتبة غير مثبتة، راجع قسم «ما هي المكتبات؟» وطرق تثبيتها في الدرس 1.5 «وميض LED وإضافة المكتبات».

افتح ملف البرنامج Greedy_snake19.8.14.ino من ملفات الدرس في بيئة Arduino IDE. بعد إتمام التوصيلات، وصّل لوحة أردوينو (UNO R3 Board) بالحاسوب بكابل توصيل (USB Cable)، ثم اضغط زر Upload (رفع) لنقل البرنامج إلى اللوحة وتشغيل اللعبة عليها.

الجزء الأول: حركة الثعبان

تعرف مصفوفتا snakeX[64] و snakeY[64] موضع كل جزء على مصفوفة 8×8. العنصر 0 للرأس، والعنصر 1 للجزء الثاني، وهكذا. يظهر المثال بدايةً عند snakeX[0]=0 و snakeY[0]=4، وطول 2.

تمر drawSnake() على الأجزاء حتى snakeLength وتستدعي lc.setLed(0, snakeX[i], snakeY[i], true). معاملات setLed هي addr عنوان الشاشة، و row و col من 0 إلى 7، و state لتشغيل LED إذا true وإطفائه إذا false.

محورا X و Y للمصفوفة
محورا X و Y للمصفوفة

الحركة وتحديد الاتجاه

تنقل nextstep() مواضع الأجزاء إلى الخلف بحلقة for: إذا كان الطول 3، يأخذ الجزء الثالث موضع الثاني، والثاني موضع الرأس؛ ثم تحدد switch اتجاه الرأس الجديد.

UP ينقص snakeY، و RIGHT يزيد snakeX، و DOWN يزيد snakeY، و LEFT ينقص snakeX. عند -1 يلتف إلى 7، وعند 8 يعود إلى 0، فيعبر حدود المصفوفة إلى الطرف الآخر.

تقيس checkGy521() التسارع عبر Wire من العنوان MPU_addr ابتداءً من السجل 0x3B، وتطلب 14 سجلًا. تجمع البايتين لقراءة AcX و AcY، وتطبعها. يفحص المثال التحديث بعد 20ms ويحتوي delay(33). مقارنة المحاور بحدود ±1000 و ±8000 تغير direction مع منع عكس اتجاه الحركة مباشرة.

الثمرة والنقاط والسرعة

الجزء الثاني: ثمرة عشوائية

ترسم الثمرة مثل جزء الثعبان، لكن اختبار الزمن يقلب fruitLed دوريًا لتومض وتتميز عن الجسم. تستخدم drawFruit() قيم fruitPrevTime و fruitBlinkTime و millis().

تختار makeFruit() إحداثيين بـ random(0, 8). تعيد الاختيار داخل while إذا كانت النقطة جزءًا من الثعبان، ثم تحفظ fruitX و fruitY.

الجزء الثالث: تناول الثمرة

إذا تطابق الرأس مع الثمرة يزيد snakeLength و score. إذا لم يبلغ MAX_SNAKE_LENGTH تنشأ ثمرة جديدة، وإلا توضع الإحداثيات عند -1. كلما أصبحت score من مضاعفات 8 يقل delayTime بمقدار 50 فتزداد السرعة. int هو نوع الأعداد الصحيحة المستخدم لتخزين الأرقام.

اصطدام الثعبان بنفسه

الجزء الرابع

تفحص snakeItSelf() الرأس مقابل كل جزء من 1 إلى snakeLength-1. عند تطابق X و Y تستدعي gameOver() وتبدأ اللعبة مجددًا.

تمسح gameOver() الشاشة ثم تضيء جميع الخلايا بالتتابع مع delay(50)، وتنتظر 300ms. تعيد score و snakeLength إلى 3، والاتجاه RIGHT، والرأس إلى (3,4)، و delayTime إلى 500، ثم تستدعي loop().

الكود

Greedy_snake19.8.14/Greedy_snake19.8.14.ino

#include "LedControl.h"
#include<Wire.h>
const int MPU_addr=0x68;  // I2C address of the MPU-6050
// connection to MAX7219 (data, clik, load, #of dispplay)
LedControl lc = LedControl(13,7,8,1);
int16_t AcX,AcY;
// direction
int xVal = 0;
int yVal = 0;
const int UP    = 0;
const int RIGHT  = 1;
const int DOWN = 2;
const int LEFT   = 3;
// Snake length
const int MAX_SNAKE_LENGTH = 64;

// Variables
//Adafruit_8x8matrix matrix = Adafruit_8x8matrix();// Display
int direction = RIGHT;                             // direction of movement
int snakeX[MAX_SNAKE_LENGTH];                      // X-coordinates of snake
int snakeY[MAX_SNAKE_LENGTH];                      // Y-coordinates of snake
int snakeLength = 2;   
int score = 3;
unsigned long prevTime = 0;                        // for gamedelay (ms)
unsigned long delayTime = 500;                     // Game step in ms

int fruitX, fruitY;
unsigned long fruitPrevTime = 0;
unsigned long fruitBlinkTime = 100;

int fruitLed = true;



void setup(){
  ////wake up the MAX72XX from power-saving mode
  lc.shutdown(0,false);
  //set a medium brightness for the Leds
  lc.setIntensity(0, 1);
  //Switch all Leds on the display off.
  lc.clearDisplay(0);
  Serial.begin(9600);
  
  randomSeed(analogRead(A5));
  Wire.begin();
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

  
  
  snakeX[0] = 0;
  snakeY[0] = 4;
  for(int i=1; i<MAX_SNAKE_LENGTH; i++){
    snakeX[i] = snakeY[i] = -1;
  }
  makeFruit();
  
}
  
void loop(){
    delay(10);
    checkGy521();  // if any button is pressed or not
    unsigned long currentTime = millis();
    if(currentTime - prevTime >= delayTime){
    nextstep(); 
    prevTime = currentTime;
    int m=analogRead(A0);
    int n=analogRead(A1);
  }
  draw(); // make snack & food
  snakeItSelf(); // check if snack bite itself
}

void checkGy521(){
  static unsigned long delaytime = millis();
  if(millis() - delaytime > 20){
    delaytime = millis();
    Wire.beginTransmission(MPU_addr);
    Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
    Wire.endTransmission(false);
    Wire.requestFrom(MPU_addr,14,true);  // request a total of 14 registers
    AcX=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)    
    AcY=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
    delay(33);
    Serial.print(AcX);
    Serial.print('\t');  
    Serial.println(AcY); 
    if(AcX>-1600&&AcX<1600&&AcY>8000&&direction!= DOWN){
    direction=UP;
    Serial.println("DOWN");
    }
    else if(AcX>-1600&&AcX<1600&&AcY<-8000&& direction!= UP){
    direction=DOWN;
    Serial.println("UP");
    }
    else if(AcX<-8000&& AcY>-1600&&AcY<1600&& direction!= LEFT){
    direction=RIGHT;
    Serial.println("LEFT");
    }
    else if(AcX>8000&&AcY>-1600&&AcY<1600&& direction!= RIGHT){
    direction=LEFT;
    Serial.println("RIGHT");
    }
  }
}

void draw(){
  lc.clearDisplay(0);
  drawSnake();
  drawFruit();
}
void drawSnake(){
  for(int i=0; i<snakeLength; i++){
/* Set the status of a single Led.
 * Params :
 *   addr  address of the display
 *   row   the row of the Led (0..7)
 *   col   the column of the Led (0..7)
 *   state If true the led is switched on, if false it is switched off 

setLed(int addr, int row, int col, boolean state); 
*/ 
    lc.setLed(0,snakeX[i], snakeY[i], true);
  }
}
void drawFruit(){
  if(inPlayField(fruitX, fruitY)){
    unsigned long currenttime = millis();
    if(currenttime - fruitPrevTime >= fruitBlinkTime){
      /*
       *Fruit keeps flashing
      */
      fruitLed = (fruitLed == true) ? false : true;
      fruitPrevTime = currenttime;
    }
    lc.setLed(0,fruitX, fruitY, fruitLed);
  }
}
/*
 * Fruit appears range
*/
boolean inPlayField(int x, int y){
  return (x>=0) && (x<8) && (y>=0) && (y<8);
}


void nextstep(){
    for(int i=snakeLength-1; i>0; i--){
    snakeX[i] = snakeX[i-1];
    snakeY[i] = snakeY[i-1]; 
  }
    
  switch(direction){
    case UP:
      snakeY[0] = snakeY[0]-1; 
      if(snakeY[0]==-1)
      snakeY[0]=7;
      break;
    case RIGHT:
      snakeX[0] = snakeX[0]+1;
      if(snakeX[0]==8)
      snakeX[0]=0;
      break;
    case DOWN:
      snakeY[0] = snakeY[0]+1;
      if(snakeY[0]==8)
      snakeY[0]=0;
      break;
    case LEFT:
      snakeX[0]=snakeX[0]-1;
      if(snakeX[0]==-1)
      snakeX[0]=7;
      break;
   }

  if((snakeX[0] == fruitX) && (snakeY[0] == fruitY)){
    snakeLength++;
    score++;
    if(snakeLength < MAX_SNAKE_LENGTH){      
      makeFruit();
    } 
    else {
      fruitX = fruitY = -1;
    }
    if(score%8==0)
      {
        delayTime = delayTime - 50;
     }   
  }
   
}
/*
 * Fruit appears outside the snake body
*/
void makeFruit(){
  int x, y;
  x = random(0, 8);
  y = random(0, 8);
  while(isPartOfSnake(x, y)){
    x = random(0, 8);
    y = random(0, 8);
  }
  fruitX = x;
  fruitY = y;
}
/*
 * Determine where the fruit appears
*/
boolean isPartOfSnake(int x, int y){
  for(int i=0; i<snakeLength-1; i++){
    if((x == snakeX[i]) && (y == snakeY[i])){
      return true;
    }
  }
  return false;
}
/*
 * Snake head hits the snake body
*/
void snakeItSelf(){  // check if snake touches itself
    for(int i=1;i<snakeLength;i++){
      if((snakeX[0] == snakeX[i]) && (snakeY[0] == snakeY[i])){
       gameOver();
      }
     }
}
void gameOver(){
  lc.clearDisplay(0);
  for(int r = 0; r < 8; r++){
        for(int c = 0; c < 8; c++){
            lc.setLed(0, r, c, HIGH);
            delay(50);
        }delay(50);
    }
  delay(300);
  score = 3;  
  snakeLength = 3;
  direction = RIGHT;
  snakeX[0]=3;
  snakeY[0]=4;
  delayTime = 500;
  loop();
}

ملفات الدرس والمكتبات

المكتبات المرفقة

ملفات الدرس

100%

عرض الصورة

استخدم + و− للتكبير والتصغير، أو اضغط Esc للإغلاق.