Grafický projekt 3

V predchádzajúcej kapitole sme dokončili triedu AnimovanýObjekt, resp. prinajmenšom sme ju pripravili na základný rámec fungovania tried PrievozníkPasažier, ktoré z nej odvodíme. V rámci tejto kapitoly implementujeme štartovacie verzie spomenutých tried. Pripomíname, že hlavným zmyslom existencie triedy AnimovanýObjekt je poskytnutie spoločného rozhrania (základne) pre všetky animované objekty v hre.

Vytvorme dve nové prázdne triedy PrievozníkPasažier (pozri kapitolu Vytvorenie novej triedy – v dialógu na vytvorenie novej triedy zvolíme „Prázdna trieda“). Obe odvoďme od prievozníka (pozri kapitolu Odvodenie triedy). Ich kód bude vyzerať takto (samozrejme v samostatných súboroch):

public class Prievozník extends AnimovanýObjekt
{
}
 
public class Pasažier extends AnimovanýObjekt
{
}

Nepôjdu preložiť. Je to preto, lebo nemajú definovaný konštruktor v súlade s požiadavkami nadradenej triedy AnimovanýObjekt. Musíme zabezpečiť správnu inicializáciu. Začneme triedou Prievozník, tá je ústrednejšia a budú ju využívať aj postavičky pasažierov. Pri tvorbe triedy AnimovanýObjekt sme vytvorili všeobecnú vlastnosť prievozník, ktorá mala hru vopred pripraviť na existenciu prievozníka. Teraz na to budeme pamätať. Keďže prievozník bude len jeden, môžeme v konštruktore bez obáv predvoliť všetky jeho vlastnosti. Tým získame bezparametrový konštruktor prievozníka, ktorý bude volať konštruktor nadradenej triedy s konkrétnymi hodnotami:

public Prievozník()
{
    super("prievozník", -30, 10, -30, 115);
}

Volaniu nadradeného konštruktora sa nevyhneme, Java by nám nedovolila program preložiť. Preklad by sa zastavil na konštruktore tejto triedy s chybovým hlásením oznamujúcim, že počet argumentov nadradenej triedy sa líši od „aktuálne zadaného“ (nezadali sme žiadny). Nepomohlo by ani vytvorenie parametrického konštruktora s rovnakým počtom argumentov ako má nadradená trieda, pretože Java parametre konštruktorov neprenáša automaticky. Ak neurčíme inak, volá sa bezparametrový konštruktor nadradenej triedy, ktorý momentálne pre triedu AnimovanýObjekt nejestvuje. Jestvoval by v prípadoch, keď by sme nedefinovali žiadny konštruktor (ani jeden), prípadne ak by sme bezparametrový konštruktor definovali explicitne…

Teraz už trieda Prievozník preložiť pôjde. Okrem povinného volania nadradeného konštruktora nastavíme v rámci procesu inicializácie niektoré vlastnosti prievozníka zdedené ešte od nadradených tried AnimovanýObjektGRobot (domovskú pozíciu, zrýchlenie, kotviace pozície). Nakoniec zavoláme statickú metódu prievozník (definovali sme ju v triede AnimovanýObjekt) s argumentom this (tento), čím oznámime prostrediu, že „toto je prievozník“ – „ja som prievozník“.

public Prievozník()
{
    super("prievozník", -30, 10, -30, 115);
 
    domov(50, -25, 0);
    zrýchlenie(2, false);
    nastavĽavý(domaX() - 50, domaY());
    nastavPravý(domaX() + 50, domaY());
 
    prievozník(this);
}

Prakticky všetky parametre prievozníka sú tu zadané „napevno“ – numerickými literálmi (terminologická pripomienka – používame termín „literál“, termínu „konštanta“ by sme sa mali v tomto kontexte vyhýbať, pretože tento termín v programovaní označuje „nemennú hodnotu označenú identifikátorom“). Flexibilitu by sme mohli zvýšiť tak, že niektoré parametre by sme určili pomocou premenných, argumentov či konštánt. Napríklad polohu prievozníka na jednotlivých brehoch by sme mohli určiť pomocou konštanty určujúcej rozptyl vzhľadom na domovskú pozíciu (čiže to, o koľko bodov doľava, resp. doprava je umiestnený kotviaci bod ľavého, resp. pravého brehu voči domovskej pozícii). Momentálne nastavujeme kotviace body na 50 bodov od domovskej pozície (volaním metód nastavĽavýnastavPravý). Ak sme si istí, že rozptyl bude za každých okolností rovnaký, definujeme konštantu rozptylBrehov:

