Igra reakcije

Igra reakcije na Arduinu: automat stanja sa switch i case, nasumično čekanje s random(), mjerenje vremena s millis() i zvukovi s tone() bez čekanja

Arduino Elektronika
Igra reakcije

Pravi fliper uvijek je u nekom stanju. Dok nitko ne igra, lampice trepere, a zaslon vrti natpis. To je attract mode, način u kojem aparat mami igrače. Kada igrač pritisne Start, počinje igra. Kada kuglica padne u rupu, aparat čeka sljedeću kuglicu, a na kraju pokaže rezultat i opet počne mamiti igrače. Ista tipka u svakom stanju znači nešto drugo, a svako stanje ima svoje lampice i svoje zvukove.

Program koji tako radi zove se automat stanja (engleski state machine). Danas ćemo ga napisati za malu igru, igru reakcije. Usput ćemo naučiti puštati zvukove koji ne zaustavljaju program.

Što to znači za fliper?

Tlocrt flipera: plavi krug označava dio lijevog zvučnika i prve LED matrice zaslona, a u velikom plavom krugu isti su dijelovi uvećani: rešetka zvučnika i svijetle točke slova P

Na tlocrtu našeg flipera plavim je krugom označen dio lijevog zvučnika i prve od četiri LED matrice na zaslonu, a u velikom krugu isti su dijelovi uvećani. Svijetle točke na matrici dio su slova P iz natpisa PINBALL, koji zaslon vrti dok aparat mami igrače, a iz zvučnika dolaze zvukovi igre. Što znače ostali dijelovi crteža, piše na stranici o fliperu.

Glavni Arduino flipera bit će automat stanja, samo s više stanja: aparat mami igrače, igra je u tijeku, kuglica je izgubljena, igrač je prejako gurnuo aparat (tilt) i igra je gotova. Svako stanje ima svoje lampice, zvukove i pravila za tipke. Kada budemo projektirali fliper, pravila ćemo prvo nacrtati kao dijagram stanja, a tek onda pisati program.

U fliperu se uvijek događa više stvari odjednom: lampice trepere, zvuk svira, a program mora u svakom trenutku primijetiti kuglicu. Zato ni tamo ne smije biti delay().

Kako se igra

  1. Dok igra čeka igrača, LED-ica polako trepće.
  2. Pritisneš tipkalo. LED-ica se ugasi, a igra čeka nasumično dugo, od 1 do 4 sekunde.
  3. LED-ica zasvijetli i zujalica kratko zazuji. Sada pritisni tipkalo što brže možeš!
  4. Program u Serial Monitoru ispiše koliko ti je trebalo, u milisekundama.

Ako pritisneš tipkalo prije nego što LED-ica zasvijetli, to je prerano. Zujalica tada dugo i duboko zazuji.

Materijal

Svaki polaznik dobiva:

Za dodatni izazov 2 trebaju još jedno tipkalo i još jedan otpornik od 470 Ω.

Spajanje

Pravila za spajanje ista su kao u prošloj lekciji. Kreni od spoja iz prošle lekcije: LED-ica je na D8, a tipkalo na D2. Zujalicu dodaj na isto mjesto kao na pješačkom semaforu, u redak 27.

DioArduino pinŽica s ArduinaOtpornikDesna strana
LED-icaD8a3220 Ω: e3–f3crvena LED-ica: dulja nožica u j3, kraća u desnu – liniju
TipkaloD2a8470 Ω: b8–b12tipkalo u e12, e14, f12, f14; žica iz j14 u desnu – liniju
ZujalicaD5a2768 Ω: e27–f27+ nožica zujalice u j27, druga nožica u desnu – liniju
Zajednički GNDGNDdesna – linija––
Shema: crvena LED-ica na pinu D8 preko otpornika od 220 oma, zujalica na pinu D5 preko otpornika od 68 oma i tipkalo na pinu D2 preko otpornika od 470 oma, sve prema GND
Spajanje: LED-ica i tipkalo kao u prošloj lekciji, a D5 u a27, otpornik od e27 do f27, plus nožica zujalice u j27 i druga nožica u desnu minus liniju
  • Zujalica ima polaritet. Nožica + je dulja, a oznaka + nalazi se i na gornjoj strani kućišta. Okrenuta zujalica šuti.
  • Otpornik od 68 Ω smanjuje struju kroz pin i ujedno stišava zujalicu.
  • Pin A0 ostaje nespojen. Zašto, saznat ćemo u Vježbi 2.

