Игра Тетрис на светодиодной матрице

В продолжение топика Индикаторное табло на светодиодных матрицах.

Создадим на этой матрице игру Тетрис. Управление с помощью уже существующей аналоговой клавиатуры.

Переход в игру и обратно по кнопке red.
Создаем простенько
Назначение кнопок:
green левая, правая — фигура влево/вправо
green вверх — поворот фигуры но 90 градусов
white — увеличение скорости на 25 пунктов
red — выход из игры


void loop() {
  int valbutton;
  // go through the six channels of the digital pot:
  switch(tekgame)
   {
   case 0: game1();
      break;
   case 1: if(!game2())
          {clearDisplay();}
      break;
   case 2: game1();
        //game3();
      break;
   default: break;
   }
   // опрос клавиатуры
   valbutton=analogRead(keyboardPin);
   if(valbutton<1000)
     {buttonClick2(buttonClick1(valbutton));}

}
// обработка нажатия кнопки
int buttonClick1(int val)
 {
  if(val>650) {KEYS1.button=1;return 1;} 
  if(val>600) {KEYS1.button=2;return 2;} 
  if(val>530) {KEYS1.button=3;return 3;} 
  if(val>450) {KEYS1.button=4;return 4;} 
  if(val>300) {KEYS1.button=5;return 5;} 
  if(val>200) {KEYS1.button=6;return 6;}
  return 0;  
  }
   void buttonClick2(int val)
 {
   if(millis()-KEYS1.millisbutton[val]<100)
     return;
   KEYS1.millisbutton[val]=millis();
   KEYS1.button=val;
   switch(val) {
    case 1: OFFSET_DISPLAY1.deltaX=1;OFFSET_DISPLAY1.deltaY=0;
        FIGURA1.dX=FIGURA1.dX-1;                                        // 
        if(PITON1.button!=4)
           PITON1.button=1;
        Serial.println("left");
        break; 
    case 2: 
        OFFSET_DISPLAY1.deltaX=0;OFFSET_DISPLAY1.deltaY=-1;  // game1
        FIGURA1.dT=1;                                        // game2 (поворот 90)
        if(PITON1.button!=3)
           PITON1.button=2;
        Serial.println("up");
       break; 
    case 3: OFFSET_DISPLAY1.deltaX=0;OFFSET_DISPLAY1.deltaY=1;
        Serial.println("down");
        if(PITON1.button!=2)
           PITON1.button=3;
       break; 
    case 4: OFFSET_DISPLAY1.deltaX=-1;OFFSET_DISPLAY1.deltaY=0;
        FIGURA1.dX=FIGURA1.dX+1;                                        // 
        if(PITON1.button!=1)
           PITON1.button=4;
       Serial.println("right");
        break; 
    case 5: OFFSET_DISPLAY1.deltaX=0;OFFSET_DISPLAY1.deltaY=0;
        // переключение на другую программу
        tekpoz1=(tekpoz1+1)%(sizeof(startpozdisplay1)-1);
        tekindex1=startpozdisplay1[tekpoz1];
        speed2=max(speed2-25,5);
        Serial.println("white");
        Serial.println(speed2);
       break; 
    case 6: 
        OFFSET_DISPLAY1.deltaX=0;OFFSET_DISPLAY1.deltaY=0;
        OFFSET_DISPLAY1.offsetX=0;OFFSET_DISPLAY1.offsetY=0;
        clearDisplay();
        tekgame=(tekgame+1)%3;
        Serial.println("red");
        break; 
    default: 
        break; 
   }   
 }



Для реализации игры создадим массив со всеми изображениями фигур для Тетриса arrFigure[][], массив указателей на новую фигуру при повороте, transFigure[], а также структуру FIGURA для сохранения информации о текущей фигуре(положении на экране, )


byte arrFigure[16][5]={
     {0,0,0,0,0},{15,0,0,0,4},{1,1,1,1,1},{3,3,0,0,2}, 
     {7,1,0,0,3},{7,4,0,0,3},{1,7,0,0,3},{4,7,0,0,3}, 
     {3,1,1,0,2},{3,2,2,0,2},{1,1,3,0,2},{2,2,3,0,2},
     {7,2,0,0,3},{2,3,2,0,2},{2,7,0,0,3},{1,3,1,0,2}};