private final static int rozptylBrehov = 50;

ktorú použijeme vo volaní metód nastavĽavýnastavPravý v konštruktore:

nastavĽavý(domaX() - rozptylBrehov, domaY());
nastavPravý(domaX() + rozptylBrehov, domaY());

Teraz, hocikedy, keď budeme potrebovať zväčšiť rozptyl kotviacich bodov na brehoch, postačí zvýšiť hodnotu z 50 napríklad na 90. Konštantami môžeme zároveň zvýšiť čitateľnosť programu. Lepšie sa číta kód, kde sú konštantné hodnoty pomenované, než kód plný čísiel. Ak je definícia konštanty vyslovene zbytočná, môžeme zvýšiť čitateľnosť kódu komentármi. Porovnajte verziu vyššie uvedeného konštruktora s nasledujúcou verziou doplnenou o definované konštanty:

private final static int rozptylBrehov = 90;
private final static int posunGrafikyX = -30;
private final static int posunGrafikyY = 10;
private final static int posunPopisuX = -30;
private final static int posunPopisuY = 115;
 
public Prievozník()
{
    super("prievozník", posunGrafikyX, posunGrafikyY, posunPopisuX, posunPopisuY);
 
    domov(50, -25, 0);    // domovská pozícia [x, y] a domovský uhol
    zrýchlenie(2, false);
    nastavĽavý(domaX() - rozptylBrehov, domaY());
    nastavPravý(domaX() + rozptylBrehov, domaY());
 
    prievozník(this);
}

V predchádzajúcej verzii bol prvý riadok plný čísiel bez jasného významu. Teraz obsahuje slová opisujúce význam hodnôt. Význam konštánt sa násobí, ak potrebujeme v rámci programu používať rovnakú hodnotu v tom istom kontexte.

Keďže väčšinu funkcionality sme naprogramovali v triede AnimovanýObjekt, máme základ triedy Prievozník hotový. Ešte ju obohatíme o možnosť automatického prechádzania prievozníka z jedného brehu na druhý. Naprogramujeme metódu prejdiRieku:

public void prejdiRieku()
{
    if (pravýBreh())
        choďNaĽavý();
    else
        choďNaPravý();
}

Čiže: „ak som na pravom brehu, prejdi na ľavý a naopak.“ Ak teraz v BlueJ-i vytvoríme novú inštanciu prievozníka a spustíme metódu prejdiRieku, uvidíme pohybujúceho sa prievozníka.

Pristúpme k tvorbe triedy Pasažier. Pri jednotlivých pasažieroch budeme chcieť definovať všetky argumenty prijímané konštruktorom triedy AnimovanýObjekt individuálne, preto definujeme konštruktor, ktorý ich bude všetky prijímať a posúvať nadradenému konštruktoru. Zároveň, keďže budeme chcieť, aby bola chôdza každého pasažiera animovaná (ako sme si to pokusne vyskúšali pri vlkovi), môžeme priamo do konštruktora umiestniť volanie metódy umožniAnimáciuChôdze:

public Pasažier(String meno, double posunX, double posunY,
    double posunPopisuX, double posunPopisuY)
{
    super(meno, posunX, posunY, posunPopisuX, posunPopisuY);
    umožniAnimáciuChôdze();
}

Pre jednotlivých pasažierov budeme potrebovať nastaviť rôzne kotviace pozície na brehoch. Podobne ako pri prievozníkovi si môžeme situáciu uľahčiť zrkadlovým umiestnením. Môžeme na to vyrobiť jednoduchú metódu prijímajúcu súradnice bodu na pravom brehu, z ktorých odvodíme zrkadlovú pozíciu na ľavom brehu:

public void pozíciaNaBrehu(double x, double y)
{
    nastavĽavý(-x, y);
    nastavPravý(x, y);
}