Vježba 1: Zvuk koji ne čeka

Zujalicu smo upoznali na pješačkom semaforu. Aktivna zujalica sama zuji uvijek istim tonom, a tone() je vrlo brzo pali i gasi, pa čujemo drukčije zvukove (više o tome piše u odjeljku Aktivna zujalica). Prvi program pri svakom pritisku kratko zazuji, a LED-ica svijetli dok držimo tipkalo. Tipkalo čitamo s bibliotekom Bounce2, kao tipku Start na kraju prošle lekcije.

// C++
#include <Bounce2.h>                   // biblioteka za smirivanje tipkala

const int tipkalo = 2;
const int ledPin = 8;
const int zujalica = 5;

Bounce2::Button igrac;                 // tipkalo igrača

void setup() {
  igrac.attach(tipkalo, INPUT_PULLUP); // tipkalo je na D2, s ugrađenim pull-up otpornikom
  igrac.interval(10);                  // promjena vrijedi kada je tipkalo mirno 10 ms
  igrac.setPressedState(LOW);          // pritisnuto je LOW
  pinMode(ledPin, OUTPUT);
  pinMode(zujalica, OUTPUT);
}

void loop() {
  igrac.update();                      // pročitaj i smiri tipkalo

  if (igrac.pressed()) {
    tone(zujalica, 800, 60);           // kratki zvuk pri svakom pritisku
  }
  digitalWrite(ledPin, igrac.isPressed());   // LED-ica svijetli dok je tipkalo pritisnuto
}

Učitaj program i pritisni tipkalo nekoliko puta, kratko i dugo.

Što je novo u programu?

  • Bounce2::Button igrac; – Tipkalo je objekt, kao tipka Start u Vježbi 5 prošle lekcije. Zove se igrac, jer je to tipkalo igrača.
  • igrac.update(); – Biblioteka pročita i smiri tipkalo. Mora biti u svakom prolazu kroz loop(), jednom, prije svih pitanja o tipkalu.
  • igrac.pressed() i igrac.isPressed() – Prvo daje true samo u prolazu u kojem je tipkalo upravo pritisnuto, a drugo dok je pritisnuto.
  • tone(zujalica, 800, 60); – Kao na semaforu: zvuk frekvencije 800 Hz traje 60 ms. tone() zvuk samo pokrene i program odmah ide dalje. Zvuk nakon 60 ms ugasi čip Arduina, sam, dok program radi nešto drugo. Zato LED-ica prati tipkalo i dok zujalica zuji.

Fanfara s delay()

Neka sada svaki pritisak svira kratku fanfaru od tri zvuka. U loop() zamijeni redak s tone() ovim recima:

// C++
    tone(zujalica, 400, 100);
    delay(150);
    tone(zujalica, 600, 100);
    delay(150);
    tone(zujalica, 800, 300);
    delay(300);

Učitaj program i isprobaj:

  1. Pritisni tipkalo i drži ga. Kada LED-ica zasvijetli?
  2. Pritisni tipkalo pet puta, što brže možeš. Koliko si fanfara čuo?
Zašto LED-ica kasni i zašto se neki pritisci izgube?
Za vrijeme triju delay() program 600 ms samo čeka. Ne gleda tipkalo i ne pali LED-icu, pa LED-ica zasvijetli tek kada fanfara završi. Pritisak koji počne i završi za vrijeme fanfare program uopće ne vidi.

Eksperiment: obriši sva tri delay() i ponovo učitaj program. Što sada čuješ?

Zašto se bez delay() čuje samo posljednji zvuk?
tone() ne čeka da zvuk završi. Sva tri poziva izvedu se jedan za drugim, unutar jedne milisekunde, a svaki novi zvuk odmah zamijeni prethodni. Arduino svira samo jedan zvuk odjednom, pa ostane samo posljednji, od 800 Hz.

Za fanfaru bez delay() program bi morao pamtiti koji zvuk je sljedeći i kada ga treba pokrenuti. Drugim riječima, morao bi pamtiti gdje je stao. Upravo to radi automat stanja.

Vježba 2: Igra kao automat stanja

