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

Переход в игру и обратно по кнопке 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;
}
Все — игра готова — смотрим видео
И скачать скетч здесь

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