Close

Arduino projekt: 2048

Herní plocha hry 2048 na TFT displeji

V dnešním díle se více než na teorii zaměříme na použití Arduina v praxi. Využijeme TFT shield popsaný v tomto článku, předvedeme si, jak pracovat s SD kartou a nakonec si naprogramujeme hru 2048.

SD karta

Arduino IDE obsahuje již od základu knihovnu pro obsluhu SD karet. Ta nám umožňuje pracovat s kartami, která mají formáty souborového systému FAT16 a FAT32. Název souboru nesmí mít více než 8 znaků a přípona musí mít znaky tři. Pokud má karta, se kterou chceme pracovat, jiný, než požadovaný typ, musíme ji před použitím zformátovat. To se ve Windows 7 udělá velmi jednoduše. Kartu vložíme do čtečky a otevřeme Počítač. Pravým tlačítkem myši otevřeme nabídku ikony karty, kterou chceme formátovat a vybereme možnost Formátovat. Po otevření dialogového okna pro formátování vybereme z nabídky Systém souborů možnost FAT, nebo FAT32. Také odškrtneme možnost Rychlé formátování (není potřeba vždy, ale máme jistotu, že se karta opravdu správně naformátuje).

Formátovací dialog Windows

Formátovací dialog

Příprava Arduina

Aby mohlo Arduino s SD vůbec komunikovat, musíme mít k němu připojenou vhodnou čtečku. Taková čtečka nemusí být vůbec drahá ani náročná na výrobu. Udělat se dá třeba jen i s pár piny (jak popisuje tento návod). Také existuje celá řada elektronických modulů nebo shieldů, které SD čtečku obsahují. Jsou jimi například Modul čtečka SD karetSD Card Shield V4.0, Arduino Ethernet Shield a další. V tomto článku si předvedeme, jak použít micro SD čtečku, kterou obsahuje TFT touch shield popsaný v minulém článku. Čtečku nalezneme na spodní straně shieldu poblíž jednoho z rohů.

Spodní strana TFT shieldu pro Arduino se slotem na SD

TFT shield

Funkce

Arduino komunikuje se čtečkou přes SPI rozhraní (piny MISO, MOSI, SCK, SS). Tyto piny jsou u každého Arduina jinde (u verze Mega je nalezneme na 50, 51, 52, 53 u Uno jim pak odpovídají 12, 11, 13, 10 atd.). Bližší informace nalezneme v dokumentaci jednotlivých desek. I když pin SS nepoužíváme, musí být nastavený jako OUTPUT (Uno: 10, Mega 53…). Pro práci s SD potřebujeme knihovnu SD.h, kterou do kódu vložíme známým příkazem #include <SD.h>.

Zasuneme kartu, připojíme shield a můžeme programovat. Funkce obsažené v knihovně se dají rozdělit do dvou skupin. První skupina funkcí slouží k práci s umístěním souborů a složek. Druhá skupina umí měnit samotný obsah souborů. Přehled funkcí nalezneme v dokumentaci knihovny. My si představíme ty nejužitečnější.

Funkce pro práci s umístěním složek a souborů
Název Funkce
SD.begin(pin) Inicializuje SD kartu. Při úspěchu vrátí true, jinak false. Parametr pin slouží k nastavení linky pro výběr čipu. Nejčastěji má hodnotu 4.
SD.exists(jmeno) Pokud zadaná složka, nebo soubor existuje, vrátí true, jinak false. Parametr jmeno může obsahovat i cestu k souboru (např. SLOZKA1/SLOZKA2/SOUBOR.TXT).
SD.mkdir(jmeno) Vytvoří zadanou složku. Pokud má proměnná jmeno formát například „sl1/sl2/a“, vytvoří se i nadřazené složky (pokud neexistují).
SD.open(jmeno, mod) Otevře vybraný soubor (jmeno se řídí stejnými pravidly jako u SD.exists()). Parametr mod je nepovinný a je defaultně nastavený na FILE_READ. V tomto stavu je soubor otevřený pouze ke čtení a kurzor pozice je umístěn na začátku. Pokud nastavíme mod na FILE_WRITE, otevře se soubor i pro zápis s kurzorem pozice na jeho konci. Pokud soubor neexistuje, funkce ho vytvoří. Všechny nadřazené složky však musí existovat.Tato funkce je důležitá tím, že vrací objekt obsahující informace o otevřeném souboru, se kterým poté pracuje druhá skupina funkcí.
SD.remove(jmeno) Odstraní vybraný soubor (ne složku!).
SD.rmdir(jmeno) Odstraní vybranou složku – složka však musí být prázdná.