Igra reakcije uvijek je u jednom od četiri stanja. Svako stanje ima svoje ime, a iz jednog stanja u drugo igra prelazi kada se nešto dogodi: kada igrač pritisne tipkalo ili kada prođe neko vrijeme.

StanjeŠto se događaKada prelaziU stanje
CEKAigra čeka igračapritisakPRIPREMA
PRIPREMALED-ica ne svijetli, igra čeka nasumično 1–4 sprošlo je nasumično vrijemeKRENI
pritisak: prerano!REZULTAT
KRENILED-ica svijetli, mjerimo vrijemepritisak: ispiši vrijemeREZULTAT
REZULTATpauza 2 sprošle su 2 sCEKA

Istu tablicu možemo nacrtati kao dijagram stanja: svako stanje je jedan pravokutnik, a strelice su prijelazi. Uz svaku strelicu piše što pokreće prijelaz. Ovakav dijagram nacrtat ćemo kasnije i za pravila cijelog flipera.

Dijagram stanja igre reakcije: iz CEKA pritisak vodi u PRIPREMA. Iz PRIPREMA nasumično vrijeme vodi u KRENI, a prerani pritisak u REZULTAT. Iz KRENI pritisak vodi u REZULTAT, a iz REZULTAT nakon 2 s natrag u CEKA
Planiraj prije nego što programiraš:
ponavljaj zauvijek:
pogledaj je li tipkalo upravo pritisnuto
ako je stanje CEKA:
ako je pritisnuto: odaberi nasumično čekanje, prijeđi u PRIPREMA
ako je stanje PRIPREMA:
ako je pritisnuto: prerano! dugi niski zvuk, prijeđi u REZULTAT
inače, ako je čekanje prošlo: upali LED-icu, kratki zvuk, prijeđi u KRENI
ako je stanje KRENI:
ako je pritisnuto: ugasi LED-icu, ispiši vrijeme, prijeđi u REZULTAT
ako je stanje REZULTAT:
ako su prošle 2 s: prijeđi u CEKA

Program je dulji od dosadašnjih, ali svaki njegov dio je kratak. Tipkalo čitamo s bibliotekom Bounce2, kao u Vježbi 1.

// C++
#include <Bounce2.h>                   // biblioteka za smirivanje tipkala

const int tipkalo = 2;
const int ledPin = 8;
const int zujalica = 5;
const unsigned long PAUZA = 2000;      // toliko traje stanje REZULTAT

// Stanja igre
const int CEKA = 0;                    // igra čeka igrača
const int PRIPREMA = 1;                // nasumično čekanje prije signala
const int KRENI = 2;                   // LED-ica svijetli: pritisni što brže!
const int REZULTAT = 3;                // kratka pauza nakon rezultata

Bounce2::Button igrac;                 // tipkalo igrača
int stanje = CEKA;                     // u kojem je stanju igra sada
unsigned long pocetakStanja = 0;       // kada je počelo sadašnje stanje
unsigned long cekanje = 0;             // koliko dugo traje PRIPREMA

void setup() {
  igrac.attach(tipkalo, INPUT_PULLUP);
  igrac.interval(10);
  igrac.setPressedState(LOW);
  pinMode(ledPin, OUTPUT);
  pinMode(zujalica, OUTPUT);
  Serial.begin(9600);
  randomSeed(analogRead(A0));          // A0 je u zraku: svaki put drukčiji početak
  Serial.println("Pritisni tipkalo za početak.");
}

void loop() {
  igrac.update();                                     // tipkalo čitamo jednom, na početku
  unsigned long uStanju = millis() - pocetakStanja;   // koliko je igra već u sadašnjem stanju

  switch (stanje) {
    case CEKA:
      if (igrac.pressed()) {
        cekanje = random(1000, 4001);  // od 1 do 4 s
        Serial.println("Pripremi se...");
        promijeniStanje(PRIPREMA);
      }
      break;

    case PRIPREMA:
      if (igrac.pressed()) {           // pritisak prije signala
        tone(zujalica, 150, 400);      // dugi niski zvuk
        Serial.println("Prerano! Pričekaj da LED-ica zasvijetli.");
        promijeniStanje(REZULTAT);
      } else if (uStanju >= cekanje) { // vrijeme je za signal
        digitalWrite(ledPin, HIGH);
        tone(zujalica, 800, 60);
        promijeniStanje(KRENI);
      }
      break;

    case KRENI:
      if (igrac.pressed()) {
        digitalWrite(ledPin, LOW);
        Serial.print("Tvoje vrijeme: ");
        Serial.print(uStanju);         // od signala do pritiska
        Serial.println(" ms");
        promijeniStanje(REZULTAT);
      }
      break;

    case REZULTAT:
      if (uStanju >= PAUZA) {
        Serial.println("Pritisni tipkalo za novu igru.");
        promijeniStanje(CEKA);
      }
      break;
  }
}