byte transFigure[16]={0,2,1,3,9,8,10,11,5,7,4,6,13,14,15,12};
struct FIGURA    // структура для хранения фигуры на экране
{
  int driving;   // 1 – в движении
  int offsetX;   // смещение по x
  int offsetY;   // смещение по y
  int dX;      // изменение влево-вправо 
  int dY;      // dy
  int dT;      // 1 - поворот 90 градусов
  int c;  // номер фигуры
  int length;  // ширина фигуры
  long millis1;
};
FIGURA FIGURA1={0,3,20,0,0,0,0,0,0};



arrFigure[][5] — первые четыре байта —
построчно для четырех(max высота фигуры) строк значение строки буфера экрана displayArray[24]
для пустой строки, занятой только фигурой
пятый байт — ширина фигуры (при движении влево-вправо проще вычислениями следить за границей экрана, при повороте это делать сложнее,
поэтому введен этот параметр)
Новая фигура помещается в верхнюю невидимую часть буфера экрана (байты 20-23)

Добавление фигуры на поле выполняет процедура add_figura(). Фигура выбирается с помощью функции random(). В структуру FIGURA заносятся следующие начальные значения:
— offsetY – номер фигуры, выдаваемый функцией random();
— offsetX=3
— int offsetY=20 (верх буфера экрана);
— с — номер фигуры;
— length — ширина;
— FIGURA1.driving=1 – фигура движется.
и вывести изображение фигуры в буфер эграна. Данные при этом заносятся в верхнюю невидимую часть буфера экрана (байты 20-23 массива displayArray).


 void add_figura()
  {
  //FIGURA1.figura=random(1,sizeof(transFigure));
  FIGURA1.figura=millis()%sizeof(transFigure);
  FIGURA1.dX=0;FIGURA1.dY=0;FIGURA1.dT=0;
  FIGURA1.offsetX=3;FIGURA1.offsetY=20;
  FIGURA1.millis1=millis();
  FIGURA1.length=arrFigure[FIGURA1.figura][4];
  for(int i=0;i<4;i++)
    {displayArray[FIGURA1.offsetY+i]=arrFigure[FIGURA1.figura][i]<<FIGURA1.offsetX;}
  FIGURA1.driving=1;  
  }


Рассмотрим как осуществляется движение фигуры вниз.
Через время установленное в переменной speed2 (меняется нажатием на кнопку white) происходит стирание из буфера экрана фигуры (процедура figura_clear()), вычисление нового положения фигуры, прорисовка (занесение в буфер экрана) фигуры в измененных коорднатах (процедура figura_driving()).


// очищение изображения фигуры
void  figura_clear(int y,int x)
  {
   for(int i=0;i<4;i++)
    displayArray[y+i]=displayArray[y+i]-(arrFigure[FIGURA1.figura][i]<<x);
   }
// перемещение фигуры
void  figura_driving(int y,int x,int figura)
  {
   for(int i=0;i<4;i++)
    displayArray[y+i]=displayArray[y+i]+(arrFigure[figura][i]<<FIGURA1.offsetX);
  }


Перед изменением положения фигуры (вниз, влево, вправо, поворот) необходимо производить проверку на столкновение с другими (уже неподвижными фигурами) и выход из границ. Для движения влево, вправо, поворот изменение положения производится до движения вниз. Для движения вниз при положительной проверке на столкновение происходит останов фигуры (FIGURA1.driwing=0). Процедуры проверки на столкновение collision1() и collision2()


