Tetris

Typ_projektu
Arduino
Zdjecie główne
Krótki opis projektu

Program jest implementacją gry "Tetris"

Niezbędne elementy

Arduino

Opis projektu

Program jest implementacją gry "Tetris". Celem gry jest ułożenie pełnych linii z klocków o różnych kształtach. Punktowane jest też przyspieszanie opadającego klocka przez użytkownika za pomocą przycisku DOWN. Po wyświetleniu ekranu startowego użytkownik ma możliwość wyboru jednego z czterech trybów gry. W trybie NORMAL klocek opada o jedną kratkę co 0,8s. W trybie FAST - co 0,4s. Tryb HARD dodatkowo zawiera poruszanie się planszy w lewo i w prawo w losowy sposób. W trybie MEMORY plansza nie jest widoczna, ukazuje się tylko na 0,5s po kontakcie z klockiem. W każdym z trybów użytkownik widzi nad planszą swój aktualny wynik oraz kształt następnego klocka. Istnieje też możliwość zatrzymania gry za pomocą przycisku PAUZA, a następnie powrót do rozgrywki lub do MENU z wyborem trybów gry. Ostateczny wynik jest wyświetlany na ekranie końcowym.

Zdjęcia
kod programu

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>

#define PIN_LEFT 7
#define PIN_RIGHT 9
#define PIN_UP 8
#define PIN_DOWN 10
#define PIN_B 5
#define PIN_A 4

#define i2c_Address 0x3c
Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1);

enum GameState { BOOT, MENU, PLAYING, PAUSE, GAMEOVER };
GameState currentState = BOOT;

enum Mode { NORMAL, FAST, HARD, MEMORY };
Mode currentMode = NORMAL;
int menuSelection = 0;
int pauseSelection = 0;
//Board geometry
#define BLOCK_SIZE 4
#define COLS 10
#define ROWS 20
int BOARD_X = 12; 
#define BOARD_Y 22

bool board[COLS][ROWS] = {0}; // table of fallen blocks
int curX, curY, curPiece, curRot, nextPiece;
unsigned long lastDrop = 0;
unsigned long memoryTimer = 0; 
unsigned long shakeTimer = 0; 
int shakeOffset = 0;
int nextShakeDelay = 2000;
int dropInterval = 800;
long score = 0;

// 4x4 grid writen in hex to represent shapes of the falling blocks
const uint16_t shapes[7][4] = {
 {0x0F00, 0x4444, 0x0F00, 0x4444}, // I
 {0x0660, 0x0660, 0x0660, 0x0660}, // O
 {0x4E00, 0x4640, 0x0E40, 0x4C40}, // T
 {0x06C0, 0x8C40, 0x06C0, 0x8C40}, // S
 {0x0C60, 0x4620, 0x0C60, 0x4620}, // Z
 {0x4460, 0x0E80, 0xC440, 0x2E00}, // L
 {0x44C0, 0x8E00, 0xE200, 0x4C40}  // J
};

void setup() {
 pinMode(PIN_LEFT, INPUT_PULLUP); pinMode(PIN_RIGHT, INPUT_PULLUP);
 pinMode(PIN_UP, INPUT_PULLUP); pinMode(PIN_DOWN, INPUT_PULLUP);
 pinMode(PIN_A, INPUT_PULLUP); pinMode(PIN_B, INPUT_PULLUP);
 display.begin(i2c_Address, true);
 display.setRotation(3); 
 randomSeed(analogRead(0));
}

void loop() {
 //switch deciding which screen to display
 switch (currentState) {
   case BOOT:      runBootScreen(); break;
   case MENU:      runMenu();       break;
   case PLAYING:   runGame();       break;
   case PAUSE:     runPause();      break;
   case GAMEOVER:  runGameOver();   break;
 }
}

void runBootScreen() {
 display.clearDisplay();
 display.setTextSize(1);
 display.setTextColor(SH110X_WHITE);
 display.setCursor(15, 30);
 display.print("TETRIS");
 if ((millis() / 500) % 2 == 0) {
   display.setCursor(5, 100);
   display.print("PRESS ANY KEY");
 }
 display.display();
 if (digitalRead(PIN_LEFT) == LOW || digitalRead(PIN_RIGHT) == LOW || 
     digitalRead(PIN_UP) == LOW || digitalRead(PIN_DOWN) == LOW || 
     digitalRead(PIN_A) == LOW || digitalRead(PIN_B) == LOW) {
   currentState = MENU;
   delay(300); 
 }
}

