Codemas - 21. den

V naší hře máme v pravém horním rohu připravenou oblast na odpočítávání času. Dnes přidáme funkce, které budou čas sledovat a v případě, že hráči dojde čas až na nulu, tak hra skončí.

Dárky nám ze sání zatím padají náhodně a když dárek spadne na zem, nemá to na hru zatím žádný vliv. Protože dárky padají různou rychlostí, je někdy dost těžké nebo až nemožné všechny dárky chytit. Mohli bychom to vyladit změnou intervalu, ve kterém dárky padají, ale to byla zase hra moc lehká.

Pojďme si konečně stanovit pravidla, jak bude hra fungovat:

  • Hráč na začátku dostane 1 minutu a 30 vteřin času.
  • Za každý chycený dárek se mu k času přičtou 3 vteřiny.
  • Za každý dárek spadený na zem se mu odečte 10 vteřin.

Když pravidla nastavíme takto, je téměř jisté, že hra nepůjde hrát donekonečna. Nový dárek padá ze saní v intervalu 1-5 vteřin, přitom za každý chycený dostaneme navíc jenom 3 vteřiny. Ne všechny dárky půjde chytit a za každý spadený je -10 vteřin, takže i těm hodně šikovným hráčům by měl čas pomalu ubývat a nakonec hra skončí.

Možná budeme muset pravidla později trochu upravit, ale prozatím nám to takto stačí. Dnes si do hry přidáme počítání a zobrazování času, zítra doplníme přičítání a odečítání času za chycené a rozbité dárky.

Nejprve si do našeho objektu hra přidáme vlastnost cas, do které si na startu hry uložíme číslo 90 (1 minuta 30 vteřin = 90 vteřin). Zároveň si vytvoříme i vlastnost casElement, kam uložíme odkaz na HTML prvek na stránce, kam budeme čas zapisovat:

// objekt pro hru
let hra = {
  element: document.getElementById('hra'),
  sirka: 900,
  vyska: 600,
  dalsiDarek: 150,
  skore: 0,
  skoreElement: document.getElementById('pocet'),
  hudba: document.getElementById('hudba'),
  zvukNaraz: document.getElementById('zvuk-naraz'),
  zvukSebrano: document.getElementById('zvuk-sebrano'),
  uvod: document.getElementById('uvod'),
  tlacitkoStart: document.getElementById('start'),
  konec: document.getElementById('konec'),
  tlacitkoZnovu: document.getElementById('znovu'),
  vysledek: document.getElementById('vysledek'),
  cas: 0,
  casElement: document.getElementById('cas')
}

Do funkce startHry přidáme nastavení výchozího času na 90 a zároveň zavoláme funkci zobrazCas, která se bude starat o zobrazení času v hlavičce hry. Ta ještě neexistuje, ale vytvoříme ji za chvíli.

Do funkce startHry přidáme:

// nastavíme výchozí čas a zobrazíme ho
hra.cas = 90;
zobrazCas();

Nyní musíme vytvořit funkci zobrazCas, která bude převádět počet vteřin uvnitř hra.cas na formát ve tvaru mm:ss (minuty:vteriny).

Pojďme si nejprve ukázat celou hotovou funkci zobrazCas a pak si vysvětlíme, co přesně dělají jednotlivé řádky. Na konec programu přidáme:

// zobrazí čas ve formátu mm:ss v hlavičce hry
function zobrazCas() {
  // nikdy nechceme zobrazit čas menší než 0,
  // takže je-li čas menší než 0, nastavíme ho na 0
  if (hra.cas < 0) {
    hra.cas = 0;
  }

  // z celkového počtu  vteřin spočítáme minuty a vteřiny
  let minuty = Math.floor(hra.cas / 60);
  let vteriny = Math.round(hra.cas - minuty * 60);
  
  // spočítané minuty a vteřiny převedeme na formát mm:ss
  let naformatovanyCas = ('00' + minuty).slice(-2) + ':' + ('00' + vteriny).slice(-2)

  // naformátovaný čas vypíšeme na obrazovku
  hra.casElement.textContent = naformatovanyCas;
}

Ve hře nikdy nechceme zobrazovat záporný čas, ale podle našich nastavených pravidel by se mohlo stát, že hráči odečteme 10 vteřin za rozbitý dárek, když už mu zbývají například jen 3 vteřiny času. Teoreticky by tedy měl mít -7 vteřin času, ale my chceme jednoduše zobrazit, že je herní čas 00:00 a hra tudíž končí. Proto na začátku funkce otestujeme, zda není čas menší než 0, a pokud ano, tak ho na 0 nastavíme.

Nyní k vlastnímu převodu počtu vteřin na formát mm:ss.

Jak z celkového počtu vteřin zjistíme, kolik je v nich obsaženo minut? Vezmeme celkový počet a vydělíme ho 60 a výsledek zaokrouhlíme směrem dolů (to dělá funkce Math.floor, pozor na velké M na začátku).