// Prelazak u novo stanje: zapamtimo i kada je počelo.
void promijeniStanje(int novoStanje) {
  stanje = novoStanje;
  pocetakStanja = millis();
}

Učitaj program, otvori Serial Monitor (9600 baud) i odigraj nekoliko krugova. Pokušaj jednom namjerno pritisnuti prerano.

Što je novo u programu?

  • const int CEKA = 0; – Stanja su obični brojevi, 0, 1, 2 i 3, ali s imenima. stanje == KRENI lakše se čita od stanje == 2, a ako pogriješimo u imenu, prevođenje javi grešku.
  • int stanje = CEKA; – Varijabla koja pamti u kojem je stanju igra. Na početku igra čeka igrača.
  • switch (stanje) { case CEKA: ... break; } – switch pogleda vrijednost varijable stanje i skoči na case s istom vrijednošću. Izvodi se samo kod tog stanja, do riječi break, koja završava switch. Isto bismo mogli napisati s nizom if i else if, ali ovako se lijepo vidi što radi koje stanje.
  • break ne smijemo zaboraviti. Bez njega program nastavi izvoditi i kod sljedećeg stanja, kao da ga nema.
  • void promijeniStanje(int novoStanje) – Svaki prijelaz ide kroz ovu funkciju. Uz novo stanje zapamti i trenutak u kojem je počelo, pocetakStanja.
  • unsigned long uStanju = millis() - pocetakStanja; – Koliko je milisekundi igra već u sadašnjem stanju. U stanju PRIPREMA uspoređujemo ga s nasumičnim čekanjem, u stanju REZULTAT s pauzom od 2 s, a u stanju KRENI to je upravo vrijeme od signala do pritiska: tvoja reakcija.
  • random(1000, 4001) – Nasumični broj od 1000 do 4000. Gornja granica, 4001, nikada ne izađe, zato pišemo za jedan više.
  • randomSeed(analogRead(A0)); – random() zapravo nije nasumičan. Nakon svakog pokretanja daje iste brojeve, istim redom, kao da ih čita iz duge tablice. randomSeed() odabire gdje u toj tablici počinjemo. Pin A0 je u zraku, pa analogRead(A0) svaki put pročita nešto drugo. Pin u zraku iz prošle lekcije ovdje nam je koristan.
  • igrac.update(); na vrhu loop() – Tipkalo čitamo jednom, na početku svakog prolaza, a stanja samo pitaju igrac.pressed(). update() stoji izvan switch, pa biblioteka prati tipkalo u svakom stanju, i u stanju REZULTAT, u kojem nas pritisak ne zanima.
  • Izmjereno vrijeme malo je dulje od tvoje reakcije. Bounce2 javi pritisak tek kada je tipkalo mirno 10 ms, kako smo vidjeli u prošloj lekciji. Zato svako vrijeme ima oko 10 do 15 ms više. Svi igrači dobiju isti dodatak, pa to ne mijenja tko je najbrži.
  • U loop() nema nijednog delay(). U stanju PRIPREMA program istodobno pazi na vrijeme i na tipkalo. Zato može primijetiti prerani pritisak.
Zašto nasumično čekanje ne pišemo kao delay(cekanje)?
Za vrijeme delay() program ne gleda tipkalo. Prerani pritisak tada ne bi primijetio, pa igrač ne bi mogao pogriješiti. Pritisnuo bi tipkalo prije signala i držao ga, a program bi mu izmjerio vrijeme od gotovo 0 ms.

Koliko brzo reagiraš?

Većina ljudi na svjetlo reagira za otprilike 200 do 250 ms, dakle za četvrtinu sekunde. Na zvuk reagiramo malo brže. U tom vremenu oko vidi svjetlo, mozak odluči da je vrijeme za pritisak, a živci pokrenu mišiće prsta. Umor i nepažnja to vrijeme produlje, a vježba ga samo malo skrati.

