====== Tetris přes terminál ======
===== Zadání =====
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.
----
===== Úvod =====
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.
{{ :2015:300-tetris.png?direct&200 |}}
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.
----
===== Hardware =====
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í:
* 48MHz, 16KB RAM, 128KB FLASH
* USB (Host/Device), USB serial port
* Rozhraní SPI, I2C, UART
* Capacitive touch sensor
* MMA8451Q - 3-axis accelerometer atd.
Zapojení desky je uvedeno na obrázku císlo 2.
{{ :2015:xfrdm.png?direct |}}
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.
----
===== Software =====
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č).
----
===== Realizace programu hry pomocí jazyka C++ =====
==== Hlavní funkce ====
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í:
* **array()** je funkcí zápisu okna hry, ve kterém se pak pohybují prvky;
* **Upgrade()** má jednoduchý smysl - obnovuje hodnoty některých proměnných, což je důležitým pro korektní práci programu;
* **newFigure()** zapisuje do okna hry nový prvek (figuru), když prvek došel do konce okna nebo do nepradného elementu. newFigure taky odsahuje funkci **choise()**, která zapisuje do okna náhodně vybraný prvek;
* **move()** zajišťuje pohyb prvků vpravo a vlevo. Smyslem této funkce je přepočet pozice //s1// podle //Ox//, ze které se zapisuje prvek. Pohyb se realizuje pomocí rotace desky doleva a doprava (mění se jen souřadnice //y//), viz. video;
* **rotate()** má odpovědnost za rotaci prvků doprava a doleva. Smyslem funkce je změna řádového čísla prvku //n// na jiné přislušné, respektive jeho přezapis. Rotace se realizuje pomocí rotace desky směrem od sebe nebo na sebe (mění se pouze souřadnice //x//);
* **step()** - táto funkce je aktuálním zapisem prvku do masivu okna programu, tj. simulace pohybu prvků dolů;
* **display()** zobrazuje aktuální krok do konzoli;
* **erase()** vymazá prvek pro zobrazení dalšího kroku hry;
* **collision()** je funkce, která říká o tom, že došlo ke kolizí prvků a je nutné zapsat další prvek do počateční pozice;
* **end()** zobrazuje klasický konec hry (nejdříj zaplnění okna neprazdnými prvky, pak jejich vymazání).
==== Knihovny ====
Knihovny použité ve zdrojovém kódu jsou následující:
* **stdio.h** - standardní knihovna jazyka C;
* **stdlib.h, time.h** - knihovny pro práci s funkcemi nahodných čísel //rand(), srand()//;
* **mbed.h** - speciální knihovna mbed;
* **MMA8451Q.h** - knihovna pro akcelerometr;
* **USBSerial.h** - knihovna pro sériové rozhraní.
Stav hry se indikuje pomocí LEDek:
* zelená LEDka označuje obecný normální stav hry;
* modrá svití trikrát po sebe v případě, když došlo k odstranění plného řádku;
* červená svití, když došlo ke konci hry.
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 // 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 // knihovna pro generaci preudonahodnych cisel
#include // 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
}
//=================================================================================
----
===== Video demostrace hry Tetris =====
{{youtube>mkZBd6H2NT4?medium}}
----
===== Závěr =====
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.