Как сделать тетрис классический

Публикация 27.02.2017

Дисплей от Nokia 5110 - это наверное самый дешевый дисплей который можно найти для своих проектов на Arduino. На базе него мы разберемся с вопросом: "Как же сделать тетрис-игру?@

Основная проблема всех библиотек для рисования на этом дисплее - отсутствие поддержки русского языка. Решается установкой дополнительных шрифтов, которые подменяют английские символы на русские.

Как сделать тетрис классический

Избалованные i2c шиной скажут "фу", ведь у нас 4 провода управления SCLK, DIN, DC, CE и RTS которые соответственно подключаются к Arduino, 3, 4, 5, 6, 7. Не забываем подключить питание 3.3 вольта, и землю.

Для рисования будем использовать библиотеку Adafruit GFX Library. В архиве с ней есть примеры использования.
Для управления игрой используем ИК пульт от телевизора. Сигнал будет принимать датчик TL1838.

Как сделать тетрис классический

Я использовал Arduino Uno, т.к. она уже имеет стабилизатор на 3.3 вольт.
Подключение IR приемника:
pin 8 — IR (управляющий). Питание на +5V и GND.
Подключение пьезодинамика:
pin 9 — speaker, Земля на GND.

Скетч тетриса:

// © Klop 2017
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <IRremote.h>
#include <EEPROM2.h>
#define rk 4 // ширина квадратика
#define rz 5 // ширина места
#define smeX 1
#define smeY 1
#define MaxX 10 // стакан кол-во мест по гориз
#define speaker 9

#define RECV_PIN 8 // нога на IRDA приемник

// pin 3 - Serial clock out (SCLK)
// pin 4 - Serial data out (DIN)
// pin 5 - Data/Command select (D/C)
// pin 6 - LCD chip select (CS)
// pin 7 - LCD reset (RST)
Adafruit_PCD8544 display = Adafruit_PCD8544(3, 4, 5, 6, 7);
IRrecv irrecv(RECV_PIN);
decode_results results;
byte mstacan[MaxX][30];
byte Lst,SmeH, center, NumNext;
byte MaxY; // стакан кол-во мест по вертик
int dxx, dyy, FigX, FigY, Victory, myspeed,tempspeed;
unsigned long  ok, levo, pravo, vniz, myrecord;
unsigned long flfirst=1234; // метка первого запуска

byte fig[][4][4]=
   {
    {{0,0,0,0},
     {0,0,0,0},
     {0,0,0,0},
     {0,0,0,0}},

    {{0,1,0,0},
     {0,1,0,0},
     {0,1,0,0},
     {0,1,0,0}},

    {{0,0,0,0},
     {0,1,1,0},
     {0,1,1,0},
     {0,0,0,0}},

    {{0,1,0,0},
     {0,1,1,0},
     {0,0,1,0},
     {0,0,0,0}},

    {{0,1,0,0},
     {0,1,0,0},
     {0,1,1,0},
     {0,0,0,0}},

    {{0,1,0,0},
     {0,1,1,0},
     {0,1,0,0},
     {0,0,0,0}},

    {{0,0,1,0},
     {0,1,1,0},
     {0,1,0,0},
     {0,0,0,0}},

    {{0,0,1,0},
     {0,0,1,0},
     {0,1,1,0},
     {0,0,0,0}},

    {{0,0,0,0}, //8
     {0,0,0,0},
     {0,0,0,0},
     {0,0,0,0}}
   }; 