Naša igra uz LED-icu daje i kratki zvuk. Isprobaj kakvo je tvoje vrijeme kada gledaš samo LED-icu, a kakvo kada zatvoriš oči i slušaš samo zujalicu.

Vježba 3: Attract mode i rekord

Igra radi, ali dok čeka igrača ničim ne pokazuje da je spremna. Dodat ćemo joj ono što ima svaki fliper:

  • dok igra čeka, LED-ica trepće svakih 500 ms, kao žuto svjetlo semafora u kvaru;
  • kratki zvuk kada igra počne;
  • rekord: najbolje vrijeme od pokretanja Arduina, uz poseban zvuk kada ga netko obori.

Dijagram stanja ostaje isti. Ne dodajemo nova stanja ni nove prijelaze, nego samo ono što se događa unutar stanja CEKA i KRENI.

Cijeli program

// C++
#include <Bounce2.h>                   // biblioteka za smirivanje tipkala

const int tipkalo = 2;
const int ledPin = 8;
const int zujalica = 5;
const unsigned long TREPTANJE = 500;   // dok igra čeka, LED-ica se mijenja svakih 500 ms
const unsigned long PAUZA = 2000;      // toliko traje stanje REZULTAT

// Stanja igre
const int CEKA = 0;                    // igra čeka igrača: LED-ica trepće
const int PRIPREMA = 1;                // nasumično čekanje prije signala
const int KRENI = 2;                   // LED-ica svijetli: pritisni što brže!
const int REZULTAT = 3;                // kratka pauza nakon rezultata

Bounce2::Button igrac;                 // tipkalo igrača
int stanje = CEKA;                     // u kojem je stanju igra sada
unsigned long pocetakStanja = 0;       // kada je počelo sadašnje stanje
unsigned long cekanje = 0;             // koliko dugo traje PRIPREMA
unsigned long rekord = 0;              // najbolje vrijeme; 0 znači da rekorda još nema

bool ledUpaljena = false;              // za treptanje dok igra čeka
unsigned long zadnjeTreptanje = 0;     // kada se LED-ica zadnji put promijenila

void setup() {
  igrac.attach(tipkalo, INPUT_PULLUP);
  igrac.interval(10);
  igrac.setPressedState(LOW);
  pinMode(ledPin, OUTPUT);
  pinMode(zujalica, OUTPUT);
  Serial.begin(9600);
  randomSeed(analogRead(A0));          // A0 je u zraku: svaki put drukčiji početak
  Serial.println("Pritisni tipkalo za početak.");
}

void loop() {
  igrac.update();                                     // tipkalo čitamo jednom, na početku
  unsigned long uStanju = millis() - pocetakStanja;   // koliko je igra već u sadašnjem stanju

  switch (stanje) {
    case CEKA:
      if (millis() - zadnjeTreptanje >= TREPTANJE) {  // LED-ica trepće bez delay()
        zadnjeTreptanje = millis();
        ledUpaljena = !ledUpaljena;
        digitalWrite(ledPin, ledUpaljena);
      }
      if (igrac.pressed()) {
        ledUpaljena = false;
        digitalWrite(ledPin, LOW);
        tone(zujalica, 400, 50);       // kratki zvuk: igra počinje
        cekanje = random(1000, 4001);  // od 1 do 4 s
        Serial.println("Pripremi se...");
        promijeniStanje(PRIPREMA);
      }
      break;

    case PRIPREMA:
      if (igrac.pressed()) {           // pritisak prije signala
        tone(zujalica, 150, 400);      // dugi niski zvuk
        Serial.println("Prerano! Pričekaj da LED-ica zasvijetli.");
        promijeniStanje(REZULTAT);
      } else if (uStanju >= cekanje) { // vrijeme je za signal
        digitalWrite(ledPin, HIGH);
        tone(zujalica, 800, 60);
        promijeniStanje(KRENI);
      }
      break;

    case KRENI:
      if (igrac.pressed()) {
        digitalWrite(ledPin, LOW);
        Serial.print("Tvoje vrijeme: ");
        Serial.print(uStanju);         // od signala do pritiska
        Serial.println(" ms");
        if (rekord == 0 || uStanju < rekord) {
          rekord = uStanju;
          tone(zujalica, 1000, 300);   // dugi visoki zvuk: novi rekord
          Serial.println("Novi rekord!");
        } else {
          tone(zujalica, 600, 100);
        }
        promijeniStanje(REZULTAT);
      }
      break;

    case REZULTAT:
      if (uStanju >= PAUZA) {
        Serial.print("Rekord: ");
        Serial.print(rekord);
        Serial.println(" ms. Pritisni tipkalo za novu igru.");
        promijeniStanje(CEKA);
      }
      break;
  }
}

