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