//==============================================
void mybeep() // звук
{analogWrite(speaker, 100);
 delay(100);
 analogWrite(speaker, 0);
}
//==============================================
void figmove(byte a, byte b)
{ 
  for (byte i=0;i<4;i++)
    for (byte j=0;j<4;j++)
      fig[a][i][j]=fig[b][i][j];
}     
//==============================================
void figinit(byte a)
{ 
  for (byte i=0;i<4;i++)
    for (byte j=0;j<4;j++)
     {
     fig[0][i][j]=fig[NumNext][i][j];

     if (fig[a][j][i]==1) // покажем след фигуру
        display.fillRect(i*rz+60, 20+(j)*rz, rk , rk, BLACK); 
        else  display.fillRect(i*rz+60, 20+(j)*rz, rk , rk, WHITE);
     }
  display.display();
  NumNext=a; 
  tempspeed=myspeed;
  dxx=0;
   for (byte i=0;i<MaxX;i++)
       if (mstacan[i][3]==2) newgame();

}
//==============================================
void viewstacan()
{
  display.drawLine(0,0,0,display.height()-1, BLACK);
  display.drawLine(0,display.height()-1,Lst, display.height()-1, BLACK);
  display.drawLine(Lst,display.height()-1,Lst,0, BLACK);

  for (byte j=4;j<MaxY;j++)
   for (byte i=0;i<MaxX;i++)
     if (mstacan[i][j]>0) 
        display.fillRect(i*rz+1, SmeH+(j-4)*rz, rk , rk, BLACK); 
         else  display.fillRect(i*rz+1, SmeH+(j-4)*rz, rk , rk, WHITE);
         
  ds(Victory,1);                                  
  display.display();
}
//================================================
void ds(int aa, int b)
{
     display.fillRect(55, 10, 29, 10, WHITE);
     display.setCursor(55,b*10);
     display.println(aa);
}
//================================================
bool iffig(int dx, int dy)
{int i,j;
 bool flag=true; bool pov=false;
 
   for (i=0;i<MaxX;i++)
     for (j=0;j<MaxY;j++)
          if (mstacan[i][j]==1)
          mstacan[i][j]=0;  // убрали временно фигуру

    if (dx==2) //  поворот
      { dx=0; pov=true;
        figmove(8,0); 
        for (i=0;i<4;i++)
          for (j=0;j<4;j++)
          { fig[0][i][j]=fig[8][3-j][i];
            if (fig[0][j][i]==1)
             { if (i+FigX+dx<0)  dx=1;     // пробуем отодвинуть от стенки слева на 1
               if (i+FigX+dx>MaxX-1) dx=-1;// пробуем отодвинуть от стенки справа на 1
             }
          }
      }
      
 for (i=0;i<4;i++)
  for (j=0;j<4;j++)
      if (fig[0][j][i]==1)
        if (i+FigX+dx<0  || i+FigX+dx>MaxX-1 || FigY+j+dy>MaxY-1 || mstacan[i+FigX+dx][FigY+j+dy]>0) 
           {flag=false; break;} // проверили на новые координаты
     
 if (flag)
  {FigX=FigX+dx; FigY=FigY+dy;byte k=0;
   for (i=0;i<4;i++) 
     for (j=0;j<4;j++)
      if (fig[0][j][i]==1)
          {mstacan[i+FigX][j+FigY]=1;
           dxx=0;
          }
  } // переместили фигуру на новые координаты
   else
  { if (pov)  {figmove(0,8);}
    for (i=0;i<4;i++)    // восстановили фигуру
     for (j=0;j<4;j++)
       if (fig[0][j][i]==1)
       mstacan[i+FigX][j+FigY]=1;
  }
 return(flag);     
}
//================================================
void clearstacan()
{
 for (byte i=0;i<MaxX;i++)
  for (byte j=0;j<MaxY;j++)
    mstacan[i][j]=0;
}
//================================================
unsigned long getbutton(char s[])
{unsigned long tt;
  display.fillRect(5, 30, 40, 10, WHITE);
  display.setCursor(5,30);
  display.print(s);
  display.display();
  while (true)
   if (irrecv.decode(&results;)) // ловим код кнопки пульта 
     { tt=results.value;
       delay(400); 
       irrecv.resume();
       break;
     }
  return(tt);   
}
//================================================
void newgame()
{ unsigned long tb;
  dxx=0;  dyy=1;
  display.setCursor(0,0);
  clearstacan();  
  NumNext=random(7)+1;
  figinit(random(7)+1);
  center=MaxX/2-2;
  FigX=center;FigY=0;
  display.setCursor(52,0);
  display.println("Линий");
  viewstacan();
  if (Victory>myrecord) 
     { myrecord=Victory; 
       EEPROM_write(16, myrecord);
     }
  display.setCursor(5,0);
  display.print("Рекорд");
  display.setCursor(5,10);
  display.print(myrecord);
  display.display();
  display.setCursor(5,20);
  delay(2000);irrecv.resume();
  display.println("Нажмите");
  tb=getbutton("  OK");
  if (tb!=ok)
    { ok=tb;
      levo=getbutton("Влево");
      pravo=getbutton("Вправо");
      vniz=getbutton("Вниз");
      EEPROM_write(0, ok);
      EEPROM_write(4, levo);
      EEPROM_write(8, pravo);
      EEPROM_write(12, vniz);
    }
                         
  display.fillRect(5, 0, (MaxX-1)*rz, 40, WHITE);
  myspeed=800; tempspeed=myspeed;
  Victory=0;

} 
//================================================
void setup() 
{ unsigned long tr; word gg=0;
     
  randomSeed(analogRead(0));
  irrecv.enableIRIn();     // Старт ресивера IRDA
  display.begin();
  display.setContrast(50);
  display.setTextSize(1);
  display.setTextColor(BLACK); // установка цвета текста
  display.clearDisplay();
  Lst=rz*MaxX; // ширина стакана в пикселях
  MaxY=display.height()/rz+4; // Высота стакана в кубиках
  SmeH=display.height()%rz; // смещение сверху в пикселях для отображения
  random(7);

  EEPROM_read(0, ok);
  EEPROM_read(4, levo);
  EEPROM_read(8, pravo);
  EEPROM_read(12, vniz);
  EEPROM_read(20, tr);
  if (tr==flfirst) EEPROM_read(16, myrecord);
     else { myrecord=0; 
            EEPROM_write(16, myrecord);
            EEPROM_write(20, flfirst);
          }    
  newgame();
}
//================================================
void dvoiki()
{
 for (byte i=0;i<MaxX;i++)
  for (byte j=0;j<MaxY;j++)
    if (mstacan[i][j]==1)
        mstacan[i][j]=2;
}
//================================================
void mydelay(int md)
{
 unsigned long starttime=millis();  
 while (millis()-starttime<md) 
   {
   if (irrecv.decode(&results;)) // ловим код кнопки пульта 
      {
       if (results.value==levo) dxx=-1; else
       if (results.value==pravo) dxx=1; else 
       if (results.value==ok) dxx=2; else
       if (results.value==vniz) {tempspeed=100; md=0;} 
       if (dxx!=0) {iffig(dxx,0);  viewstacan(); }
       delay(40); // убираем дребезг  
       irrecv.resume();
      }
   }
}
//================================================
bool iffs() // есть ли полные строки?
{
bool res=false;
 for (byte j=0;j<MaxY;j++)
 {
  bool fl=true;
  for (byte i=0;i<MaxX;i++)
    if (mstacan[i][MaxY-1-j]==0) fl=false;
  if (fl) 
    {
    Victory++; 
    myspeed=myspeed-(Victory/30)*50; // через каждые 30 линий увеличим скорость падения
    if (myspeed<0) myspeed=0;
    res=true; mybeep(); 
    for (byte k=0;k<MaxX;k++) 
     for (byte n=j;n<MaxY-2;n++) 
        mstacan[k][MaxY-n-1]=mstacan[k][MaxY-n-2];
     viewstacan();
    } 
 }
 return(res);
}
//================================================
void loop()
{   if (!iffig(dxx,dyy)) 
       if (!iffig(0,dyy))
          { dvoiki();
            figinit(random(7)+1);
            FigX=center;FigY=0;
            while (iffs()) ;
          }
   viewstacan();
   mydelay(tempspeed);
}