// контроль столкновения с кирпичиками поворот,в сторону
 boolean collision1(int yd,int figura,int figurapr,int dx,int ddx)
  {
  byte opand,disp;
  boolean collision=false;
  for(int i=0;i<4;i++) // по высоте фигуры - 4
    {
    disp=displayArray[yd+i];
    disp=disp-(arrFigure[figurapr][i]<<dx);  
    opand=disp & (arrFigure[figura][i]<<ddx); 
    if(opand>0)
      {collision=true;}
    } 
   return collision; 
  }
  // контроль столкновения с кирпичиками вниз
 boolean collision2(int yd,int figura,int figurapr,int dx,int ddx)
  {
  byte opand,disp;
  boolean collision=false;
  for(int i=0;i<4;i++) // по высоте фигуры - 4
    {
    disp=displayArray[yd+i];
    if(i>0)
      disp=disp-(Figure[figurapr][i-1]<<dx);  
    opand=disp & (arrFigure[figura][i]<<dx); 
    if(opand>0)
      {collision=true;}
    } 
   return collision; 
  } 



И код главной поцедуры — game2()


 // игра тетрис
 boolean game2()
  {
   if(FIGURA1.driving==0)
     add_figura(); 
   if((millis()-FIGURA1.millis1)>speed2 && FIGURA1.driving>0)
     {
     //figura_clear(FIGURA1.offsetY);  
     // по X
     if(FIGURA1.dX!=0)
        {
        int dxpr=FIGURA1.dX;
        int offxpr=FIGURA1.offsetX+dxpr;
        offxpr=max(offxpr,0);
        offxpr=min(offxpr,8-FIGURA1.length);       
        if(!collision1(FIGURA1.offsetY,FIGURA1.figura,FIGURA1.figura,FIGURA1.offsetX,offxpr))
         {
        figura_clear(FIGURA1.offsetY,FIGURA1.offsetX);
        FIGURA1.offsetX=FIGURA1.offsetX+FIGURA1.dX;
        FIGURA1.offsetX=max(FIGURA1.offsetX,0);
        FIGURA1.offsetX=min(FIGURA1.offsetX,8-FIGURA1.length);       
        FIGURA1.dX=0;
        Serial.println(FIGURA1.offsetX);    
        figura_driving(FIGURA1.offsetY,0,FIGURA1.figura);
         }
        
        //figura_clear(FIGURA1.offsetY);
       }
     // поворот 90 градусов
     if(FIGURA1.dT>0)
        {
        if(!collision1(FIGURA1.offsetY,transFigure[FIGURA1.figura],FIGURA1.figura,FIGURA1.offsetX,FIGURA1.offsetX))
         {
         figura_clear(FIGURA1.offsetY,FIGURA1.offsetX);
         figura_driving(FIGURA1.offsetY,0,transFigure[FIGURA1.figura]);
         FIGURA1.figura=transFigure[FIGURA1.figura];
         FIGURA1.length=arrFigure[FIGURA1.figura][4];
         }
        FIGURA1.dT=0;
        //figura_clear(FIGURA1.offsetY);
       }
     // вниз по времени
     if(collision2(FIGURA1.offsetY-1,FIGURA1.figura,FIGURA1.figura,FIGURA1.offsetX,FIGURA1.offsetX))
       {FIGURA1.millis1=millis();FIGURA1.driving=0;
     if(FIGURA1.offsetY==20)
       return false;
     else
       return true;
     }
     figura_clear(FIGURA1.offsetY,FIGURA1.offsetX);  
     figura_driving(FIGURA1.offsetY-1,0,FIGURA1.figura);
     FIGURA1.offsetY=FIGURA1.offsetY-1;
     if(FIGURA1.offsetY<5)
       FIGURA1.driving=0;
  
     
        
    FIGURA1.dX=0;FIGURA1.dY=0;FIGURA1.dT=0;
    FIGURA1.millis1=millis();
      }
   return true;  
  }


Все — игра готова — смотрим видео



И скачать скетч здесь

Комментарии (1)

RSS свернуть / развернуть
+
0
Забавно вышло.

Вообще-то название тетрис от «тетра», что значит четыре. Т.е. в игре используются все варианты фигур из четырех элементов. Там где больше — это уже китайтрис:) Так, для фактов
avatar

Ozze

  • 27 мая 2012, 11:02

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.