Program jest implementacją gry "Tetris"
Arduino
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.
#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); }
}