Grafický projekt 5

V predchádzajúcej kapitole sme dokončili ovládanie hry. V tejto kapitole sa budeme zaoberať tromi záležitosťami: 1. úspešným a neúspešným dokončením hry, 2. vykreslením grafiky prostredia a 3. jednoduchému ozvučeniu postáv.

Prvou zo spomenutých záležitostí sme sa v rámci tohto materiálu už raz zaoberali. V textovom projekte sme riešili rovnaký problém. Ak si spomeniete, v metódach niektoNiekohoZožralhraSkončila textového projektu sme porovnávali prítomnosť postáv na jednom či druhom brehu. Spôsob riešenia bude čiastočne rovnaký i v grafickej verzii projektu. To, na ktorom z brehov sa pasažier práve nachádza, vieme určiť ľahko. Trieda AnimovanýObjekt má na to definované potrebné metódy. Stačí ich vhodne použiť.

Pozor! Dokončenie hry neznamená ukončenie aplikácie. Také riešenie by som nepovažoval sa správne! (Už som sa s niečím takým stretol…)

Najjednoduchšie bude zaradiť kontrolu do prekrytej metódy aktivita v hlavnej triede. Začnime detekciou dokončenia hry, pretože podmienka na úspešné dokončenie hry je jednoduchšia. Všetci pasažieri musia byť na ľavom brehu:

@Override public void aktivita()
{
    if (vlk.ľavýBreh() && koza.ľavýBreh() && kapusta.ľavýBreh())
    {
        deaktivuj();
        svet.správa("Všetci pasažieri sú úspešne prevezení na ľavý breh!");
    }
}

Dokončenie sme zariadili jednoducho, deaktivovali sme hlavného robota a vypísali sme správu oznamujúcu úspech. Skúsme previezť všetkých pasažierov na druhý breh. Môžeme v ľubovoľnom poradí. Hra oznámi hráčov úspech trochu predčasne: hneď, ako prikážeme poslednému pasažierovi, aby vystúpil. Nepôsobí to prirodzene. Zariaďme to preto tak, aby kontrola (ne)úspešnosti hráčovho počínania si prebiehala len vtedy, ak nie je žiadny pasažier v pohybe. Nasledujúci riadok kódu umiestnený na začiatku metódy aktivita to zabezpečí:

if (vlk.vPohybe() || koza.vPohybe() || kapusta.vPohybe()) return;

Možno by bolo vhodné pridať do podmienky aj prievozníka… (To nechám na posúdenie čitateľa.)

Keď pasažierov prevezieme na ľavý breh teraz, správa sa zobrazí až po zastavení posledného z nich. Hlavný robot je deaktivovaný, preto prestane fungovať mechanizmus skrývania a zobrazovania popisov postavičiek, ale blokovanie ich ovládania sme zatiaľ nijako nezabezpečili. Ak klikneme na hociktorú postavu (vrátane prievozníka), pohne sa. Otázka znie: kam umiestniť vhodný kód na zablokovanie ovládania postáv?

Postavy ovládame myšou prostredníctvom reakcie klik v obsluhe udalostí. Deaktivovanie ktoréhokoľvek z robotov nemá žiadny vplyv na to, či sa spustí alebo nespustí obsluha udalosti kliknutia myšou. Taktiež nemáme možnosť priameho blokovania obsluhy udalostí. Jediný spôsob je umiestniť na začiatok reakcie (reakcie klik) obmedzujúcu podmienku, ktorou ju „odstavíme“. Zamyslime sa nad tým, čo bude najvhodnejšie využiť na tento účel. V podstate potrebujeme, aby sa ovládanie zablokovalo «po» dokončení hry. Dokončenie hry sa snažíme signalizovať hlavnému robotovi jeho deaktivovaním. Presne tento stav – stav aktivity hlavného robota – môžeme využiť na odstávku reakcie na kliknutie. Ak je hlavný robot neaktívny, nech sa reakcia obsluhy udalostí nevykoná:

