Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2015:2048-game

Hra 2048

Zadání

Realizujte hru 2048 na kitu FRDM-KL25Z. Jednotlivá čísla budou reprezentovány barvou, která bude zobrazena pomocí matice RGB LED. Pro řízení pohybu využijte akcelerometr.


Úvod

Vznik hry

Hra 2048 vznikla prakticky za jediný víkend. Devatenáctiletý mladík jménem Gabriele Cirulli naprogramoval tuto hru jako školní projekt. Z tohoto nápadu velmi rychle vznikl fenomén a podobně jako v případě Flappy Bird začaly hříčku 2048 během několika dnů hrát desítky milionů hráčů.

Popis hry

Cílem hry je spojit čísla dohromady (mocniny 2), s cílem dosáhnout konečného čísla '2048 ' na jednom čtverečku. Herní pole je mřížka 4×4 (16 čtverečků),ta je zobrazena na Obr.1. Na začátku hry jsou dva čtverce (také nazývané dlaždice) s číslem 2 uvnitř. Pokud se spojí dvě dlaždice se stejným číslem, splynou do nové dlaždice s dvojnásobným číslem, než je původní: 2+2 = 4, 4+4 = 8, … 1024+ 1024 = 2048! Chceme-li přesunout dlaždice na herním poli, stačí si vybrat směr (nahoru, dolů, vpravo nebo vlevo). Všechny dlaždice se přesunou zvoleným směrem - efekt sesypání k jedné stěně. V jednom kole se ale čísla mohou spojovat pouze jednou. Např. pokud sesypeme řádek 2 2 2 2 směrem doleva, výsledkem bude 4 4 0 0, nikoliv 8 0 0 0. Dalším pravidlo je, že se sesypou čísla nejvyšším číslem směrem ke stěně. Např. řádek 4 4 4 0 se změní na 8 4 0 0 nikoliv na 4 8 0 0. Po sesypání se objeví nové číslo 2 na některém z volných políček. Pokud je herní pole naplněno, hra končí.

V počítači stačí použít pouze 4 šipky na klávesnici. Na mobilním zařízení s dotykovým displejem přejedeme prstem požadovaným směrem.

Implementace pro RGB displej

Mým záměrem bylo implementovat tuto hru na RGB displej 8×8. Na něm ale nelze zobrazovat čísla, proto budou reprezentovány pomocí různých barev. Řízení směru pohybu je řízeno pomocí náklonu displeje (akcelerometru). Všechna pravidla zůstávají stejná jako u originální hry.

  Obr.1 Ukázka ze hry 2048

Hardware

RGB matice LED je zapojena podle Obr. 1 níže. Společným kontaktem pro každou RGB LED je anoda. Z toho vyplývá, že matice musí být spínána po řádcích tak, že je přivedeno kladné napětí na společnou anodu jednoho řádku a pomocí uzemnění jednotlivých katod jsou rozsvěcovány jednotlivé LED.

K řízení RGB displeje nestačil samotný MCU ze dvou důvodů: 1. má pouze 3,3V logiku, která nestačí k bezepčnému rozsvícení LED (zejména modré) 2. každý pin má omezený výstupní i vstupní proud na 4mA

Z toho důvodu bylo potřeba vytvořit HW převodník, který převede 3,3V logiku na 5V a současně výkonově posílí výstupní piny. Pro spínání napětí +5V bylo použito převodníku (high switch), který tvoří jeden NMOS a jeden PMOS. Pro spínání napětí 0V bylo použito tranzistorového pole ULN2803A. Kompletní zapojení převodníku je na Obr.2 níže.

  Obr.2 Schéma zapojení převodníku

Rozměr převodníku byl zvolen přesně podle rozměrů RGB matice LED a rozmístění součástek bylo voleno tak, aby se vešel nad kit FRDM-KL25Z. Deska byla vyráběna na ústavu UREL, proto nejsou použity klasické prokovy, místo nich je použito propojení drátkem. Převodník můžeme vidět na Obr.3 níže a soubory k jeho výrobě jsou k dispozici zde: prevodnik.rar

  Obr.3 Osazený převodník

Software

Program byl napsán ve vývojovém prostředí www.mbed.org. Celý kód je k dispozici v tomto odkazu: https://developer.mbed.org/users/xlizne01/code/2048/

Funkce main

Ve funkci main dochází v nekonečné smyčce ke čtení dat z akcelerometru, detekci pohybu a také ke zobrazování aktuálních dat na RGB matici. Při spuštění jsou také přidány první 2 kostky na herní pole pomocí funkce pridej().