Funkce SD.open() vrací objekt souboru, se kterým pracujeme. Ten je datového typu File. Následující funkce jsou tedy pro práci s objektem soubor získaným takto:

File soubor = SD.open("abc.txt", FILE_WRITE);
Funkce pro práci s obsahem souboru
Název Funkce
soubor.available() Funguje stejně jako u sériové komunikace – vrátí počet nepřečtených bytů v souboru.
soubor.close() Zavře aktivní soubor.
soubor.read() Přečte jeden byte ze souboru.
soubor.write(data) Zapíše jeden byte do souboru.
soubor.print(data) Zapíše data do souboru jako text (ASCII kódování). Čísla rozdělí na jednotlivé číslice a ty poté zapíše jednotlivě.
soubor.println(data) Funguje stejně jako .print(), jen na konec informace přidá zalomení řádku a návrat posuvníku.

Na ukázku si dovolím použít příklady z dokumentace.

Příklad 1.: Zápis hodnot

Tento příklad měří hodnoty na A0, A1 a A3 a zapisuje je do složky na SD kartě.

#include <SD.h> //vložení knihovny

const int chipSelect = 4; //výběr pinu pro SD.begin()

void setup(){
	Serial.begin(9600);
	while (!Serial) {
		; //počká na zahájení komunikace - pouze pro desky s AT32u4
	}


	Serial.print("Initializing SD card...");
	pinMode(10, OUTPUT); //SS pin desky se kterou pracujeme

	//je karta přítomna a v pořádku?
	if (!SD.begin(chipSelect)) {
		Serial.println("Card failed, or not present");
		return;
	}
	Serial.println("card initialized.");
}

void loop(){
	//vytvoří řetězec pro shromažďování dat
	String dataString = "";

	//přečti 3 vstupy a výsledek zapiš do řetězce
	for (int analogPin = 0; analogPin < 3; analogPin++) {
		int sensor = analogRead(analogPin);
		dataString += String(sensor);
		if (analogPin < 2) {
			dataString += ","; 
		}
	}

	//otevře soubor
	File dataFile = SD.open("datalog.txt", FILE_WRITE);

	// pokud se otevření povedlo, data se zapíší do složky
	if (dataFile) {
		dataFile.println(dataString);
		dataFile.close();
		Serial.println(dataString);
	}  
	else {
		Serial.println("error opening datalog.txt");
	} 
}

Příklad 2.: Výpis dat ze souboru

V druhém příkladu přečteme data uložená na SD v předchozí části a vypíšeme je po sériové lince.

#include <SD.h>

const int chipSelect = 4;

void setup(){
	Serial.begin(9600);
	while (!Serial) {
		;
	}

	Serial.print("Initializing SD card...");
	pinMode(10, OUTPUT);
	  
	if (!SD.begin(chipSelect)) {
		Serial.println("Card failed, or not present");
		return;
	}
	Serial.println("card initialized.");

	File dataFile = SD.open("datalog.txt");

	if (dataFile) {
		while (dataFile.available()) {
			Serial.write(dataFile.read());
		}
		dataFile.close();
	}  
	else {
		Serial.println("error opening datalog.txt");
	} 
}

void loop(){}

Tímto jsme si v rychlosti předvedli práci s SD kartou a můžeme se pustit do programování hry.

Hra 2048

V březnu roku 2014 zveřejnil na svém webu devatenáctiletý italský vývojář hru 2048. To rázem strhlo lavinu jejích klonů. A nešlo jen o verze pro prohlížeče, ale i všemožné platformy a programovací jazyky. Základní princip je jednoduchý. Na poli 4×4 máme dlaždice s hodnotami mocnin 2. Pohybem do čtyř stran můžeme dlaždice se stejnou hodnotou sečíst. Výsledkem sečtení je dlaždice o hodnotě součtu dvou předchozích (a nebo mocnina dvou s o jedno vyšším exponentem). V našem případě využijeme TFT dotykový displej pro zobrazení a ovládání a čtečku karet pro uložení postupu.

