mylms

... web o elektronice


Hra života na Arduinu

Tak si tak brouzdám po netu a narazím na něco, co se jmenuje Hra života. Už ani nevím, jestli to bylo na YouTube, na Wikipedii, nebo někde jinde. Ale na Wiki jsem zjistil, že princip výpočtu je vlastně velmi jednoduchý. Napadlo mě si to zkusit nasimulovat, nejdřív jsem chtěl na počítači…ale pak jsem se rozhodl, že opráším displej z Číny, který tu mám více než rok a ještě jsem na něj nesáhl. K tomu malý Arduino a uvidíme, opráším moje rezavý zkušenosti s C++ (Wiring) a uvidíme, co z toho vymáčknu.

Jen krátce o principu hry… jestli to lze tedy nazvat hrou. Hra vlastně není pro žádného hráče. Na začátku je plocha do které se vygenerují „buňky“. Všechny buňky mají naprogramováno identické chování. Buňka může mít pouze dva stavy – živá/mrtvá. Stav buněk se mění podle počtu živých buněk v jejím okolí. Podle druhu algoritmu může hra připomínat vývoj společenství živých organizmů.

 

 



Hardware

„Hra“ života mi běží na Arduino Micro, k němu mám připojený barevný grafický displej s rozlišením 320×240 px s driverem ILI9341. Pro ovládání displeje používám knihovny Adafruit_ILI9341.h a Adafruit_GFX.h. K Arduinu mám dále připojené dvě kapacitní „tlačítka“, které reagují přes plastovou krabičku. Edit.: Už jsem to rozebral :D

 

gameoflife-2

Game of life on Arduino

Samotná kapitola pro mě bylo rozchození displeje. Záměrně jsem sáhl po barevném displeji 320×240 px. Ne že by se mi na to nějak přesně hodil, nebo tak… Ale ještě jsem ho nezkoušel a zajímalo mě, jestli ho vůbec dokážu zprovoznit. Chvilku jsem tápal; internet v tomhle směru moc nepomáhá. Krom toho, že existuje asi 20 druhů různých Arduin, několik knihoven na ovládání displejů a asi stejný počet možnosti propojení. Ještě tu mám malé OLED displeje 128*64 px., které mi fungují, mám je vyzkoušený a vypadají dost dobře. Uvidím, k čemu je použiji…

Protože displej má 3,3V a Arduino 5V logiku je nutné použít oddělovač. Já jsem použil 6 odporových děličů. Napájení LED podsvětlení je vyvedeno přímo z výstupu Arduina přes předřadný odpor 120 Ohm. Podsvětlení mám připojeno na výstup, který podporuje PWM. Ne že bych to používal, ale je možnost stmívání. Místo tlačítek používám kapacitní snímače z Aliexpressu.

Napájení ještě nemám vyřešeno. Zatím napájím přes USB kabel, protože upravuju program. Ale už mám vestavěný akumulátor ze staršího telefonu. Takže bude stačit nějaký nabíjecí obvod (Aliexpress je toho plnej) a přemýšlel jsem o solárním panelu, abych takovouhle bejkárnu nemusel živit… Stejně přemýšlím, že to rozeberu. Nemám moc rád neužitečný věci.

 

gameoflife-1

Schéma zapojení

 

gameoflife-3

Vnitřní (ne)uspořádání. Ještě chybí nabíjení akumulátoru a připevnit Arduino. Vlevo nahoře je šestinásobný odporový dělič.

 

Software

Software kupodivu není zas tak složitý. Největší problém mi dalo vůbec rozchodit displej. Respektive jsem nevěděl jaký piny Arduina použít, aby to fungovalo správně. Nakonec se mi podařilo rozchodit dvě knihovny (jiný jsem ani nezkoušel). Zkoušel jsem knihovny Ucglib a Adafruit_GFX s Adafruit_ILI9341. Poslední dvě jmenované nakonec používám. Myslím že zabírají méně paměti a jsou o něco rychlejší. Ale nějak extra jsem to neporovnával…v podstatě vlastně vůbec. Ale vypadalo to tak. 

//Ovladani displeje s ILI9341 s pomocí Arduino MICRO 
//www.mylms.cz knihovna Ucglib

