Projekt gry Tetris na Arduino Uno to implementacja klasycznej gry Tetris przy użyciu mikrokontrolera Arduino Uno. Gra będzie wyświetlana na małym ekranie LCD i będzie sterowana za pomocą przycisków takich jak przyciski nawigacyjne. Gracz będzie mógł poruszać i obracać klockami, starając się ułożyć je w pełne rzędy, które znikną, gdy zostaną ułożone w całość. Celem projektu będzie zaprogramowanie mechaniki gry Tetris oraz obsługa interakcji z graczem przy użyciu ograniczonych zasobów mikrokontrolera Arduino Uno.
1. Arduino UNO
2.Płytka do sterowania ( ESP8266)
3. Wyświetlacz LED 128x64
4. Przewód łączący Arduino z komputerem
5.Para rąk
Tworzenie gry Tetris na platformie Arduino Uno stanowi fascynujące wyzwanie, zwłaszcza ze względu na ograniczone zasoby tego mikrokontrolera oraz konieczność efektywnego zarządzania pamięcią i zasobami obliczeniowymi. Jednym z największych i najbardziej pracochłonnych problemów jest stworzenie pola do gry, czyli obszar, na którym klocki Tetrisa będą się poruszać i układać.
Pierwszym krokiem jest zdefiniowanie struktury danych reprezentującej pole do gry.
Następnym krokiem jest implementacja funkcji odpowiedzialnej za rysowanie pola do gry na wybranym wyświetlaczu. Tutaj należy uwzględnić zarówno fizyczną wielkość wyświetlacza, jak i sposób reprezentacji stanu pola na ekranie. Może to wymagać przeliczenia współrzędnych wirtualnych na fizyczne piksele na ekranie.
Kolejnym wyzwaniem jest obsługa poruszania się klocków po polu gry oraz ich kolizji z innymi klockami lub granicami pola. Tutaj konieczne jest uwzględnienie reguł gry Tetris oraz odpowiednie zarządzanie stanem pola, tak aby klocki mogły się swobodnie poruszać, obracać i układać w rzędy.
Ważne jest także zapewnienie płynności działania gry przy ograniczonych zasobach mikrokontrolera. Może to wymagać optymalizacji kodu oraz wykorzystania odpowiednich algorytmów do zarządzania czasem i pamięcią.
Podsumowując, choć napisanie pola do gry Tetris na Arduino Uno może być trudnym i pracochłonnym zadaniem ze względu na ograniczone zasoby mikrokontrolera, odpowiednia analiza wymagań, optymalizacja kodu oraz kreatywne podejście do rozwiązywania problemów mogą prowadzić do efektywnej implementacji tej klasycznej gry na platformie sprzętowej.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define WIDTH 64 // OLED display width, in pixels
#define HEIGHT 128 // OLED display height, in pixels
Adafruit_SSD1306 display(128, 64, &Wire, -1);
static const unsigned char PROGMEM mantex_logo [] = {
0x00, 0x00, 0x18, 0x06, 0x01, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3c, 0x0f, 0x03, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x03, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x3f, 0x80, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x3e, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x7c, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x78, 0x7f, 0xff, 0xff, 0xe1, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xff, 0xff, 0xff, 0xf8, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xff, 0xff, 0xff, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0xf1, 0xff, 0x0f, 0xff, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf1, 0xfc, 0x01, 0xff, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf1, 0xf0, 0x00, 0xff, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0xf1, 0xf0, 0x00, 0x7f, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x01, 0xf1, 0xe0, 0x70, 0x3f, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xe1, 0xf8, 0x3f, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xe1, 0xff, 0xff, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xe1, 0xff, 0xff, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xc1, 0xff, 0xff, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xe1, 0xe0, 0x07, 0xfc, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0xf1, 0xe1, 0xe0, 0x01, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf1, 0xe1, 0xe0, 0x00, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf1, 0xe0, 0xe3, 0xf8, 0x7c, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0xf1, 0xe0, 0x63, 0xfc, 0x7c, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x3f, 0xf1, 0xf0, 0x23, 0xfc, 0x3c, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xf8, 0x23, 0xfc, 0x3c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xff, 0x63, 0xfc, 0x3c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xff, 0xe3, 0xfc, 0x3c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xff, 0xe3, 0xfc, 0x7c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xff, 0xe3, 0xf8, 0x7c, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0xf1, 0xff, 0xe1, 0xf0, 0x7c, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf1, 0xff, 0xe0, 0x00, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf1, 0xff, 0xe0, 0x03, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf1, 0xff, 0xe0, 0x1f, 0xfc, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0xf1, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf1, 0xff, 0xff, 0xff, 0xf8, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0xf0, 0xff, 0xff, 0xff, 0xf8, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x78, 0x7f, 0xff, 0xff, 0xf0, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x7c, 0x1f, 0xff, 0xff, 0xc1, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x3c, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x3f, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x1f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3e, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3c, 0x0f, 0x83, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x3c, 0x0f, 0x01, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
const char pieces_S_l[2][2][4] = {{
{0, 0, 1, 1}, {0, 1, 1, 2}
},
{
{0, 1, 1, 2}, {1, 1, 0, 0}
}};
const char pieces_S_r[2][2][4]{{
{1, 1, 0, 0}, {0, 1, 1, 2}
},
{
{0, 1, 1, 2}, {0, 0, 1, 1}
}};
const char pieces_L_l[4][2][4] = {{
{0, 0, 0, 1}, {0, 1, 2, 2}
},
{
{0, 1, 2, 2}, {1, 1, 1, 0}
},
{
{0, 1, 1, 1}, {0, 0, 1, 2}
},
{
{0, 0, 1, 2}, {1, 0, 0, 0}
}};
const char pieces_Sq[1][2][4] = {{
{0, 1, 0, 1}, {0, 0, 1, 1}
}};
const char pieces_T[4][2][4] = {{
{0, 0, 1, 0},{0, 1, 1, 2}
},
{
{0, 1, 1, 2},{1, 0, 1, 1}
},
{
{1, 0, 1, 1},{0, 1, 1, 2}
},
{
{0, 1, 1, 2},{0, 0, 1, 0}
}};
const char pieces_l[2][2][4] = {{
{0, 1, 2, 3}, {0, 0, 0, 0}
},
{
{0, 0, 0, 0}, {0, 1, 2, 3}
}};
const short MARGIN_TOP = 19;
const short MARGIN_LEFT = 3;
const short SIZE = 5;
const short TYPES = 6;
int click[] = { 1047 };
int click_duration[] = { 100 };
int erase[] = { 2093 };
int erase_duration[] = { 100 };
word currentType, nextType, rotation;
short pieceX, pieceY;
short piece[2][4];
int interval = 20, score;
long timer, delayer;
boolean grid[10][18];
boolean b1, b2, b3;
int left=9;
int right=7;
int change=10;
int speed=8;
void checkLines(){
boolean full;
for(short y = 17; y >= 0; y--){
full = true;
for(short x = 0; x < 10; x++){
full = full && grid[x][y];
}
if(full){
breakLine(y);
y++;
}
}
}
void breakLine(short line){
delay(100);
for(short y = line; y >= 0; y--){
for(short x = 0; x < 10; x++){
grid[x][y] = grid[x][y-1];
}
}
for(short x = 0; x < 10; x++){
grid[x][0] = 0;
}
display.invertDisplay(true);
delay(50);
display.invertDisplay(false);
score += 10;
}
void refresh(){
display.clearDisplay();
drawLayout();
drawGrid();
drawPiece(currentType, 0, pieceX, pieceY);
display.display();
}
void drawGrid(){
for(short x = 0; x < 10; x++)
for(short y = 0; y < 18; y++)
if(grid[x][y])
display.fillRect(MARGIN_LEFT + (SIZE + 1)*x, MARGIN_TOP + (SIZE + 1)*y, SIZE, SIZE, WHITE);
}
boolean nextHorizontalCollision(short piece[2][4], int amount){
for(short i = 0; i < 4; i++){
short newX = pieceX + piece[0][i] + amount;
if(newX > 9 || newX < 0 || grid[newX][pieceY + piece[1][i]])
return true;
}
return false;
}
boolean nextCollision(){
for(short i = 0; i < 4; i++){
short y = pieceY + piece[1][i] + 1;
short x = pieceX + piece[0][i];
if(y > 17 || grid[x][y])
return true;
}
return false;
}
void generate(){
currentType = nextType;
nextType = random(TYPES);
if(currentType != 5)
pieceX = random(9);
else
pieceX = random(7);
pieceY = 0;
rotation = 0;
copyPiece(piece, currentType, rotation);
}
void drawPiece(short type, short rotation, short x, short y){
for(short i = 0; i < 4; i++)
display.fillRect(MARGIN_LEFT + (SIZE + 1)*(x + piece[0][i]), MARGIN_TOP + (SIZE + 1)*(y + piece[1][i]), SIZE, SIZE, WHITE);
}
void drawNextPiece(){
short nPiece[2][4];
copyPiece(nPiece, nextType, 0);
for(short i = 0; i < 4; i++)
display.fillRect(50 + 3*nPiece[0][i], 4 + 3*nPiece[1][i], 2, 2, WHITE);
}
void copyPiece(short piece[2][4], short type, short rotation){
switch(type){
case 0: //L_l
for(short i = 0; i < 4; i++){
piece[0][i] = pieces_L_l[rotation][0][i];
piece[1][i] = pieces_L_l[rotation][1][i];
}
break;
case 1: //S_l
for(short i = 0; i < 4; i++){
piece[0][i] = pieces_S_l[rotation][0][i];
piece[1][i] = pieces_S_l[rotation][1][i];
}
break;
case 2: //S_r
for(short i = 0; i < 4; i++){
piece[0][i] = pieces_S_r[rotation][0][i];
piece[1][i] = pieces_S_r[rotation][1][i];
}
break;
case 3: //Sq
for(short i = 0; i < 4; i++){
piece[0][i] = pieces_Sq[0][0][i];
piece[1][i] = pieces_Sq[0][1][i];
}
break;
case 4: //T
for(short i = 0; i < 4; i++){
piece[0][i] = pieces_T[rotation][0][i];
piece[1][i] = pieces_T[rotation][1][i];
}
break;
case 5: //l
for(short i = 0; i < 4; i++){
piece[0][i] = pieces_l[rotation][0][i];
piece[1][i] = pieces_l[rotation][1][i];
}
break;
}
}
short getMaxRotation(short type){
if(type == 1 || type == 2 || type == 5)
return 2;
else if(type == 0 || type == 4)
return 4;
else if(type == 3)
return 1;
else
return 0;
}
boolean canRotate(short rotation){
short piece[2][4];
copyPiece(piece, currentType, rotation);
return !nextHorizontalCollision(piece, 0);
}
void drawLayout(){
display.drawLine(0, 15, WIDTH, 15, WHITE);
display.drawRect(0, 0, WIDTH, HEIGHT, WHITE);
drawNextPiece();
char text[6];
itoa(score, text, 10);
drawText(text, getNumberLength(score), 7, 4);
}
short getNumberLength(int n){
short counter = 1;
while(n >= 10){
n /= 10;
counter++;
}
return counter;
}
void drawText(char text[], short length, int x, int y){
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE); // Draw white text
display.setCursor(x, y); // Start at top-left corner
display.cp437(true); // Use full 256 char 'Code Page 437' font
for(short i = 0; i < length; i++)
display.write(text[i]);
}
void setup() {
pinMode(left, INPUT_PULLUP);
pinMode(right, INPUT_PULLUP);
pinMode(change, INPUT_PULLUP);
pinMode(speed, INPUT_PULLUP);
Serial.begin(9600);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
display.setRotation(1);
display.clearDisplay();
display.drawBitmap(3, 23, mantex_logo, 64, 82, WHITE);
display.display();
delay(2000);
display.clearDisplay();
drawLayout();
display.display();
randomSeed(analogRead(0));
nextType = random(TYPES);
generate();
timer = millis();
}
void loop() {
if(millis() - timer > interval){
checkLines();
refresh();
if(nextCollision()){
for(short i = 0; i < 4; i++)
grid[pieceX + piece[0][i]][pieceY + piece[1][i]] = 1;
generate();
}else
pieceY++;
timer = millis();
}
if(!digitalRead(left)){
delay(100);
if(b1){
if(!nextHorizontalCollision(piece, -1)){
pieceX--;
refresh();
}
b1 = false;
}
}else{
b1 = true;
}
if(!digitalRead(right)){
delay(100);
if(b2){
if(!nextHorizontalCollision(piece, 1)){
pieceX++;
refresh();
}
b2 = false;
}
}else{
b2 = true;
}
if(!digitalRead(speed)){
interval = 20;
}else{
interval = 400;
}
if(!digitalRead(change)){
delay(100);
if(b3){
if(rotation == getMaxRotation(currentType) - 1 && canRotate(0)){
rotation = 0;
}else if(canRotate(rotation + 1)){
rotation++;
}
copyPiece(piece, currentType, rotation);
refresh();
b3 = false;
delayer = millis();
}
} else if(millis() - delayer > 50){
b3 = true;
}
}