Hodnoty

Z praktických důvodů nebudeme pracovat s mocninami dvou, ale pouze s exponenty (mocniny by se na displej špatně vešly). Základní dlaždice bude mít tedy hodnotu 1. Při součtu dvou stejných dlaždic vznikne jedna dlaždice s o jedna větší hodnotou. Tímto způsobem lze na poli 4×4 dosáhnout maximální hodnoty 17. Princip zjištění maximální hodnoty je vidět na obrázku.

Maximální hodnoty hry 2048

Maximální hodnoty

V programu budeme mít uloženy hodnoty jednotlivých dlaždic v poli plocha[y][x]. Pokud bude mít prvek hodnotu nula, bude se chovat, jak by tam žádná dlaždice nebyla. Další hodnoty, kterých může nabýt jsou 1 až 17.

Jdeme na to

Nyní si celý program rozebereme část po části.

Na začátek si musíme vložit všechny potřebné knihovny a nadefinovat používané proměnné.

#include <stdint.h>
#include <SeeedTouchScreen.h>
#include <TFTv2.h>
#include <SPI.h>
#include <SD.h>
 
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // mega
#define YP A2
#define XM A1
#define YM 54
#define XP 57
 
#elif defined(__AVR_ATmega32U4__) //leonardo
#define YP A2
#define XM A1
#define YM 18
#define XP 21
 
#else
#define YP A2
#define XM A1
#define YM 14
#define XP 17
 
#endif
 
//konfigurace
 
TouchScreen ts = TouchScreen(XP, YP, XM, YM);
byte SDpin = 4; 
 
int min_x = 232;
int max_x = 1780;
int min_y = 166;
int max_y = 1826;

Point p; 

//proměnné používané ve hře

//0 - úvodní obrazovka, 1 - normální hra, 2 - prohra, 3 - výhra
byte stat = 0; 

//pole pro uložení hodnot jednotlivých dlaždic plochy
byte plocha[4][4]; 

uint16_t barvy[18]; //pole s hodnotami barev

boolean rewrite_all = true; //přepis celé obrazovky
boolean rewrite_game; //přepis herní plochy
boolean moved; //mlelo se s dlaždicemi?
boolean gEnd; //konec hry?
boolean gWin; //výhra?

//pomocné proměnné
byte added = 0; //kolikrát došlo k sečtení dlaždic
byte max_added;
byte len;

File soubor;

V dalším kroku nastavíme vše podstatné. Inicializujeme SD kartu a displej, nastavíme SS pin na OUTPUT (10 pro UNO, 53 pro MEGA…). Dále si připravíme do pole barvy[] paletu barev. Barvy si můžeme zvolit libovolně. V našem případě jsou rovnoměrně rozložené po celé škále.

void setup(){
    //inicializace SD karty a displeje
    Tft.TFTinit();
    SD.begin(SDpin);
    pinMode(53, OUTPUT);
    
    //přiřazení hodnot barvám
    barvy[0] = GRAY1;
    for(int b = 1; b < 18; b++){
        barvy[b] = b * (65535 / 18);
    }
    
    randomSeed(analogRead(0)); //seed pro náhodný generátor
}

Vše máme nastaveno. Nyní už se budeme zabývat blokem loop(). To, v jaké části zrovna hra je, je uloženo v proměnné stat (0 – úvodní obrazovka, 1 – normální hra, 2 – prohra, 3 – výhra). Pokud je proměnná rewrite_all true, dojde k přepsání celé obrazovky. Na úvodní obrazovce nalezneme dvě tlačítka: LOAD a NEW. V této části tedy také ověříme, jestli uživatel na některé z nich zmáčkl. Pokud dojde ke zmáčknutí tlačítka LOAD, nahraje se uložená hra z SD. V našem případě jsou na kartě informace o dlaždicích uloženy jako jeden byte pro jednu dlaždici. Tyto byty jsou uloženy bez mezer na jednom řádku. Při zmáčknutí tlačítka NEW se všechny dlaždice nastaví na 0 a poté se pomocí námi definované funkce addTile() (bude přidána na konci) přidají dvě dlaždice o hodnotě 1, nebo 2. Funkce checkP() aktualizuje bod p.