#include <SPI.h>
#include <Ucglib.h>

Ucglib_ILI9341_18×240×320_SWSPI ucg(/sclk=/ 13, /data=/ 11, /cd=/ 6 , /cs=/ 5, /reset=/ 4);

 

 

//Ovladani displeje s ILI9341 s pomocí Arduino MICRO 
//www.mylms.cz knihovna Adafruit

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

Adafruit_ILI9341 tft = Adafruit_ILI9341(5, 6, 11, 13, 4, 12);

 

 

Pokud funguje ovládání displeje, tak už samotné kreslení na displej je velice jednoduché. Stačí si pročíst průvodce. Knihovna Ucglib se ovládá v podstatě úplně stejně.

//vypsání textu na konkrétní pozici 
tft.setCursor(25, 20); //pozice levého horního rohu
tft.setTextColor(CYAN); //barva
tft.setTextSize(4); //velikost textu
tft.print(„mylms.cz“); //samotné vypsání textu

//vykreslení vyplněného obdélníku
tft.fillRect(x, y, w, h, WHITE);

//a tak dále…

 

 

Logika hry je velice jednoduchá. Jde o to naplnit pole buňkami a potom je podle vzorce přepočítat. Přepočet taky není složitý. Nejhorší je, paměťová náročnost pole buněk. Pole buněk je třeba mít dvakrát. Jedno staré, ze kterého se vypočítá pole nové. To se vykreslí, uloží se jako pole staré a jde se počítat znova. Celý program vlastně funguje takto:

  1. START
  2. Inicializace displeje
  3. Vykreslení úvodní obrazovky
  4. Vytvoření náhodného vzoru a uložení do pole oldArray
  5. Výpočet nového pole (newArray) ze starého pole (oldArray) *
    1. zjištění kolik sousedů má aktuálně kontrolovaná buňka
    2. změna životnosti buňky podle počtu sousedů a aktuálního (oldArray) stavu
  6. Vykreslení na displej (pouze pokud displej svítí)
    1. Zjištění jestli se změnila buňka (porovnání oldArray a newArray *** ) (ANO – > skok na 2/ NE → skok na 1)
    2. Vykreslit barvu podle životnosti buňky
    3. Pokud není překresleno celé pole tak skok na 1
  7. Zkopírování newArray → oldArray
  8. Test stisku tlačítka – pokud bylo stisknuto, tak rozsvítit displej **
  9. Pokud displej svítí více než 60 sekund, tak zhasnout ***
  10. Skok na 5
 * tato funkce se provádí každých 1000 ms; ** tato funkce se provádí každých 50 ms; *** optimalizace kvůli rychlosti/spotřebě

 

Kvůli velikosti paměti Arduina používám dvě bool pole oldArray a newArray. Obě o stejné velikosti plochy (nyní 30 * 30 buněk). Tedy 900 bitů v jednom poli. Dohromady to je 1800 bitů, které se musejí stále dokola přepočítávat a vykreslovat.

 

Po spuštění a po inicializaci displeje (intro atd.) se naplní staré pole náhodnými daty. Jednoduše, pomocí smyčky FOR projedu dvourozměrné pole a vygeneruju náhodně TRUE/FALSE 

void naplneni() { 
//naplnění pole náhodnými hodnotami
for (byte x = 0; x < WORLDSIZE; x++) {
 for (byte y = 0; y < WORLDSIZE; y++) {
  rndNumber = random(255);
  if(rndNumber >= 220) {
   oldArray[x][y] = true;
  }
  else {
   oldArray[x][y] = false;
  }
 }
}
}

 

Tentokrát jsem kvůli úspoře místa a rychlosti nepoužil Tasker jako v sodobaru, ale jednoduše počítám čas od posledního spuštění funkce. Žádné delay(), které by brzdilo procesor. Elegantnější by samozřejmě bylo používat přerušení.

void loop(void){ 
//stisk tlacitka
if (millis() >= time_setDisplay + 50) {
 time_setDisplay = millis();
 setDisplay();
}

//vykreslení grafiky
if (millis() >= fime_grafika + 500) {
 time_grafika = millis();
 grafika();
}
}

 