if (neaktívny()) return;

Podobne zabezpečíme problém neúspechu hráča. Pozrime sa, ako sme to riešili v textovej verzii projektu:

private boolean niektoNiekohoZožral()
{
    boolean vlkNaĽavom = jeNaĽavomBrehu("vlk");
    boolean kozaNaĽavom = jeNaĽavomBrehu("koza");
    boolean kapustaNaĽavom = jeNaĽavomBrehu("kapusta");
 
    if ((vlkNaĽavom && kozaNaĽavom &&
        !kapustaNaĽavom && prievozníkJeNaPravomBrehu) ||
        (!vlkNaĽavom && !kozaNaĽavom &&
        kapustaNaĽavom && !prievozníkJeNaPravomBrehu))
    {
        System.out.println("Vlk zožral kozu!");
        return true;
    }
 
    if ((kozaNaĽavom && kapustaNaĽavom &&
        !vlkNaĽavom && prievozníkJeNaPravomBrehu) ||
        (!kozaNaĽavom && !kapustaNaĽavom &&
        vlkNaĽavom && !prievozníkJeNaPravomBrehu))
    {
        System.out.println("Koza zožrala kapustu!");
        return true;
    }
 
    return false;
}

Skúsme využiť rovnaké podmienky, iba prepíšme premenné na vlastnosti objektov:

if ((vlkavýBreh() && kozaavýBreh() &&
    !kapustaavýBreh() && prievozník.pravýBreh()) ||
    (!vlkavýBreh() && !kozaavýBreh() &&
    kapustaavýBreh() && !prievozník.pravýBreh()))
{
    deaktivuj();
    svet.správa("Vlk zožral kozu!");
}
 
if ((kozaavýBreh() && kapustaavýBreh() &&
    !vlkavýBreh() && prievozník.pravýBreh()) ||
    (!kozaavýBreh() && !kapustaavýBreh() &&
    vlkavýBreh() && !prievozník.pravýBreh()))
{
    deaktivuj();
    svet.správa ("Koza zožrala kapustu!");
}

Kód umiestnime do metódy aktivita hlavného robota a hru spustíme. Nespráva sa korektne. Dôvodom je tretí stav určujúci prítomnosť postavy na palube plte. Ten situáciu značne odlišuje od tej, akú sme mali v textovej verzii. Pri nej sme rozlišovali iba dva stavy: prítomnosť na ľavom alebo pravom brehu rieky. Zamyslime sa nad situáciou z iného pohľadu. Keď sa pozrieme na tabuľku stavov hry prevzatú z kapitoly textový projekt 3:

Vlk

Koza

Kapusta

Prievozník

Komentáre

pravý

pravý

pravý

pravý

[počiatočný stav]

pravý

pravý

pravý

ľavý

pasažieri sa strážia navzájom

pravý

pravý

ľavý

pravý

kozu stráži prievozník

pravý

pravý

ľavý

ľavý

vlk zožral kozu

pravý

ľavý

pravý

pravý

nikto nikoho neohrozuje

pravý

ľavý

pravý

ľavý

nikto nikoho neohrozuje

pravý

ľavý

ľavý

pravý

koza zožrala kapustu

pravý

ľavý

ľavý

ľavý

kapustu stráži prievozník

ľavý

pravý

pravý

pravý

kapustu stráži prievozník

ľavý

pravý

pravý

ľavý

koza zožrala kapustu

ľavý

pravý

ľavý

pravý

nikto nikoho neohrozuje

ľavý

pravý

ľavý

ľavý

nikto nikoho neohrozuje

ľavý

ľavý

pravý

pravý

vlk zožral kozu

ľavý

ľavý

pravý

ľavý

kozu stráži prievozník

ľavý

ľavý

ľavý

pravý

alternatívny koniec hry

ľavý

ľavý

ľavý

ľavý

[koniec hry]