void loop(){    
    if(rewrite_all){
        Tft.fillRectangle(0,0,239,319, barvy[0]);
    }
    
    //úvodní obrazovka
    if(stat == 0){
        if(rewrite_all){
            Tft.fillRectangle(10,10,220,145, barvy[2]);
            Tft.fillRectangle(10,165,220,145, barvy[2]);      
            Tft.drawString("LOAD", 35, 55, 7, BLACK);
            Tft.drawString("NEW", 55, 210, 7, BLACK);  
            
            rewrite_all = false;
        }
        
        checkP();
        
        if(p.z > 10){
            //tlačítko LOAD
            if(p.y < 160){
                rewrite_all = true;
                rewrite_game = true;
                
                if(!SD.exists("G2048.TXT")){
                    stat = 0; 
                    Tft.fillRectangle(20,95,200,100, barvy[17]);
                    Tft.drawString("ERROR", 28, 123, 6, BLACK); 
					
					delay(500);
                }
                else{
                    stat = 1;
                    soubor = SD.open("G2048.TXT");
                    int i = 0;
                    byte p;
                    
                    for(int i=0; i < 4; i++){ //vyčistíme plochu
                        for(int j = 0; j < 4; j++){
                            plocha[i][j] = 0;
                        }
                    }
                    
                    while(soubor.available()){
                        p = soubor.read();
                        plocha[(i - i%4) /4][i % 4] = p;
                        i++;               
                    }
                    soubor.close();
                }
            }
            
            //tlačítko NEW
            else if(p.y >= 160){
                rewrite_all = true;
                rewrite_game = true;
                moved = false;
                added = 0;
                gEnd = false;
                gWin = false;
                stat = 1; //pokračovat budeme hrou
                
				//celá plocha se nastaví na 0
                for(int i = 0; i < 4; i++){
                    for(int j = 0; j < 4; j++){
                        plocha[i][j] = 0;
                    }
                }
                
                addTile();
                addTile();
            }
        }
    }

Pokračovat budeme případem normální hry. Pokud je proměnná rewrite_game true, dojde k přepsání herní plochy. Dále program detekuje, jestli došlo k dotyku v oblasti herní plochy a tlačítek. Ovládání směru je nastaveno tak, jak vidíte na obrázku. Detekce dotyku v oblasti směrových šipek vychází z funkce y = x, jejímž grafem je přímka, která svírá 45° s oběma osami.

Také zkontrolujeme, jestli jsou možné další tahy. Pokud ano, musí být buďto alespoň jedna dlaždice 0, nebo musí být alespoň jedna dvojice dlaždic se stejnou hodnotou. Výhru poznáme tak, že se na poli vyskytne dlaždice o hodnotě 17.

Pokud se během hry pohnulo s bloky (tah byl úspěšný), přidáme pomocí funkce addTile() novou dlaždici. Také nadefinujeme tlačítka pro uložení a návrat do hlavního menu.

Segmenty displeje hry 2048

Segmenty displeje

Herní plocha hry 2048 na TFT displeji

Herní plocha

    //normální hra
    else if(stat == 1){
        if(rewrite_all){
            rewrite_all = false;
            
            Tft.fillRectangle(10,240,105,70,barvy[10]);
            Tft.fillRectangle(125,240,105,70,barvy[10]);
             
            Tft.drawString("SAVE", 27, 263, 3, BLACK); 
            Tft.drawString("MAIN", 142, 250, 3, BLACK);
            Tft.drawString("MENU", 142, 278, 3, BLACK);
        }
        if(rewrite_game){
            Tft.fillRectangle(10,10,220,220,BLACK);
            
            //zobrazení jedotlivých dlaždic
            for(int x = 0; x < 4; x++){
                for(int y = 0; y < 4; y++){
                    Tft.fillRectangle(15+(x*54),15+(y*54), 49, 49, barvy[plocha[y][x]]);
                    if(plocha[y][x] != 0){
                        if(plocha[y][x] >= 10){
                            len = 0;  
                        }
                        else{
                            len = 10;
                        }  
                        
                        Tft.drawNumber(plocha[y][x], 19+(x*54)+len, 29+(y*54), 3, BLACK);
                    }
                }
            }
            rewrite_game = false;
        } 
        
        checkP();
        
        if(p.z > 10){
            
            //detekce dotyku v oblasti herní  plochy
            if(p.x > 10 && p.x < 220 && p.y > 10 && p.y < 230){
                //směr nahoru
                if(p.x > p.y && p.x < -1*p.y + 240){
                    goUp();
                }
                
                //směr dolů
                else if(p.x < p.y && p.x > -1*p.y + 240){
                    goDown();
                }
                
                //směr doprava
                else if(p.x > p.y && p.x > -1*p.y + 240){
                    goRight();
                }
                
                //směr doleva
                else if(p.x < p.y && p.x < -1*p.y + 240){
                    goLeft();
                }
                
                do{
                    checkP();
                    delay(10);
                }while(p.z > 10);
                
                //kontrola konce hry
                gEnd = true; //nastavíme na true a pokud nebude pravda, v následujícím cyklu to změníme
                for(int x = 0; x <= 3; x++){
                    for(int y = 0; y <= 3; y++){
                        if(plocha[y][x] == 0){
                            gEnd = false;
                        }
                        else if(x == 3 && y == 3){
                            //nic   
                            //u dolní pravé dlaždice nic nekontrolujeme  
                        }
                        else if(x == 3){
                            if(plocha[y][3] == plocha[y+1][3] ){
                                gEnd = false;
                            }    
                        }
                        else if(y == 3){
                            if(plocha[3][x] == plocha[3][x+1]){
                                gEnd = false;
                            }
                        }
                        else{
                            //zbylá část pole
                            if(plocha[y][x] == plocha[y][x+1] || plocha[y][x] == plocha[y+1][x]){
                                gEnd = false;
                            }
                        }
                    } 
                }
                
                //kontrola výhry
                gWin = false;
                for(int x = 0; x <= 3; x++){
                    for(int y = 0; y <= 3; y++){
                        if(plocha[y][x] == 17){
                            gWin = true;    
                        }
                    }
                }
                
                //pokud se hnulo s bloky 
                if(moved){
                    moved = false;
                    rewrite_game = true;
                       
                    if(gEnd == false){
                        addTile();
                    }             
                }
                
                //rozhodnutí dalšího postupu
                if(gEnd == true){
                    gEnd = false;
                    
                    stat = 2;
                    
                    rewrite_all = true;
                    rewrite_game = true;
                }  
                else if(gWin == true){
                    gWin = false;
                    
                    stat = 3;
                    
                    rewrite_all = true;
                    rewrite_game = true;
                }
                
                rewrite_game = true;    
            }
            
            //dolní část herní plochy s tlačítky
            else if(p.y > 240 && p.y < 310){
                
                //tlačítko SAVE
                if(p.x > 10 && p.x < 115){
                    Tft.fillRectangle(20,95,200,100, barvy[17]);
                    
                    //ukládání
                    if(SD.exists("G2048.TXT")){
                        SD.remove("G2048.TXT"); //vyčistíme případný starý soubor
                    }
                    soubor = SD.open("G2048.TXT", FILE_WRITE);
                        
                    //pokud se otevření povedlo, zapíšeme hodnoty
                    if(soubor){
                        for(int y = 0; y <=3; y++){
                            for(int x = 0; x <= 3; x++){
                                soubor.write(plocha[y][x]);   
                            }
                        }
                        soubor.close();
                        Tft.drawString("SAVED", 28, 123, 6, BLACK);
                    }
                    else{
                        Tft.drawString("ERROR", 28, 123, 6, BLACK);
                    }
                        
                    delay(1000);
                    
                    rewrite_all = true;
                    rewrite_game = true;
                } 
                
                //tlačítko MAIN MENU
                else if(p.x > 125 && p.x < 210){
                    stat = 0;                    
                    rewrite_all = true;           
                }              
            }
        }
    }

Obrazovky pro výhru a prohru jsou prakticky stejné, liší se pouze v nápisu. V dolní části mají tlačítko MAIN MENU.

    //prohra
    else if(stat == 2){
        if(rewrite_all){
            Tft.fillRectangle(10,10,220,300,barvy[2]);
            Tft.fillRectangle(20,200,200,100, barvy[10]);
            
            Tft.drawString("GAME", 32, 35, 7, BLACK);
            Tft.drawString("OVER", 32, 115, 7, BLACK);
            Tft.drawString("MAIN MENU", 37, 238, 3, BLACK);
            
            rewrite_all = false;
        }
        
        checkP();
        
        //tlačítko MAIN MENU
        if(p.z > 10){
            if(p.x > 20 && p.x < 220 && p.y > 200 && p.y < 320){
                rewrite_all = true;
                stat = 0;
            }
        }
    }
    
    //výhra
    if(stat == 3){
        if(rewrite_all){
            Tft.fillRectangle(10,10,220,300,barvy[2]);
            Tft.fillRectangle(20,200,200,100, barvy[10]);
            
            Tft.drawString("YOU", 50, 35, 7, BLACK);
            Tft.drawString("WON", 50, 115, 7, BLACK);
            Tft.drawString("MAIN MENU", 37, 238, 3, BLACK);
            
            rewrite_all = false;
        }
        
        checkP();
        
        //tlačítko MAIN MENU
        if(p.z > 10){
            if(p.x > 20 && p.x < 220 && p.y > 200 && p.y < 320){
                rewrite_all = true;
                stat = 0;
            }
        }       
    }
}