V tomto momente je najvyšší čas navrhnúť štýl ovládania hry. Navrhujem vyriešiť to čo najjednoduchšie. Hráč klikne na postavičku, s ktorou chce manipulovať, a tá sa automaticky pohne podľa toho, v akom je momentálnom rozpoložení. Čiže ak bude na niektorom z brehov, overí, či sa prievozník nachádza na rovnakom brehu, a ak áno, prejde na palubu plte, a naopak, ak je na palube, vystúpi na ten breh, pri ktorom sa prievozník momentálne nachádza. To vyžaduje detekciu troch rôznych stavov. Na to je vhodné použiť riadiacu štruktúru switch. Metódu nazvime choď. Všetko potrebné na jej implementáciu máme pripravené. Stačí to správne použiť:

public void choď()
{
    switch (pozícia())
    {
        case pravýBreh:
            if (prievozník().pravýBreh())
                choďNaPrievozníka();
            break;
 
        case ľavýBreh:
            if (prievozník().ľavýBreh())
                choďNaPrievozníka();
            break;
 
        case prievozník:
            if (prievozník().ľavýBreh())
                choďNaĽavý();
            else
                choďNaPravý();
            break;
    }
}

Teraz môžeme pristúpiť k vytvoreniu hlavnej triedy, pomocou ktorej všetky doteraz naprogramované komponenty prepojíme. Vytvoríme hlavnú triedu (v dialógu na vytvorenie triedy zvolíme „Hlavná trieda aplikácie“) s názvom HlavnáTrieda. Po prečistení a úpravách vygenerovaného kódu získame takýto zjednodušený tvar:

public class HlavnáTrieda extends GRobot
{
    private HlavnáTrieda()
    {
    }
 
    public static void main(String[] args)
    {
        new HlavnáTrieda();
    }
}

Do neho budeme postupne vpisovať potrebný kód. V prvom rade je nutné vytvoriť všetky inštancie postáv hry, s ktorými budeme manipulovať. Súradnice relatívneho posunu grafiky a popisného textu pri pasažieroch by sme predbežne stanovili odhadom a neskôr prispôsobili – metódou pokusu a omylu. Vo výsledku by mohli byť definície takéto:

private final Prievozník prievozník = new Prievozník();
private final Pasažier vlk = new Pasažier("vlk", 0, 0, 0, 65);
private final Pasažier koza = new Pasažier("koza", 0, -10, -5, 50);
private final Pasažier kapusta = new Pasažier("kapusta", 0, -25, 0, 25);

Ak teraz spustíme hlavnú triedu, všetky postavičky sa zobrazia vzájomne prekryté v strede plátna:

{Súbor: figures-00.png}

Samozrejme, zatiaľ nijaká z nich nereaguje (napr. na klikanie), to sme zatiaľ neriešili. Môžeme ich aspoň príkazovo navigovať k jednému z brehov. Ak do konštruktora napíšeme:

prievozník.choďNaPravý();
vlk.choďNaPravý();
koza.choďNaPravý();
kapusta.choďNaPravý();

Keď triedu spustíme, zistíme, že zo všetkých postáv sa iba prievozník vydá k neviditeľnému pravému brehu. Na čo sme zabudli…? Pozrime sa podrobne na triedu Pasažier. Zdá sa, že všetko máme… Všetko… až na to, že sme doteraz nikde nevolali metódu pozíciaNaBrehu…(!) Ak metódu nezavoláme, akoby nejestvovala. Je to ako napísať recept, ale nečítať ho, iba ho založiť do kuchárskej knihy a nikdy nepoužiť… Máme niekoľko možností, ako situáciu doriešiť. Buď metódu zavoláme v konštruktore HlavnáTrieda, alebo vygenerujeme náhodné súradnice v určitom rozsahu a volanie pridáme na koniec konštruktora Pasažier, alebo pridáme ďalšie dva argumenty do konštruktora Pasažier a nimi inicializujeme pozície na brehoch. Osobne by som volil tú poslednú možnosť.