int main() 
{      
    akcelerometr(); 
    pridej();   
    pridej();   //přidání prvních dvou kostek
 
    while(1) // nekonečná smyčka pro detekci pohybu a zobrazování aktuální matice
    { 
        akcelerometr();
        pohyb();
        zobrazeni();
    }
}

Funkce akcelerometr

Tato funkce slouží k výpočtu úhlu, kterým je deska nakloněna a pak pomocí rozhodovacích úrovní MOVE_ANGLE a NULL_ANGLE. Pokud se deska nakloní některým směrem v úhlu větším než MOVE_ANGLE, uloží se do proměnné naklon směr náklonu. Pokud je deska v klidové pozici vodorovně se zemí (o tom rozhoduje rozhodovací úroveň NULL_ANGLE) uloží se do proměnné naklon 0.

void akcelerometr()
{
    float ax, ay, az;
 
    ax = acc.getAccX();
    ay = acc.getAccY();
    az = acc.getAccZ();
 
    xAngle = atan( ax / (sqrt((ay)*(ay) + (az)*(az)))) * 60;
    yAngle = atan( ay / (sqrt((ax)*(ax) + (az)*(az)))) * 60;
 
    if((abs(xAngle)+abs(yAngle))<NULL_ANGLE)
    {
        naklon = '0';   // 0 nula
    }
 
    if(abs(xAngle) >= abs(yAngle))
    {
        if(xAngle >= MOVE_ANGLE)
        {                 
            naklon = 'U';   // +X up
        }
 
        if(xAngle <= -MOVE_ANGLE)
        {
            naklon = 'D';    // -X  down
        }   
    }
 
    else
    {      
        if(yAngle >= MOVE_ANGLE)
        {
            naklon = 'L';   // +Y left
        }
 
        if(yAngle <= -MOVE_ANGLE)
        {
            naklon = 'R';   // -Y right
        }   
    }   
} 

Funkce pohyb

Funkce ovládá pohyb desky pomocí proměnné naklon, v které je uložena aktuální poloha desky. Aby došlo při pohybu na jednu stranu jen k jednomu posunu, musí se deska vždy vrátit do nulové pozice. K tomu slouží pomocná proměnná p. Pokud dojde k pohybu desky, je zavolána funkce sesypej()

void pohyb()
{   
    switch(naklon)
    {
        case 'D': //pohyb smerem dolu
            if(p==0)
            {
                sesypej(1);
                p=1;
            }
            break;
 
        case 'R': // pohyb smerem vpravo
            if(p==0)
            {
                sesypej(2);
                p=1;
            }
            break;
 
        case 'U': // pohyb smerem nahoru
            if(p==0)
            {
                sesypej(3);
                p=1;
            }
            break;
 
        case 'L': // pohyb smerem vlevo
            if(p==0)
            {
                sesypej(4);
                p=1;
            }
            break;
 
        case '0': // detekovana nulova pozice
            p=0;
            break;
 
        default:
            break;  
    }
}

Funkce sesypej

Funkce sesypej je výpočetní funkcí, která počítá pohyb po herním poli a musí respektovat pravidla hry 2048, které jsou uvedeny v úvodu. Popis jednotlivých částí je uveden v kódu.