Nyní ještě musíme nadefinovat použité funkce. Funkce checkP() aktualizuje pozici dotyku (bod p). Funkce addTile() přidá dlaždici o hodnotě 1, nebo 2 na volné místo.

//nastaví aktuální pozici dotyku uživatele
void checkP(){ 
    p = ts.getPoint();
    p.x = map(p.x, min_x, max_x, 0, 240);
    p.y = map(p.y, min_y, max_y, 0, 320);
}

//přidá novou dlaždici
void addTile(){ 
    byte r1, r2;
    
    do{
        r1 = random(4);
        r2 = random(4);      
    }while(plocha[r1][r2] != 0);
                
    plocha[r1][r2] = random(1,3);    
}

void goUp(){
    for(int x = 0; x <= 3; x++){
        if(plocha[0][x] == plocha[1][x] && plocha[2][x] == plocha[3][x]){
            max_added = 2;       
        }
        else{
            max_added = 1;
        }
                        
        for(int y = 0; y <= 2; y++){
            for(int y_p = y; y_p >= 0; y_p--){
                if(plocha[y_p][x] == plocha[y_p+1][x] && plocha[y_p][x] != 0 && added < max_added){
                    plocha[y_p][x]++;
                    plocha[y_p+1][x] = 0;
                    added++;
                    moved = true;
                }
                if(plocha[y_p][x] == 0 && plocha[y_p+1][x] != 0){
                    plocha[y_p][x] = plocha[y_p+1][x];
                    plocha[y_p+1][x] = 0;
                    moved = true;
                }
            }     
        }
        added = 0;    
    }
}	

