Arduino projekt: 2048
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.
Obsah článku:
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).
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 karet, SD 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ů.
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.
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.
//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.
- Sledovač slunce s Arduinem - 23.3.2022
- Programovatelný kytarový pedál s Arduinem - 26.2.2020
- Arduino infračervený teploměr vytištěný na 3D tiskárně - 11.2.2020
Martin Skála
14.10.2015 at 19:34Dobry 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:05Dobrý 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:
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:04Dekuji za odpoved, uz tomu rozumim 🙂