void sesypej(int x)
{
    int a,b,c,d; //pomocné proměnné pro uložení řádků/sloupců při sesypání 
    int same=0; // pomocná proměnná
 
    for(int k=0;k<4;k++)
    {
        for(int l=0;l<4;l++)
        {
            pole2[k][l]=pole[k][l]; //uložení původní matice do pomocné matice
        } 
    }
 
    for(int n=0;n<4;n++)
    {   
        if(x==1) // pohyb dolu
        {
            a=pole[n][0];
            b=pole[n][1];
            c=pole[n][2];
            d=pole[n][3];
        }
 
        if(x==2) // pohyb vpravo
        {
            a=pole[3][n];
            b=pole[2][n];
            c=pole[1][n];
            d=pole[0][n];
        }
 
        if(x==3) // pohyb nahoru
        {
            a=pole[n][3];
            b=pole[n][2];
            c=pole[n][1];
            d=pole[n][0];
        }
 
        if(x==4) // pohyb vlevo
        {
            a=pole[0][n];
            b=pole[1][n];
            c=pole[2][n];
            d=pole[3][n];
        }
 
            //sesypani
        if(a==0)
            {a=b; b=c; c=d; d=0;}
        if(a==0)
            {a=b; b=c; c=d; d=0;}
        if(a==0)
            {a=b; b=c; c=d; d=0;}
        if(b==0)
            {b=c; c=d; d=0;}
        if(b==0)
            {b=c; c=d; d=0;}
        if(c==0)
            {c=d; d=0;}
 
           //spojeni 
        if(a==b)
            {a=2*a; b=c; c=d; d=0;}
        if(b==c)
            {b=2*b; c=d; d=0;}
        if(c==d)
            {c=2*c; d=0;}
 
        if(x==1) // dolu
        {   
            pole[n][0]=a;
            pole[n][1]=b;
            pole[n][2]=c;
            pole[n][3]=d;
        }
 
        if(x==2) // vpravo
        {   
            pole[3][n]=a;
            pole[2][n]=b;
            pole[1][n]=c;
            pole[0][n]=d;
        }
 
        if(x==3) // nahoru
        {   
            pole[n][3]=a;
            pole[n][2]=b;
            pole[n][1]=c;
            pole[n][0]=d;
        }
 
        if(x==4) // vlevo
        {   
            pole[0][n]=a;
            pole[1][n]=b;
            pole[2][n]=c;
            pole[3][n]=d;
        }      
    }
 
    for(int q=0;q<4;q++)
    {
        for(int w=0;w<4;w++)
        {
            if (pole2[q][w]!=pole[q][w]) //kontrola, že došlo k nějaké změně v matici
            {
                same=1;
            }
        }
    }
 
    if(same==1)
    { 
        pridej();  
    } 
}

Funkce pridej

Funkce přidá na herní pole novou kostku. Dle pravidel hry je 10% šance že nebude přidáno číslo 2, ale bude přidáno číslo 4. Náhodné číslo je řízeno pomocí funkce rand() v kombinaci s daty z akcelerometru, aby byla funkce čistě náhodná.

void pridej()
{
    int g=1;    // pomocná proměnná
    int f;      // nahodne cislo v rozmezi 0-15 sloužící ke generování náhodnách souřadnic
 
    while(g==1)
    { 
        f =(rand()+int(xAngle)+int(yAngle))%16;
        if(pole[f/4][f%4]==0)
        {
            if((rand()+int(xAngle)+int(yAngle))%10==1) // reprezentuje 10% šanci, že přidá číslo 4
            {
                pole[f/4][f%4]=4;
                g=0;
            }
            else // přidání čísla 2
            {
                pole[f/4][f%4]=2;
                g=0;
            }
        }
    }
}

Funkce zobrazeni

Funkce řídí zobrazování dat na RGB matici, v původním návrhu mělo být použito PWM, ale později bylo zjištěno, že PWM nefungují nezávisle a z této možnosti sešlo. Proto bylo vytvořeno SW řízení svítivosti jednotlivých LED. Herní pole je rozděleno na 4 řádky a 4 sloupce a je tedy spínáno v 16 krocích. V jednom kroku je pak řízena svítivost čtyř RGB LED. Jakou barvou bude čtverec svítit, určí funkce barva().

void zobrazeni()
{
    int RledA, RledB, GledA, GledB, BledA, BledB;
 
    for(int k=0;k<4;k++)
    {  
        prvni=0;
        druhy=0;
        treti=0;
        ctvrty=0;
        paty=0;
        sesty=0;
        sedmy=0;
        osmy=0;
 
        wait(0.00005); //opatreni proti tzv. duchum
 
        if(k==0)
        {
            prvni=1;
            druhy=1;
        }
 
        if(k==1)
        {
            treti=1;
            ctvrty=1;
        }
 
        if(k==2)
        {
            paty=1;
            sesty=1;
        }
 
        if(k==3)
        {
            sedmy=1;
            osmy=1;
        }
 
        for(int j=0;j<4;j++)
        { 
            barva(j,k);
 
            for(int i=11;i>0;i--)
            {
                if(RED>0)
                {
                    RledA=1;
                    RledB=1;
                }
                else
                {
                    RledA=0;
                    RledB=0;
                }
 
                if(GREEN>0)
                {
                    GledA=1;
                    GledB=1;
                }
                else
                {
                    GledA=0;
                    GledB=0;
                }
 
                if(BLUE>0)
                {
                    BledA=1;
                    BledB=1;
                }
                else
                {
                    BledA=0;
                    BledB=0;
                }
 
                if(j==0)
                {
                    Rled1=RledA;
                    Rled2=RledB;
                    Gled1=GledA;
                    Gled2=GledB;
                    Bled1=BledA;
                    Bled2=BledB;
                }
 
                if(j==1)
                {
                    Rled3=RledA;
                    Rled4=RledB;
                    Gled3=GledA;
                    Gled4=GledB;
                    Bled3=BledA;
                    Bled4=BledB;
                }
 
                if(j==2)
                {
                    Rled5=RledA;
                    Rled6=RledB;
                    Gled5=GledA;
                    Gled6=GledB;
                    Bled5=BledA;
                    Bled6=BledB;
                }
 
                if(j==3)
                {
                    Rled7=RledA;
                    Rled8=RledB;
                    Gled7=GledA;
                    Gled8=GledB;
                    Bled7=BledA;
                    Bled8=BledB;
                } 
 
                wait(0.00001);
                RED--;
                BLUE--;
                GREEN--;  
            }    
        }
    } 
}

