Realizovat hru Tetris, která bude pro zobrazování využívat vhodného terminálu na sériové lince (např. PuTTY a standard VT100). Pro řízení pohybu využit akcelerometr na kitu FRDM-KL25Z. Stav hry se indikuje pomocí RGB LED.
Klasická hra Tetris představuje pohyb různých prvků v omezeném okně ve směru dolů a jejich vložení do sebe. Když je nějaký řádek plný, pak se odstaňuje. Základní prvky (figury), které se mohou být použity v Tetrisu, jsou uvedeny na obrázku číslo 1.
Obr.1 Prvky hry Tetris
V tomto projektu pro jednoduchost a krátkost kódu budou použity jen 2.prvek a variace 3. a 5.prvku.
V tomto projektu hardware je docela jednoduchý. Pro implementaci projektu je použitá deska mbed FRDM-KL25Z a kabel mini-USB, který je připojen k PC. Charakteristiky desky KL25Z jsou následující:
Zapojení desky je uvedeno na obrázku císlo 2.
Obr.2 Zapojení kitu KL25Z
Pro náhravání programu z počítače se používá mini-USB port OpenSDA, pro jeho spuštění - mini-USB port USB-KL25Z.
Pro realizaci hry Tetris v tomto projektu je použit compiler na webu https://developer.mbed.org/, ve kterém je možné vytvořit libovolný projekt na C++. Taky pro zobrazení hry je použit terminal PuTTY se standardem VT100, pracující na virtuálním sériovém portu COM4 (pro konkretní počítač).
Hlavní funkce MAIN je realizována pomocí několika jednodušších funkcí:
int main(void) { wait(1.5); // male zpozdeni, aby se okno programu PuTTY otevrelo driv, nez se zacne hra srand(time(0)); // tato funkce je pouzita, aby poslopnost figur nebyla vzdy stejna n = rand() % 13; // nahone cislo figury array(); // zapis displeje while(complete == 0) { // pokud neni masiv displeje plny, probiha hra upgrade(); // obnoveni promennych newFigure(); // zapis nove figury move(); // pohyb figury vlvo a vpravo rotate(); // rotace figury vlevo a vpravo zz = s1; // aktualizace pozice zapisu figury podle Ox step(); // aktualni krok hry (pohyb figury dolu) display(); // zobrazeni aktualniho kroku erase(); // vymazani kroku collision(); // signalizace kolize a prechod zase do pozice = 1 a zapis nove figury } end(); // organizace konce hry }
Kratké vysvětlení funkcí:
Knihovny použité ve zdrojovém kódu jsou následující:
Stav hry se indikuje pomocí LEDek:
V každém kroku hry se taky zobrazuje počet bodů SCORE. Za odstanění jednoho plného řádku hráč dostává 100 bodů. Plný zdrojový kód s podrobnějším popisem funkcí a proměnných je uveden níže.
//=======================Inkludovani vhodnych knihoven============================= #include <stdio.h> // standartni knihovna pro zakladni funkce #include "mbed.h" // knihovna mbed #include "MMA8451Q.h" // knihovna pro rozhrani akcelerometru #include "USBSerial.h" // knihovna serioveho USB-portu #include <stdlib.h> // knihovna pro generaci preudonahodnych cisel #include <time.h> // taky pro nahodna cislaб pro praci s casem //================================================================================= //=======================Definice portu pro akcelerometr=========================== #define MMA8451_I2C_ADDRESS (0x1d<<1) //================================================================================= USBSerial serial; // seriovy port // ======================Deklarace promennych====================================== DigitalOut red(LED1, 1); // definice pro cervenou, modrou a zelenou LEDku DigitalOut green(LED2, 0); // nastaveni zelene na 0 (zapnuta), ostatni na 1 DigitalOut blue(LED3, 1); //----------figury----------------------------------------------------------------- int f0[3][3] = { {79, 79, 32}, // * * {79, 79, 32}, // * * {32, 32, 32}, }; int f1[3][3] = { {79, 32, 32}, // * {79, 79, 79}, // * * * {32, 32, 32}, }; int f2[3][3] = { {32, 32, 79}, // * {79, 79, 79}, // * * * {32, 32, 32}, }; int f3[3][3] = { {79, 79, 79}, // * * * {32, 32, 79}, // * {32, 32, 32}, }; int f4[3][3] = { {79, 79, 79}, // * * * {79, 32, 32}, // * {32, 32, 32}, }; int f5[3][3] = { {32, 79, 32}, // * {79, 79, 79}, // * * * {32, 32, 32}, }; int f6[3][3] = { {79, 79, 79}, // * * * {32, 79, 32}, // * {32, 32, 32}, }; int f7[3][3] = { {79, 32, 32}, // * {79, 79, 32}, // * * {79, 32, 32}, // * }; int f8[3][3] = { {32, 79, 32}, // * {79, 79, 32}, // * * {32, 79, 32}, // * }; int f9[3][3] = { {79, 32, 32}, // * {79, 32, 32}, // * {79, 79, 32}, // * * }; int f10[3][3] = { {32, 79, 32}, // * {32, 79, 32}, // * {79, 79, 32}, // * * }; int f11[3][3] = { {79, 79, 32}, // * * {79, 32, 32}, // * {79, 32, 32}, // * }; int f12[3][3] = { {79, 79, 32}, // * * {32, 79, 32}, // * {32, 79, 32}, // * }; int f[3][3] = { {32, 32, 32}, {32, 32, 32}, // prazdna {32, 32, 32}, }; //--------------------------------------------------------------------------------- int a[16][12]; int l = sizeof(a)/sizeof(a[0]); int s = sizeof(a[0])/sizeof(a[0][0]); float x, y, point = 0.34; int i, j, p = 0, q = 0, s1 = 0; int position = 0, n, h, zz, score; bool collis, complete = 0; // a[16][12] - masiv pro aktualni zobrazeni hry // l - pocet radku masivu // s - pocet sloupcu masivu // x, y - vstupni hodnoty z akcelerometru // point - prahova hodnota pro akcelerometr // i, j - promenne cyklu // p - pocet radku figury, q - pocet sloupcu figury // s1 - pozice podle Ox, ze ktere se zapisuje figura do masivu // position - pozice podle Oy, ze ktere se zapisuje figura do masivu // n - radove cislo figury // h - promenna rozhodujici, zda je figura v pohybu // zz - aktualizovana promenna s1 // score - skore hry // collis - signalizace kolize figur // complete - nastavuje se na "1" tehdy, kdyz je masiv plny a musi byt konec hry //=======================Funkce zapisu do masivu displeje========================== void array(void) { for (i = 0; i < l; i++) { for (j = 0; j < s; j++) { if ((i == 0) || (i == l - 1) || (j == 0) || (j == s - 1)) a[i][j] = 79; else a[i][j] = 32; } // 32 - prazdny prvek, zapisuji se uvnitr pole } // 79 - prvek "O", zapisuji se do leve, prave stran, nahore a dole } //================================================================================= //===========Funkce vypoctu parametru figury a jeji zapisu do prazdne============== void sizes_of_figure(int *figure, int *nul_fig, int &p, int &q, int &s1) { int i; int c[3] = {0, 0, 0}, e[3] = {0, 0, 0}; // masivy pro zapis poctu elementu for (i = 0; i < 3; i++) { if (figure[i] == 79) c[i]++; if (figure[i+3] == 79) c[i]++; // hledani elementu "O" po sloupcich if (figure[i+6] == 79) c[i]++; } for (i = 0; i < 3; i++) { if (figure[i*3] == 79) e[i]++; if (figure[i*3+1] == 79) e[i]++; // hleani elementu "O" po radcich if (figure[i*3+2] == 79) e[i]++; } p = c[0]; q = e[0]; for (i = 1; i < 3; i++) { if (c[i] > p) p = c[i]; // hledani max hodnoty v masivech c a e } // coz budou rozmery figury p a q for (i = 1; i < 3; i++) { if (e[i] > q) q = e[i]; } s1 = s/2 - q/2; // vypocet pozice pro zapis figury do pole podle Ox for (i = 0; i < 9; i++) { nul_fig[i] = figure[i]; // zapis aktualni figury do prazdne } } //================================================================================= //=======================Funkce nahodneho vyberu figury============================ void choise(int &nn, int &p, int &q, int &s1) { switch (nn) { case 0: sizes_of_figure(&f0[0][0],&f[0][0], p, q, s1); break; case 1: sizes_of_figure(&f1[0][0],&f[0][0], p, q, s1); break; case 2: sizes_of_figure(&f2[0][0],&f[0][0], p, q, s1); break; case 3: sizes_of_figure(&f3[0][0],&f[0][0], p, q, s1); break; case 4: sizes_of_figure(&f4[0][0],&f[0][0], p, q, s1); break; case 5: sizes_of_figure(&f5[0][0],&f[0][0], p, q, s1); break; case 6: sizes_of_figure(&f6[0][0],&f[0][0], p, q, s1); break; case 7: sizes_of_figure(&f7[0][0],&f[0][0], p, q, s1); break; case 8: sizes_of_figure(&f8[0][0],&f[0][0], p, q, s1); break; case 9: sizes_of_figure(&f9[0][0],&f[0][0], p, q, s1); break; case 10: sizes_of_figure(&f10[0][0],&f[0][0], p, q, s1); break; case 11: sizes_of_figure(&f11[0][0],&f[0][0], p, q, s1); break; case 12: sizes_of_figure(&f12[0][0],&f[0][0], p, q, s1); break; } } //================================================================================= //=======================Funkce pohybu figur vlevo a vpravo======================== void move(void) { if (y > point && s1 > 1) { // kdyz hodnota y akcelerometru s1--; // prekroci prahovou hodnotu h = 1; // figura se pohybuje, tj. se meni } // pozice podle Ox s1 if (y < -point && s1 < s - q - 1) { // h je signalizator pohybu s1++; h = 2; } } //================================================================================= //=======================Funkce rotace figury ve smeru doprava===================== void rotateRight(int &n) { switch(n) { case 0: n = 0; break; case 1: n = 11; break; case 2: n = 9; break; case 3: n = 10; break; case 4: n = 12; // smyslem je, ze se meni cislo figury break; case 5: n = 7; break; case 6: n = 8; break; case 7: n = 6; break; case 8: n = 5; break; case 9: n = 4; break; case 10: n = 1; break; case 11: n = 3; break; case 12: n = 2; break; } } //================================================================================= //=======================Funkce rotace figury ve smeru doleva====================== void rotateLeft(int &n) { switch(n) { case 0: n = 0; break; case 1: n = 10; break; case 2: n = 12; break; case 3: n = 11; // -------//-------- break; case 4: n = 9; break; case 5: n = 8; break; case 6: n = 7; break; case 7: n = 5; break; case 8: n = 6; break; case 9: n = 2; break; case 10: n = 3; break; case 11: n = 1; break; case 12: n = 4; break; } } //================================================================================= //======================Funkce obecne rotace======================================= void rotate(void) { int i, j; if (position < l - q - 2) { if (x > point) { for (i = position; i < position + p; i++) { // kdyz hodnota x for (j = s1; j < s1 + q; j++) { // akcelerometru prekroci a[i][j] = 32; // prahovou hodnotu point, } } rotateLeft(n); // figura se otoci doleva choise(n, p, q, zz); // tj. prezapisuje nova } if (x < -point) { for (i = position; i < position + p; i++) { for (j = s1; j < s1 + q; j++) { a[i][j] = 32; } } rotateRight(n); // stejne doprava choise(n, p, q, zz); } } } //================================================================================= //=============================Aktualizace promennych============================== void upgrade(void) { MMA8451Q acc(PTE25, PTE24, MMA8451_I2C_ADDRESS); x = acc.getAccX(); y = acc.getAccY(); // obnoveni promennych pro sousacny krok collis = 0; h = 0; position++; } //================================================================================= //============================Zapis nove figury==================================== void newFigure(void) { if (position == 1) { s1 = 0; // zapisuje se nova figura za podminky, choise(n, p, q, s1); // ze pozice se zase rovna 1 } // (podle Oy, dale jen "pozice") } //================================================================================= //====================Zapis do pole displeje figury (jeden krok)=================== void step(void) { int v, w, i, j; v = 0; for (i = position; i < (position + p); i++) { w = 0; for (j = zz; j < (zz + q); j++) { if (a[i][j] == 32) a[i][j] = f[v][w]; // tato podminka je pro to, aby else a[i][j] = 79; // prazdny element figury "nesnedl" // nejaky zapsany element masivu "O" if (i == 1 && a[i + p][j] == 79) complete = 1; // podminka presmerujici na konec hry if (a[i + 1][j] == 79 && f[v][w] == 79) collis = 1; // podminka, presmerujici na zapis nove figury od zacatku masivu (pozice = 1) w++; } v++; } } //================================================================================= //====================Smazani figury v predchozim kroku============================ void erase(void) { int v,w; if (collis == 0) { // funkce se splnuje jen za podminky, ze jeste nedoslo ke kolizi v = 0; for (i = position; i < (position + p); i++) { w = 0; for (j = zz; j < (zz + q); j++) { if (f[v][w] == 79) a[i][j] = 32; // vymazani jen elementu "O" figury, w++; // aby se nevymazali jiz zapsane } // v predchozich krocich elementy "O" v++; } } } //================================================================================= //====================Funkce zobrazeni aktualniho kroku do displeje================ void display(void) { for (i = 0; i < l; i++) { for (j = 0; j < s; j++) { serial.printf("%c ", a[i][j]); // zobrazeni znaku ASCII } serial.printf("\r\n"); } serial.printf("SCORE: %d\r\n", score); wait(0.6); // zpozdeni pro zachyceni kroku na displeji serial.printf("\e[;H\e[0J"); // smazani kroku pro zobrazeni dalsiho } //================================================================================= //=============Funkce odstraneni kolize a prechod do zapisu dalsi figury=========== void collision(void) { int number, full, i, j; // full je nastaveno na "1", kdyz je cely radek plny if (collis == 1) { number = 1; // promenna cisla radku while (number < l - 1) { full = 0; for (j = 1; j < s - 1; j++) { if (a[number][j] == 79) full++; } if (full == s - 2) { // kdyz je full = s - 2, radek je plny, protoze dva ostatni prvky jsou hranice pole for (i = number; i >= 1; i--) { for (j = 1; j < s - 1; j++) { if (i != 1) a[i][j] = a[i - 1][j]; // posun masivu dolu else a[i][j] = 32; } } score += 100; // pocet skore for (i = 0; i < l; i++) { for (j = 0; j < s; j++) { serial.printf("%c ", a[i][j]); } serial.printf("\r\n"); } serial.printf("SCORE: %d\r\n", score); green = 1; blue = 0; wait(0.1); blue = 1; wait(0.1); blue = 0; wait(0.1); // kdyz se odstranue radek, trikrat po sebe se rozsviti modra LEDka blue = 1; wait(0.1); blue = 0; wait(0.1); blue = 1; wait(0.1); green = 0; serial.printf("\e[;H\e[0J"); } number++; } position = 0; // prechod do pocatecni pozice n = rand() % 13; // zase nahodny vyber figury } } //================================================================================= //============================Funkce konce hry==================================== void end(void) { int r; r = l - 2; // zase promenna cisla radku green = 1; blue = 1; red = 0; // kdyz je konec hry, rozsviti cervena LEDka while (r > 0) { for (i = 0; i < s; i++) { a[r][i] = 79; // klasicke zaplneni radku elementami "O" na konci hry } for (i = 0; i < l; i++) { for (j = 0; j < s; j++) { serial.printf("%c ", a[i][j]); } serial.printf("\r\n"); } wait(0.1); serial.printf("\e[;H\e[0J"); r--; } r = 1; while (r <= l - 2) { for (i = 1; i < s - 1; i++) { a[r][i] = 32; // pak - smazani vsech radku (zaplneni prazdnymi elementami) } for (i = 0; i < l; i++) { for (j = 0; j < s; j++) { serial.printf("%c ", a[i][j]); } serial.printf("\r\n"); } wait(0.1); if (r < l - 2) { serial.printf("\e[;H\e[0J"); } r++; } serial.printf("THE GAME IS OVER\r\n"); // nadpis signalizuje konec hry serial.printf("SCORE: %d\r\n", score); // vysledny pocet skore red = 1; // zhasnuti cervene LEDky } //================================================================================= //==================================Main=========================================== int main(void) { wait(1.5); // male zpozdeni, aby se okno programu PuTTY otevrelo driv, nez se zacne hra srand(time(0)); // tato funkce je pouzita, aby poslopnost figur nebyla vzdy stejna n = rand() % 13; // nahone cislo figury array(); // zapis displeje while(complete == 0) { // pokud neni masiv displeje plny, probiha hra upgrade(); // obnoveni promennych newFigure(); // zapis nove figury move(); // pohyb figury vlvo a vpravo rotate(); // rotace figury vlevo a vpravo zz = s1; // aktualizace pozice zapisu figury podle Ox step(); // aktualni krok hry (pohyb figury dolu) display(); // zobrazeni aktualniho kroku erase(); // vymazani kroku collision(); // signalizace kolize a prechod zase do pozice = 1 a zapis nove figury } end(); // organizace konce hry } //=================================================================================
Cílem tohoto individuálního projektu bylo realizovat hru Tetris, která pro zobrazení používá terminál na sériové lince, přičemž stav hry se indikuje pomocí LEDek. Program funguje správně, zdrojový kód nemá žádnou chybu nebo upozornění. Minus realizovaného programu je v tom, že hráč může vidět v okně PuTTY přechod od jednoho kroku hry k dalšímu. Ale celkem je možné říct, že vsechny úkoly tohoto projektu jsou splněny.