V proceduře setDisplay() jednoduše hlídám, jestli nejsou stisknuta tlačítka. Pokud ano, tak rozsvítím podsvícení LCD. Pokud nejsou stisknuta tlačítka, tak počítám počet překreslení od uvolnění. Po nastavené době displej zhasne. V proceduře grafika() se provádí veškerá práce s grafikou, tedy výpočet a pokud svítí displej, tak i vykreslení.

Protože počítám i počet živých buněk z kterých vykresluji graf, tak si nejdřív vynuluji proměnnou population. Ve smyčce poté procházím celé pole a pozici aktuálně kontrolované buňky předávám funkce pocetSousedu(x, y);. Ta vrátí počet sousedících živých buněk. Potom podle životnosti buňky a počtu okolních buněk rozhodnu, jestli buňka bude žít. Pokud ano, tak nastavím TRUE a přičtu živou buňku do population.
Aby mi buňky nevychcípaly, mám v na konci vykreslování proceduru injectCells(). Pokud má populace dvakrát po sobě stejný počet živých buněk, tak na náhodné místo vložím několik buněk (vykresluji Křídlo/Glider). Sice to neřeší pokud by se cyklicky měnil počet třeba ze čtyřech na pět a zpět. Ale taková pravděpodobnost je malá… A lze se tomu vyhnout zavoláním funkce např. jednou za 1000 generací…

void vypocet() { 
population = 0; //reset populace před sečtením
generation++; //další generace buněk
for (byte x = 0; x < WORLDSIZE; x++) {
 for (byte y = 0; y < WORLDSIZE; y++) {
  neighbors = pocetSousedu(x,y); //zobrazení počtu okolních buněk

  if (oldArray[x][y]) {
   //ziva bunka
   if ((neighbors == 2 )||(neighbors == 3)) {
    newArray[x][y] = true; //bunka zustane
    population++;
   }
   else {
    newArray[x][y] = false; //bunka umre
   }
  }

  else {
   //mrtva bunka
   if (neighbors == 3) {
    newArray[x][y] = true;
    //bunka ozije
    population++;
   }
   else {
    newArray[x][y] = false; //bunka neozije
   }
  }
 }
}

//přidání buněk při stejné populaci jako minule
if (lastPopulation == population) {
 injectCells();
}
lastPopulation = population;
}

 

Samotná funkce pocetSousedu(x, y); vrací počet sousedících buněk. Její výstup může být 0 až 9. Protože používám malou plochu, buňky sousedící s krajem mají za sousedy buňky z druhého okraje. Takže buňka na souřadnici 0, 0 (levý horní okraj) má za sousedy i buňky z pravého horního okraje, levého dolního okraje, … Viz obrázek. Černá buňka je kontrolovaná a ze šedých buněk se bere výsledek. Takže buňky v podstatě nejsou na 2D ploše ale na toroidu, nebo tak na něčem podobným.

Kvůli tomu, že kontroluji buňky za hranami (hlídám zápornou hodnotu, nebo přetečení) „musím“ používat proměnou typu integer. Rychlejší by možná bylo použít typ byte a k pozici připočítávat offset. Pak by se počítalo pouze s kladnými čísly. Výpočet pole buněk však není největší brzda…

Teď jsem ještě zjistil, že je možná možné proměnné typu bool sčítat. Zatím jsem neověřoval, jestli vychází výsledek správně, tak tam mám několikrát IF… Ale pokud by to fungovalo bezchybně, mohlo by se to celé smazat a použít zakomentovaný return.

 

int pocetSousedu(byte x, byte y) { 
byte result = 0;

//příprava pro přetečení z plochy
ax = x + 1;
ay = y + 1;
bx = x - 1;
by = y - 1;

if (ax == WORLDSIZE) { ax = 0; }
if (ay == WORLDSIZE) { ay = 0; }
if (bx == -1) { bx = WORLDSIZE-1; }
if (by == -1) { by = WORLDSIZE-1; }

if (oldArray[bx][by]) { result++; }
if (oldArray[x][by]) { result++; }
if (oldArray[ax][by]) { result++; }
if (oldArray[bx][y]) { result++; }
if (oldArray[ax][y]) { result++; }
if (oldArray[bx][ay]) { result++; }
if (oldArray[x][ay]) { result++; }
if (oldArray[ax][ay]) { result++; }

//return oldArray[bx][by] + oldArray[x][by] + oldArray[ax][by] + oldArray[bx][y] + oldArray[ax][y] + oldArray[bx][ay] + oldArray[x][ay] + oldArray[ax][ay];
return result;
}

 

