Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2015:putty-tetris

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.

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.

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 <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
}
//=================================================================================

Video demostrace hry Tetris


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.

2015/putty-tetris.txt · Poslední úprava: 2015/12/28 15:17 autor: Egor Dulesov