void runMenu() {
 display.clearDisplay();
 display.setCursor(15, 5); display.print("MODE:"); 
 display.drawLine(0, 15, 64, 15, SH110X_WHITE);
 const char* options[] = {"NORMAL", "FAST", "HARD", "MEMORY"};
 for(int i=0; i<4; i++) {
   display.setCursor(10, 30 + (i*15));
   if(menuSelection == i) display.print("> ");
   display.print(options[i]);
 }
 if(digitalRead(PIN_UP) == LOW) { menuSelection--; if(menuSelection < 0) menuSelection = 3; delay(150); }
 if(digitalRead(PIN_DOWN) == LOW) { menuSelection++; if(menuSelection > 3) menuSelection = 0; delay(150); }
 if(digitalRead(PIN_B) == LOW) { currentMode = (Mode)menuSelection; startGame(); delay(200); }
 display.display();
}

void startGame() {
 memset(board, 0, sizeof(board));
 score = 0; currentState = PLAYING;
 BOARD_X = 12; shakeOffset = 0; nextShakeDelay = 2000;
 curPiece = random(0, 7);
 nextPiece = random(0, 7); 
 spawnPiece();
 if(currentMode == NORMAL) dropInterval = 800;
 else if(currentMode == FAST) dropInterval = 400;
 else if(currentMode == HARD) dropInterval = 400; 
 else if(currentMode == MEMORY) dropInterval = 600;
}

void runPause() {
 display.clearDisplay(); 
 display.setCursor(15, 10); display.print("PAUSE");
 display.drawLine(0, 20, 64, 20, SH110X_WHITE);
 const char* pOptions[] = {"RESUME", "QUIT"};
 for(int i=0; i<2; i++) {
   display.setCursor(10, 40 + (i*15));
   if(pauseSelection == i) display.print("> ");
   display.print(pOptions[i]);
 }
 if(digitalRead(PIN_UP) == LOW) { pauseSelection = 0; delay(150); }
 if(digitalRead(PIN_DOWN) == LOW) { pauseSelection = 1; delay(150); }
 if(digitalRead(PIN_B) == LOW) {
   if(pauseSelection == 0) currentState = PLAYING;
   else currentState = MENU;
   delay(250);
 }
 display.display();
}

void runGame() {
 //Opens Pause menu
 if(digitalRead(PIN_A) == LOW) { 
   delay(200); 
   pauseSelection = 0; 
   currentState = PAUSE; 
   return; 
 }
 //Mode hard logic
 //shakes the platform each 2000ms randomly to the left or right
 if(currentMode == HARD) {
   if(millis() - shakeTimer > nextShakeDelay) {
     int maxRange = 2 + (score / 2000); 
     if(maxRange > 5) maxRange = 5;
     shakeOffset = random(-maxRange, maxRange + 1);
     int baseDelay = 1500 - (score / 10); 
     if(baseDelay < 300) baseDelay = 300;
     nextShakeDelay = random(baseDelay / 2, baseDelay); 
     shakeTimer = millis();
   }
   BOARD_X = 12 + shakeOffset;
 } else { BOARD_X = 12; }

 handleInput();
 if (millis() - lastDrop > dropInterval) {
   if (!movePiece(0, 1)) {
     lockPiece();
     checkLines();
     spawnPiece();
     if(currentMode == MEMORY) memoryTimer = millis();
   }
   lastDrop = millis();
 }
 drawGame();
}