V poslední části vytvoříme funkce pro pohyb dlaždic do stran. To je logicky asi nejsložitější část programu. My si ji vysvětlíme na pohybu doprava – ostatní pohyby jsou pouze modifikací.

Při zmáčknutí tlačítka doprava dojde ke kontrole dlaždic v řádku, pokud plocha[y][0] == plocha[y][1] a zároveň plocha[y][2] == plocha[y][3] (například pro {2,2,3,3}, nebo {3,3,3,3}), může dojít k sečtení dvakrát. V ostatních případech pouze jednou. Kdybychom tuto část vynechali, docházelo by ke špatnému sčítání – {1,1,2,0} by při pohybu doprava skončilo takto: {0,0,0,3}. Námi požadovaný stav je však {0,0,2,2,}. Poté pokračujeme kontrolou sousedících dvojic a přesunem hodnot jiných než 0 na prázdné dlaždice (s hodnotou 0).

void goUp(){
    for(int x = 0; x <= 3; x++){
        if(plocha[0][x] == plocha[1][x] && plocha[2][x] == plocha[3][x]){
            max_added = 2;       
        }
        else{
            max_added = 1;
        }
                        
        for(int y = 0; y <= 2; y++){
            for(int y_p = y; y_p >= 0; y_p--){
                if(plocha[y_p][x] == plocha[y_p+1][x] && plocha[y_p][x] != 0 && added < max_added){
                    plocha[y_p][x]++;
                    plocha[y_p+1][x] = 0;
                    added++;
                    moved = true;
                }
                if(plocha[y_p][x] == 0 && plocha[y_p+1][x] != 0){
                    plocha[y_p][x] = plocha[y_p+1][x];
                    plocha[y_p+1][x] = 0;
                    moved = true;
                }
            }     
        }
        added = 0;    
    }
}