Игра поддерживает привязку к любому пульту. Для этого достаточно в начале игры, на вопрос «Нажмите ОК» нажать на пульте кнопку, которая будет отвечать за вращение фигуры. Если пульт игре уже знакомый, то игра сразу запустится. Если пульт новый, то код кнопки ОК не совпадет с запомненным и игра потребует последовательно нажать кнопки «Влево», «Вправо» и «Вниз». Эти кнопки будут записаны в энергонезависимую память Ардуино и впоследствии именно этот пульт будет узнаваться сразу по нажатию кнопки «ОК».

Как сделать тетрис классический

При «проваливании» на собранную строку будет воспроизводиться писк. Он реализован на особенности нескольких пинов Ардуино (в нашем случае 9) выдавать ШИМ с заданной частотой. Игра поддерживает все атрибуты нормальной игры. Ту и подсказка следующей фигуры и текущий счет. Игра ведет учет рекордов. Это значение хранится в энергонезависимой памяти Ардуино. Чтобы сбросить рекорд, достаточно изменить в скетче значение flfirst=1234 на любое другое. В игре также идет автоувеличение скорости падения через каждые 30 списанных строчек, так что, бесконечно долго поиграть не получится ). Скетч не оптимизировался и тщательно не прогонялся, а был написан на досуге в свое удовольствие. Если кто обнаружит ошибку — пишите. О ©. Скетч разрешается править для себя как угодно. Только при публикации где-либо своих вариантов ссылку на первоисточник-муську указывайте ). Для чего делал — длинные выходные + «из любви к искусству». Была бы дочка маленькой, сделал бы ей, наверное, мини игровой автомат для кукольной комнатки на 8 марта, как раз успел бы. Добавил бы несколько игр типа Змейки и Арканоида, а корпус вырезал бы из текстолита, наверное. Только дочка в этом году уже докторскую защищает, так, что мимо, но может кому еще эта идея пригодится ).

Как сделать тетрис классический
Файлы для скачивания:
* комментарии публикуются после модерации
Нет комментариев