Funkce barva

Funkce barva slouží ke konverzi číselného zobrazení v herním poli na barvu. Např. pokud je v herním poli 0, nesvítí žádná LED, pokud je v poli číslo 2, tak svítí matně červená, pro číslo 4 svítí jasně červená, pro číslo 8 svítí matně modrá, a tak dále, až po číslo 2048, kde svítí jasně všechny 3 LED, což je cílem hry.

void barva(int k , int l)
{
    if(pole[k][l]==0)
    {
        RED=0;
        GREEN=0;
        BLUE=0;
    }
 
    if(pole[k][l]==2)
    {
        RED=1;
        GREEN=0;
        BLUE=0;
    }
 
    if(pole[k][l]==4)
    {
        RED=10;
        GREEN=0;
        BLUE=0;
    }
 
    if(pole[k][l]==8)
    {
        RED=0;
        GREEN=0;
        BLUE=2;
    }
 
    if(pole[k][l]==16)
    {
        RED=0;
        GREEN=0;
        BLUE=10;
    }
 
    if(pole[k][l]==32)
    {
        RED=0;
        GREEN=2;
        BLUE=0;
    }
 
    if(pole[k][l]==64)
    {
        RED=0;
        GREEN=10;
        BLUE=0;
    }
 
    if(pole[k][l]==128)
    {
        RED=0;
        GREEN=10;
        BLUE=10;
    }
 
    if(pole[k][l]==256)
    {
        RED=10;
        GREEN=0;
        BLUE=10;
    }
 
    if(pole[k][l]==512)
    {
        RED=10;
        GREEN=10;
        BLUE=0;
    }
 
    if(pole[k][l]==1024)
    {
        RED=2;
        GREEN=2;
        BLUE=2;
    }
 
    if(pole[k][l]==2048)
    {
        RED=10;
        GREEN=10;
        BLUE=10;
    } 
 
    RED=RED*BRIGHTNESS/10;
    GREEN=GREEN*BRIGHTNESS/10;
    BLUE=BLUE*BRIGHTNESS/10;  
}

Video demostrace hry 2048

Ve videu můžeme vidět krátkou ukázku ze hry. Vidíme, že funguje pohyb různýmy směry, správně funguje sesypání i spojování, podle logiky hry 2048. Bohužel je také vidět, že nesvítí jedna červená LED, která byla odpálena při HW testování RGB matice. Funkčnost hry to ale téměř nesnižuje, protože svítí ještě okolní 3 body a lze jasně poznat o jakou barvu se jedná.


Závěr

Cílem projektu bylo realizovat hru 2048 s pomocí kitu FRFM-25Z a RGB matice LED. Řízení pohybu mělo být provedeno pomocí akcelerometru. Všechny body zadání se podařilo splnit a hra je plně funkční. Největším nedostatkem je podle mě nedostatečně kvalitní RGB matice, která nezobrazuje úplně věrohodně jednotlivě barvy a jsou stále vidět jednotlivé LED uvnitř jedné RGB LED. Tento nedostatek byl částečně vyřešen přidáním rozptylovací fólie z LCD monitoru shora na matici. U hry se neprojevují žádné chyby a je dobře hratelná. Já osobně jsem strávil jejím hraním dost času a podařilo se mi dosáhnout i čísla 2048, tedy jasně bílé barvy :)

2015/2048-game.txt · Poslední úprava: 2016/01/15 15:09 autor: Václav Lízner