// Prelazak u novo stanje: zapamtimo i kada je počelo.
void promijeniStanje(int novoStanje) {
  stanje = novoStanje;
  pocetakStanja = millis();
}

Učitaj program i odigraj nekoliko krugova. Neka igra i netko pokraj tebe: tko ima rekord?

Što je novo u programu?

  • Treptanje je samo u stanju CEKA. Isti oblik s millis() koristili smo za treptanje žutog u lekciji Dva semafora na križanju. Kod u case CEKA: izvodi se samo dok igra čeka, pa u ostalim stanjima LED-ica ne trepće, a da to nigdje ne moramo zabraniti. To je velika prednost automata stanja: svako stanje ima svoj kod.
  • ledUpaljena = false; i digitalWrite(ledPin, LOW); – Kada igra krene, LED-icu ugasimo. Inače bi mogla ostati upaljena, jer je zadnje treptanje možda bilo baš upali.
  • unsigned long rekord = 0; – Najbolje vrijeme. Vrijednost 0 znači da rekorda još nema, jer reakcija od 0 ms nije moguća.
  • if (rekord == 0 || uStanju < rekord) – || znači ili. Uvjet je istinit ako rekorda još nema ili ako je novo vrijeme bolje od rekorda. U prošloj lekciji upoznali smo &&, koji znači i.
  • Svaki važan događaj ima svoj zvuk: početak igre, signal, prerani pritisak, obično vrijeme i novi rekord. Svi zvukovi pokreću se s tone() i ne zaustavljaju program.
  • Rekord se ne pamti kada Arduino isključimo. Na fliperu ćemo rekorde kasnije spremati u memoriju koja ih pamti i kada je Arduino isključen.

Ako nešto ne radi

  • Zujalica šuti ⇒ Je li dulja nožica, s oznakom +, u j27, a druga u desnoj – liniji? Je li žica s D5 u a27, a otpornik od e27 do f27?
  • LED-ica ne svijetli ili ne reagira na tipkalo ⇒ Spoj LED-ice i tipkala isti je kao u prošloj lekciji. Pogledaj odjeljak Ako nešto ne radi u njoj.
  • Serial Monitor ne pokazuje ništa ili pokazuje čudne znakove ⇒ Odaberi 9600 baud.
  • Nasumično čekanje svaki put je isto, nakon svakog pokretanja ⇒ Je li na pinu A0 nešto spojeno? Je li u setup() redak s randomSeed()?
  • Igra preskače stanja ili radi nešto što nije u dijagramu ⇒ Je li na kraju svakog case napisan break;?
  • Igra ne reagira na tipkalo ⇒ Je li igrac.update(); na početku loop()? Bez njega biblioteka tipkalo nikada ne pročita.
  • Greška Bounce2.h: No such file or directory ⇒ Biblioteka Bounce2 nije instalirana. Instaliraj je kao u prošloj lekciji.
  • Program se ne može učitati ⇒ Provjeri jesu li u Arduino IDE-u odabrani ploča Arduino Uno i pravi port.

Dodatni izazovi

  1. Zaspao si? Ako igrač u stanju KRENI ne pritisne tipkalo za 3 s, neka program ispiše Prespor! i prijeđe u stanje REZULTAT. Koju strelicu tada treba dodati u dijagram stanja?
  2. Dva igrača. Dodaj drugo tipkalo na D4, spojeno kao u dodatnom izazovu 1 prošle lekcije. U stanju KRENI pobjeđuje igrač koji prvi pritisne svoje tipkalo. Tko pritisne prerano, gubi. Savjet: drugo tipkalo dobije svoj objekt, na primjer igrac2, a na početku loop() pozovi update() za oba.
  3. Fanfara bez delay(). Neka igra na početku svira fanfaru od tri zvuka iz Vježbe 1, ali bez delay(). Savjet: frekvencije spremi u niz, int note[] = {400, 600, 800};. Niz je varijabla s više vrijednosti, a do svake dolazimo po rednom broju: note[0] je 400, note[2] je 800. Program treba pamtiti koja je nota sljedeća i kada ju treba pokrenuti. To je mali automat stanja unutar igre.
  4. Prosjek. Neka program nakon svakih pet krugova ispiše prosječno vrijeme tih pet krugova. Prerani pritisak ne računa se kao krug.