gameoflife-4

Výpočet okolních buněk

 

Nejpomalejší z celého programu je vykreslování na displej. Zatím se mi nepodařilo zjistit, jestli je možné do displeje odeslat data a potom je naráz vykreslit (omezilo by se alespoň blikání), nebo lze vykreslování zrychlit. Možná to bude tím, že jsem po tom zatím nepátral… Opět ve smyčce projíždím pole, ale ještě před vykreslením zjišťuji jestli se buňka oproti dřívějšímu stavu změnila. Pokud ne, je zbytečné ji překreslovat. Pokud ano, tak ji překreslím. Vykreslování je díky tomu minimálně o 50 % rychlejší. Čím míň změněných buněk, tím je vykreslování rychlejší.
Teď mě napadlo, že by možná bylo možný vše vykreslovat v jedné smyčce. Ale nové pole by se na staré stejně muselo  zkopírovat až nakonec, protože je nepřípustné aby se tyto dvě pole míchala dokupy. Takže by ke konci stejně musela být smyčka… Každopádně problém s rychlostí vykreslování to neřeší…

 

void vykresleni() { 
byte cellSize = 240 / WORLDSIZE;

for (byte x = 0; x < WORLDSIZE; x++) {
 for (byte y = 0; y < WORLDSIZE; y++) {
  if (oldArray[x][y] != newArray[x][y]) {
   if (newArray[x][y]) {
    //živá buňka
    tft.fillRect(x * cellSize, y * cellSize, cellSize - 2, cellSize - 2, WHITE);
   }
   else {
    //mrtvá buňka
    tft.fillRect(x * cellSize, y * cellSize, cellSize - 2, cellSize - 2, BLACK);
   }
  }
  oldArray[x][y] = newArray[x][y];
 }
}

 

Takhle to je asi vše… Celý program si můžete stáhnout tady. Je to přesně ten program, který mi funguje. Ale funkci zaručit nemůžu, protože když vidím, jaký jsou peripetie s těma displejema z číny…

Celkově z toho mám rozporuplný pocity. Tohle je přesně taková ta věc, která k ničemu není. Na zapřemejšlení nad algoritmem života buněk sice dobrý, ale na nějaký reálnější situace pomalý. Lepší by byl program na PC, který by zpracoval např. tisíce buněk několikrát rychleji. Ostatně, na netu si můžete najít online hru života, se kterou se dá i víc pracovat. Je to takový Tamagotchi (pro mladší generace Pou) o který se nemusíte starat. Ale jako zajímavost dobrý…

Jinak, pokusím se nějak zkulturnit prezentaci kódu na těchto stránkách. Zatím jsem to moc nepotřeboval. Sice tu mám nějaký barvítko na kód, ale s tím nejsem spokojen. Pokud máte nějakej tip na snadnou implementaci, dejte vědět do komentářů.



Napsal Petan před pěti měsíci v kategorii Elektronika Programy. Připojeno 0 komentářů.
Přečteno 979x.

Na programy zde poskytované není žádná záruka na funkčnost (viz licence). Jednotlivé články, stejně jako celý obsah stránek není návodem a slouží pouze k studijním účelům. Zapojení výše mají pouze informativní charakter! Vždy se řiďte originálním návodem k použití! Na elektrickém (vyhrazeném) zařízení smí pracovat pouze osoba s příslušnou kvalifikací dle vyhlášky 50/78 Sb! Vše tedy děláte na vlastní nebezpečí! Autor stránek nebere žádnou zodpovědnost za případné újmy na zdraví, životě, majetku a jiné!

Některé části textů mohou obsahovat texty, případně obrázky ze stránek Wikipedia a Wikimedia Commons. Tyto části jsou dostupné pod původní licencí Creative Commons.



Doposud nebyl připojen žádný komentář. Buďte první!

Připojte váš komentář!

* Hvězdičkou jsou označena povinná pole. Autor stránek odpovídá vždy do komentáře.