Jak zjistíme, kolik vteřin zbývá z celkového času zbývá, když odečteme celé minuty? Vynásobíme minuty, které jsme spočítali v předchozím kroku, číslem 60 a výsledek odečteme od celkového času. Protože náš herní čas může být desetinné číslo (viz. dále), tak i v našem výpočtu může vyjít počet vteřin jako desetinné číslo. Pomocí Math.round (opět velké M) číslo aritmeticky zaokrouhlíme na celé číslo.

Máme tedy spočítané samostatně minuty a vteřiny. Nyní z nich musíme sestavit textový řetězec ve tvaru mm:ss.

Kdybychom v JavaScriptu jednoduše napsali minuty + ':' + vteriny, tak v případě, že nám bude zbývat například 1 minuta a 3 vteřiny, dostaneme výsledný text '1:3'. Tak se čas ale nezapisuje, my chceme jako výsledek získat '01:03'.

Použijeme jednoduchou programátorskou fintu a před počet minut nebo vteřin připojíme dvě nuly a z výsledného textového řetězce pak vezmeme pouze poslední dvě písmena.

Máme-li například uvnitř proměnné vteriny číslo 3, tak zápis '00' + vteriny dá výsledek '003'. Když z toho výsledku vezmeme poslední dva znaky, dostaneme '03', což je přesně to, co potřebujeme.

Poslední dva znaky z textového řetězce získáme pomocí metody textovyRetezec.splice(-2).

V naší funkci tedy vezmeme poslední dva znaky z minut se dvěma nulami na začátku, připojíme za ně dvojtečku a přidáme poslední dva znaky ze vteřin se dvěma nulami na začátku. Je-li náš čas 1 minuta a 3 vteřiny, dostaneme 01:03.

Výsledný naformátovaný čas nakonec vložíme do HTML elementu v hlavičce hry.

Celé to zní velice komplikovaně a krkolomně, ale zase tak složité to není. Jen se nesmíme leknout "vzorečků", které na první pohled vypadají jako černá magie, i když se jedná pouze o jednoduchou matematiku ze základní školy.

Když teď hru spustíme, měli bychom v pravém horním rohu vidět čas 01:30. Naše funkce pro zobrazování času tedy funguje. Pojďme nyní zařídit, aby se čas odpočítával dolů k nule.

Odpočítávání času

Ve hře máme funkci aktualizujHru, která se volá 50× za vteřinu. Vytvoříme si novou funkci aktualizujCas, kterou budeme volat z funkce aktualizujHru (tj. také 50× za vteřinu). Uvnitř této funkce snížíme herní čas vždy o 0.02 (tj. padesátinu vteřiny).

JavaScript v prohlížeči ve skutečnosti nemůže zajistit, že se funkce setInterval, kterou používáme pro nastavení časovače, bude volat přesně 50× za vteřinu (nebo v jakémkoliv jiném nastaveném intervalu). Náš běžící čas se tedy oproti skutečnému času může mírně rozcházet. V naší hře je to ale momentálně ten nejjednodušší způsob, jak čas počítat, takže ho použijeme.

Na úplné přesnosti času na setiny nebo tisíciny vteřiny v této hře stejně nezáleží. Kdybychom chtěli čas počítat přesněji museli bychom to zařídit jinak, ale to by bylo na delší povídání.

Vždy po odečtení času zároveň zavoláme funkci pro jeho zobrazení na obrazovce. Na konec našeho programu přidáme:

function aktualizujCas() {
  // odečteme od času 1/50 vteřiny
  hra.cas = hra.cas - 0.02;

  // zobrazíme aktualizovaný čas
  zobrazCas();
}

Nyní musíme funkci pro odečítání času volat uvnitř funkce aktualizujHru (to je ta, co se spouští 50× za vteřinu). Celá funkce aktualizujHru bude vypadat následovně:

// funkce pro aktualizování polohy objektů na obrazovce
// spouští se 50× za vteřinu
function aktualizujHru() {
  // posuneme sáně
  posunSane();

  // posuneme padající dárky
  posunDarky();

  // otestujeme padající dárky
  otestujDarky();

  // odpocitavame do dalsiho darku
  cekejNaDalsiDarek();

  // aktualizuje čas a zjistí, zda už nedošel na 0
  aktualizujCas();
}

Na závěr

A je to. Když nyní hru spustíme, měl by čas v pravém horním rohu hry běžet. Když doběhne až na nulu, měl by na nule zůstat, ale zatím se nestane nic jiného. Hra neskončí ani se nám nepřičítají vteřiny za sebrané dárky a neodečítají vteřiny za dárky spadlé na zem. To vše do hry doplníme zítra.

Pro případ, že se ti dnes něco nepovedlo, kompletní kód hry po dnešních úpravách si můžeš prohlédnout zde.

Máš-li dotazy, diskutuj u příspěvku pro dnešní den ve facebookové události. Těšíme se na tebe opět zítra.