void handleInput() {
 if (digitalRead(PIN_LEFT) == LOW) { if(movePiece(-1, 0)) delay(120); }
 if (digitalRead(PIN_RIGHT) == LOW) { if(movePiece(1, 0)) delay(120); }
 if (digitalRead(PIN_DOWN) == LOW) { 
   if(movePiece(0, 1)) score += 1; 
   delay(40); 
 }

 if (digitalRead(PIN_B) == LOW || digitalRead(PIN_UP) == LOW) { 
   int nextRot = (curRot + 1) % 4;
   if (!checkCollision(curX, curY, nextRot)) curRot = nextRot;
   delay(180);
 }
}
//Physic.Check for colison with the border or other blocks
bool checkCollision(int nx, int ny, int nr) {
 for (int i = 0; i < 4; i++) {
   for (int j = 0; j < 4; j++) {
     if ((shapes[curPiece][nr] >> (15 - (j * 4 + i))) & 1) {
       int px = nx + i; int py = ny + j;
       if (px < 0 || px >= COLS || py >= ROWS || (py >= 0 && board[px][py])) return true;
     }
   }
 }
 return false;
}

bool movePiece(int dx, int dy) {
 if (!checkCollision(curX + dx, curY + dy, curRot)) { curX += dx; curY += dy; return true; }
 return false;
}

void lockPiece() {
 for (int i = 0; i < 4; i++)
   for (int j = 0; j < 4; j++)
     if ((shapes[curPiece][curRot] >> (15 - (j * 4 + i))) & 1)
       if (curY + j >= 0) board[curX + i][curY + j] = true;
}

void checkLines() {
 int lines = 0;
 for (int j = ROWS - 1; j >= 0; j--) {
   bool full = true;
   for (int i = 0; i < COLS; i++) if (!board[i][j]) full = false;
   if (full) {
     lines++;
     for (int k = j; k > 0; k--) for (int i = 0; i < COLS; i++) board[i][k] = board[i][k-1];
     for (int i = 0; i < COLS; i++) board[i][0] = false;
     j++; 
   }
 }
 if (lines == 1) score += 100; 
 else if (lines == 2) score += 300;
 else if (lines == 3) score += 500; 
 else if (lines == 4) score += 800;
}

void spawnPiece() {
 curX = 3; curY = 0; curPiece = nextPiece; nextPiece = random(0, 7); curRot = 0;
 if (checkCollision(curX, curY, curRot)) currentState = GAMEOVER;
}
//Draws the game state in the current state
void drawGame() {
 display.clearDisplay();
 display.setCursor(0, 0); display.print("S:"); display.print(score);
 display.setCursor(40, 0); display.print("N:");
 for (int i = 0; i < 4; i++)
   for (int j = 0; j < 4; j++)
     if ((shapes[nextPiece][0] >> (15 - (j * 4 + i))) & 1)
       display.fillRect(52 + i*3, 0 + j*3, 2, 2, SH110X_WHITE);

 display.drawRect(BOARD_X-1, BOARD_Y-1, COLS*BLOCK_SIZE+2, ROWS*BLOCK_SIZE+2, SH110X_WHITE);
 bool showBoard = (currentMode != MEMORY) || (millis() - memoryTimer < 500);
 for (int i = 0; i < COLS; i++)
   for (int j = 0; j < ROWS; j++)
     if (board[i][j] && showBoard)
       display.fillRect(BOARD_X + i*BLOCK_SIZE, BOARD_Y + j*BLOCK_SIZE, BLOCK_SIZE-1, BLOCK_SIZE-1, SH110X_WHITE);
 
 for (int i = 0; i < 4; i++)
   for (int j = 0; j < 4; j++)
     if ((shapes[curPiece][curRot] >> (15 - (j * 4 + i))) & 1)
       display.fillRect(BOARD_X+(curX+i)*BLOCK_SIZE, BOARD_Y+(curY+j)*BLOCK_SIZE, BLOCK_SIZE-1, BLOCK_SIZE-1, SH110X_WHITE);
 display.display();
}
//Displays end of game screen
void runGameOver() {
 display.clearDisplay();
 display.setCursor(5, 20); display.print("GAME OVER");
 display.setCursor(5, 40); display.print("SCORE:");
 display.setCursor(5, 50); display.print(score);
 display.setCursor(5, 100); display.print("Press B"); 
 display.display();
 if(digitalRead(PIN_B) == LOW) { currentState = MENU; delay(300); }
}

Pliki_projektu
Schemat
Youtube
Tagi
tetris