a zároveň sa nad situáciou zamyslíme z praktického hľadiska, nájdeme riešenie problému hľadaním odpovede na otázku: „Čo majú spoločné tie stavy hry, ktoré nás v tomto okamihu zaujímajú, čiže situácie znamenajúce hráčov neúspech?“

Pri neúspechu sú vždy kritickí pasažieri na spoločnom brehu (ich stavy sa rovnajú), pričom ostatné dve postavy hry musia byť buď na opačnom brehu rieky, alebo na plti (prevzatá tabuľka síce s plťou pre jednoduchosť nepočíta, ale my v skrytosti budeme). Pripomeňme, že máme dve dvojice kritických pasažierov a naraz môžeme kontrolovať len jednu z nich. Vyberme si jednu kritickú dvojicu (vlk a koza alebo koza a kapusta) a vytvorme podmienku.

  • Prvá časť podmienky striehnucej na hráčovo pochybenie vzájomne porovná stavy oboch postáv kritickej dvojice (či sa rovnajú).
  • Ďalšie dve časti podmienky spojenej operátorom „a súčasne“ budú porovnávať stavy ostatných dvoch postáv so stavom ľubovoľnej postavy z kritickej dvojice (či sú rôzne).

(Preto môžeme porovnávať ostatné postavy s ľubovoľnou postavou z kritickej dvojice, lebo ak má útočník svoju obeť zožrať, musia byť obaja – obeť i útočník – na rovnakom brehu, čiže je jedno, s ktorou z nich ďalšiu postavu porovnávame.)

Stav každej postavy dokážeme zistiť pomocou metódy pozícia z triedy AnimovanýObjekt. Pre kritickú dvojicu vlka a kozy bude kód overujúci hráčove pochybenie vyzerať takto:

if (vlk.pozícia() == koza.pozícia() &&
    kapusta.pozícia() != vlk.pozícia() &&
    prievozník.pozícia() != vlk.pozícia())
{
    deaktivuj();
    svet.správa("Vlk zožral kozu!");
}

Rovnaký manéver vykonáme pre druhú kritickú dvojicu:

if (koza.pozícia() == kapusta.pozícia() &&
    vlk.pozícia() != koza.pozícia() &&
    prievozník.pozícia() != kapusta.pozícia())
{
    deaktivuj();
    svet.správa ("Koza zožrala kapustu!");
}

Otestujme správanie sa hry. Hlásenie o neúspechu je zobrazené okamžite po zmene stavu pltníka (jeho vyštartovaní na opačný breh). Riešenie som už naznačoval – nájsť ho bude vašou úlohou.

V poradovníku sa ocitlo dokončenie ďalšej záležitosti – grafiky prostredia. Na nakreslenie prostredia využijeme hlavého robota. V hlavnej triede naprogramujeme súkromnú metódu, do ktorej budeme umiestňovať všetky príkazy kreslenia. V prvom rade skryjeme hlavného robota:

private void nakresliProstredie()
{
    skry();
}

Volanie metódy nezabudneme umiestniť na záver konštruktora. Príkaz môžeme umiestniť tesne pred aktiváciu hlavného robota. Najskôr nakreslíme oblohu a vodu. Oblohu nakreslíme vyplnením podlahy („podlahou“ máme na mysli názov jedného z kresliacich plátien vo svete robota – pozri dokumentáciu skupiny tried GRobot) tyrkysovou farbou a na nakreslenie vody použijeme obdĺžnik vyplnený modrou farbou:

// Obloha
podlaha.vyplň(tyrkysová);
 
// Voda
skočNa(0, -100);
farba(modrá);
vyplňObdĺžnik(405, 55);

Výsledok bude jednoduchý:

{Súbor: environment-01.png}

Prvý nápad na nakreslenie brehov je použiť dve tmavozelené elipsy:

// Brehy
skočNa(-500, -300);
farba(tmavozelená);
vyplňElipsu(500, 350);
 