Pozastavme sa teraz nachvíľu. Predstavme si, že nie sme autormi triedy Pasažier. O akékoľvek zmeny musíme požiadať autora, spoluriešiteľa v rámci tímu. Požiadavka musí byť čo najpresnejšia. Mala by obsahovať, s čím zmena súvisí, aké poskytujeme vstupy, aké očakávame výstupy, prípadne stručné zdôvodnenie požiadavky a podobne. Požiadavku zaradíme do zoznamu požiadaviek (wish-list) a ak je to možné, naprogramujeme náš kód tak, ako by už bolo požiadavke vyhovené (ibaže s testovaním budeme musieť vyčkať).

Požiadavka na novú funkcionalitu by mohla vyzerať relatívne jednoducho:

  • Umiestnenie: trieda Pasažier, konštruktor;
  • Opis: vytvorenie novej verzie konštruktora alebo úprava jestvujúcej verzie – pridanie dvoch nových vstupov;
  • Vstupy: pozícia na brehu určená dvoma premennými typu double;
  • Výstupy: objekt s inicializovanými kotviacimi pozíciami na oboch brehoch;
  • Zdôvodnenie: žiadame pridanie inicializácie kotviacich bodov na brehoch do konštruktora; zjednoduší sa tým pre nás proces inicializácie nových objektov typu Pasažier.

Formát požiadavky môže vyzerať v rámci každej organizácie rôzne. Individuálne rozdiely sa môžu dotýkať formátovania, rozloženia, niektoré položky môžu byť nepovinné a podobne. Požiadavku by sme odoslali kolegovi (autorovi triedy) a počas čakania by sme sa museli zaoberať inými záležitosťami, pretože kým by našej požiadavke nebolo vyhovené, nemohli by sme podľa nej upraviť svoj kód. Zoznam požiadaviek vždy môžeme využiť aj na zlepšenie organizácie našej vlastnej práce. Pomôže nám pri orientácii v množstve úloh, ktoré často čakajú pri programovaní na jedného pracovníka.

Predpokladajme, že uplynul nejaký čas a kolega našej požiadavke vyhovel: konštruktoru Pasažier pridal dva argumenty typu doublepozíciaNaBrehuXpozíciaNaBrehuY, ktoré teraz využijeme. V rámci nášho projektu musíme kolegovu prácu zastúpiť – pridajte požadované argumenty do hlavičky konštruktora a na koniec tela konštruktora zaraďte volanie metódy pozíciaNaBrehu:

pozíciaNaBrehu(pozíciaNaBrehuX, pozíciaNaBrehuY);

Teraz môžeme upraviť definície pasažierov v hlavnej triede. Pridajme na záver každej inicializácie dve hodnoty (opäť, súradnice zistíme empiricky):

private final Pasažier vlk = new Pasažier("vlk", 0, 0, 0, 65, 200, 10);
private final Pasažier koza = new Pasažier("koza", 0, -10, -5, 50, 280, 10);
private final Pasažier kapusta = new Pasažier("kapusta", 0, -25, 0, 25, 360, 10);

Spustime hru. Teraz sa na svoju pozíciu vydajú všetky postavičky, ibaže zatiaľ akosi nemajú dostatok miesta. Kapusta vyjde úplne za hranice okna, až k okraju plátna, ktorý uvidíme až keď okno zväčšíme. Ak sa nechceme uspokojiť s predvolenými rozmermi plátna a okna, môžeme ich upraviť podľa potrieb v konštruktore hlavnej triedy. (Upozorňujeme, že volanie nadriadeného konštruktora – super() – musí byť vždy úplne prvým príkazom konštruktora odvodenej triedy!) Hlavná trieda je odvodená priamo od triedy GRobot. Jej konštruktory umožňujú pozmeniť predvolenú veľkosť plátna, avšak túto zmenu môžeme urobiť v rámci jedného projektu iba raz. Ďalším príkazom svet.zbaľ prispôsobíme veľkosť okna rozmerom plátna. Zmeňme plátno s oknom na panoramatický rozmer:

super(800, 300, "Prievozník…");
svet.zbaľ();

V tejto fáze kapitolu ukončíme. V najbližšej kapitole sa budeme venovať ovládaniu hry.

Príloha 10 – prvé verzie tried Prievozník, Pasažier a HlavnáTrieda

Zobraziť | Prevziať