Što smo naučili?

  • Automat stanja je program koji je uvijek u jednom od nekoliko stanja. U svakom stanju radi nešto drugo, a u drugo stanje prelazi kada se nešto dogodi.
  • Stanja i prijelaze crtamo kao dijagram stanja: pravokutnici su stanja, a strelice prijelazi.
  • Stanja su brojevi s imenima (const int CEKA = 0;), a varijabla stanje pamti sadašnje stanje.
  • switch i case izvode kod samo onog stanja u kojem je program. Svaki case završava s break.
  • Ako pri svakom prijelazu zapamtimo vrijeme, millis() - pocetakStanja kaže koliko je program već u tom stanju.
  • S bibliotekom Bounce2 tipkalo čitamo jednom, s update() na početku svakog prolaza, a stanja pitaju pressed().
  • tone(pin, frekvencija, trajanje) zvuk samo pokrene i ne čeka da završi. Arduino svira jedan zvuk odjednom: novi zamijeni prethodni.
  • delay() zaustavlja cijeli program. Za to vrijeme Arduino ne vidi tipkalo.
  • random(od, do) daje nasumični broj, a gornja granica nikada ne izađe. randomSeed(analogRead(A0)) s pinom u zraku daje drukčije brojeve nakon svakog pokretanja.
  • || znači ili, a && znači i.

Mali kviz

1. Što je automat stanja?
Program koji je uvijek u jednom od nekoliko stanja. U svakom stanju radi nešto drugo, a iz stanja u stanje prelazi kada se nešto dogodi, na primjer kada igrač pritisne tipkalo ili kada prođe neko vrijeme.
2. Zašto LED-ica u Vježbi 1 prati tipkalo i dok zujalica zuji?
tone(zujalica, 800, 60) zvuk samo pokrene i program odmah ide dalje. Zvuk nakon 60 ms ugasi čip Arduina, sam, a program za to vrijeme i dalje čita tipkalo i pali LED-icu.
3. Što se dogodi ako na kraju jednog case zaboravimo break?
Program ne izađe iz switch, nego nastavi izvoditi i kod sljedećeg stanja. Igra tada radi stvari koje nisu u dijagramu stanja, na primjer preskoči stanje.
4. Što znači uStanju u stanju KRENI?
Koliko je milisekundi prošlo otkad je LED-ica zasvijetlila, dakle otkad je igra prešla u stanje KRENI. Kada igrač pritisne tipkalo, to je njegovo vrijeme reakcije.
5. Može li random(1000, 4001) dati 4001?
Ne može. Gornja granica nikada ne izađe, pa je najveći broj 4000. Zato pišemo 4001 kada želimo čekanje do 4000 ms.
6. Zašto pin A0 u ovoj igri mora ostati nespojen?
randomSeed(analogRead(A0)) treba nasumičnu vrijednost. Pin u zraku svaki put pročita nešto drugo, pa igra nakon svakog pokretanja čeka drukčije dugo. Kada bi pin bio spojen, čitao bi uvijek isto, a igra bi nakon svakog pokretanja čekala jednako dugo, istim redom.
7. Kako igra primijeti prerani pritisak?
U stanju PRIPREMA program u svakom prolazu kroz loop() gleda i vrijeme i tipkalo. Ako je tipkalo pritisnuto prije nego što je čekanje prošlo, to je prerani pritisak, pa igra prelazi u stanje REZULTAT.
8. Zašto u stanju KRENI LED-ica ne trepće, iako je kod za treptanje u programu?
Kod za treptanje nalazi se u case CEKA:, pa se izvodi samo dok je igra u stanju CEKA. U stanju KRENI izvodi se samo kod iz case KRENI:.