void goDown(){
    for(int x = 0; x <= 3; x++){
        if(plocha[0][x] == plocha[1][x] && plocha[2][x] == plocha[3][x]){
            max_added = 2;       
        }
        else{
            max_added = 1;
        }
                            
        for(int y = 3; y >= 1; y--){
            for(int y_p = y; y_p <= 3; y_p++){
                if(plocha[y_p-1][x] == plocha[y_p][x] && plocha[y_p][x] != 0 && added < max_added){
                    plocha[y_p-1][x]++;
                    plocha[y_p][x] = 0; 
                    added++;
                    moved = true;
                }
                if(plocha[y_p][x] == 0 && plocha[y_p-1][x] != 0){
                    plocha[y_p][x] = plocha[y_p-1][x];
                    plocha[y_p-1][x] = 0;
                    moved = true;
                }
            }    
        }
        added = 0;     
    }
}

void goRight(){
    for(int y = 0; y <= 3; y++){
        if(plocha[y][0] == plocha[y][1] && plocha[y][2] == plocha[y][3]){
            max_added = 2;       
        }
        else{
            max_added = 1;
        }
                            
        for(int x = 3; x >= 1; x--){
            for(int x_p = x; x_p <= 3; x_p++){
                if(plocha[y][x_p-1] == plocha[y][x_p] && plocha[y][x_p] != 0 && added < max_added){
                    plocha[y][x_p-1]++;
                    plocha[y][x_p] = 0;
                    added++;
                    moved = true;
                }
                if(plocha[y][x_p] == 0 && plocha[y][x_p-1] != 0){
                    plocha[y][x_p] = plocha[y][x_p-1];
                    plocha[y][x_p-1] = 0;
                    moved = true;
                }
            }    
        }
        added = 0;     
    }
}

void goLeft(){
    for(int y = 0; y <= 3; y++){
        if(plocha[y][0] == plocha[y][1] && plocha[y][2] == plocha[y][3]){
            max_added = 2;       
        }
        else{
            max_added = 1;
        }
                            
        for(int x = 0; x <= 2; x++){
            for(int x_p = x; x_p >= 0; x_p--){
                if(plocha[y][x_p] == plocha[y][x_p+1] && plocha[y][x_p] != 0 && added < max_added){
                    plocha[y][x_p]++;
                    plocha[y][x_p+1] = 0;  
                    added++;
                    moved = true;
                }
                if(plocha[y][x_p] == 0 && plocha[y][x_p+1]){
                    plocha[y][x_p] = plocha[y][x_p+1];
                    plocha[y][x_p+1] = 0;
                    moved = true;
                }
            }     
        }
        added = 0;     
    }     
}	

Celý program je možné stáhnout ze stránky github.

V případě jakýchkoliv dotazů či nejasností se na mě neváhejte obrátit v komentářích.

Zbyšek Voda

3 Comments on “Arduino projekt: 2048

Martin Skála
14.10.2015 at 19:34

Dobry den,

proc je prosim maximalni hodnota 17? Nema byt 16? V obrazku „Maximalni hodnoty“ chybi cislo 11 🙂

Zbyšek Voda
14.10.2015 at 20:05

Dobrý den, děkuji za nahlášení chyby. Obrázek je špatně, ale maximální hodnota je opravdu 17 🙂

Vycházím z předpokladu, že se do hry automaticky přidávají 2 na 1 a 2 na 2. Abych mohl vytvořit 2 na n, musím mít na ploše dostupné všechny nižší exponenty. V nejoptimálnějším případě se tedy dostanu do situace s následujícími exponenty:

5 9 13
2 6 10 14
3 7 11 15
4 8 12 16

Pokud mi teď na volné pole padne 2 na 1 hra končí. Pokud ale budu mít štěstí, padne mi tam 2 na 2 a já poskládám všechny dvojice, až se dostanu na hodnotu 2 na 17.

Každopádně nechápu, v jakém rozpoložení mysli jsem vytvořil tento obrázek, kde jsem smíchal dvě herní situace najednou 🙂 Ještě jednou děkuji za nahlášení.

Martin Skála
19.10.2015 at 23:04

Dekuji za odpoved, uz tomu rozumim 🙂

Napsat komentář