skočNa(500, -300);
vyplňElipsu(500, 350);

Výsledok nie je ideálny:

{Súbor: environment-02.png}

Po pridaní grafických objektov postáv, pôsobí tento tvar brehov ako z inej perspektívy:

{Súbor: environment-02b.png}

Nie je to prirodzené. Nepožadujeme dokonalosť, ale skúsme aspoň naznačiť tok rieky rozširujúci sa smerom k nám. Použijeme niekoľko elíps nakreslených pod seba so zväčšujúcou sa vzdialenosťou od stredu. Ak chceme využiť relatívny posun robota, musíme nakresliť každý breh v samostatnom cykle:

// Ľavý breh
farba(tmavozelená);
skočNa(-350, -50);
 
for (int i = 0; i < 5; ++i)
{
    vyplňElipsu(200, 60);
    choď(-15, -30);
}
 
// Pravý breh
skočNa(350, -50);
 
for (int i = 0; i < 5; ++i)
{
    vyplňElipsu(200, 60);
    choď(15, -30);
}

{Súbor: environment-03.png}

Nakoniec dokreslime slnko a prostredie máme hotové:

// Slnko
farba(svetložltá);
skočNa(-220, 180);
kruh(95);

{Súbor: environment-04.png}

Po grafickej stránke je hra v podstate dokončená. V tomto stave ide už iba o dolaďovanie detailov. Doteraz sa všetko dotýkalo grafiky a ovládania. Hráči istotne ocenia spestrenie hry zvukom. Hoci veľmi jednoduchým. Takmer všetko potrebné na jednoduché ozvučenie už máme pripravené. Do triedy AnimovanýObjekt ešte doprogramujme jednoduchú metódu:

public void vydajZvuk()
{
    svet.zvuk(zvuk);
}

Využijeme ju na to, aby postavička vydala zvuk vždy vtedy, keď na ňu hráč klikne, v dôsledku čoho sa pohne. Keby sme chceli, aby postavička vydala zvuk pri každom pohybe, umiestnili by sme volania metódy vydajZvuk na do tela každej z metód choďNaĽavý, choďNaPravýchoďNaPrievozníka. Ibaže to si neželáme. Preto volania umiestnime do obsluhy udalostí v hlavnej triede – do reakcie klik:

if (vlk.myšV())
{
    vlk.choď();
    vlk.vydajZvuk();
}
else if (koza.myšV())
{
    koza.choď();
    koza.vydajZvuk();
}
else if (kapusta.myšV())
{
    kapusta.choď();
    kapusta.vydajZvuk();
}
else if (prievozník.myšV())
{
    prievozník.prejdiRieku();
    prievozník.vydajZvuk();
}

Principiálne môžeme považovať hru za dokončenú. Zlepšovanie detailov, či už grafických alebo funkčných, ponecháme na čitateľovi. Z tých funkčných môžeme niektoré zlepšenia naznačiť: počiatočné umiestnenie postáv (teraz všetky dokráčajú na počiatočné miesto zo stredu plátna), pridanie ponuky (s možnosťami a predvoľbami, napríklad s možnosťou opätovného spustenia hry…), zmiznutie alebo animovanie zožratej postavy a podobne. V súlade s tým by sme mohli využiť nadobudnuté poznatky a definovať, napríklad, novú vymenovaciu triedu na účely rozlišovania rôznych stavov hry:

public enum Stav
{
    prebieha, hotovo, jemKapustu, jemKozu, kapustaZjedená, kozaZjedená
}

Môžeme definovať ďalšie grafické objekty, ale to už skutočne ponecháme na čitateľa.

Veríme, že ste sa pri tomto sprievodcovi veľa naučili. Tešíme sa na ďalšie programovacie stretnutie pri podobnom materiáli v budúcnosti…

Príloha 12 – posledná verzia triedy HlavnáTrieda

Zobraziť | Prevziať