Textový projekt 4

V predchádzajúcej kapitole sme tvorbu projektu zanechali vo fáze, v ktorej už nie je ďaleko do úplného úspešného dokončenia. V podstate nám chýba iba použitie naposledy naprogramovaných metód v praxi (niektoNiekohoZožralhraSkončila) a vykonanie niekoľkých „kozmetických“ úprav. Jednou, z pohľadu algoritmizácie a princípov programovania, „kozmetickou“ úpravou je vypísanie pravidiel pre hráča. To, čo je z pohľadu funkčnosti softvéru menej podstatnou záležitosťou, je zároveň veľmi podstatnou záležitosťou pre hráča (alias používateľa). Pamätajte, že softvér, ktorý nemá poriadne dopracovanú komunikáciu s používateľom, sa stáva nepoužiteľný, nech funguje akokoľvek spoľahlivo.

Metóda, ktorá sa bude starať o vypísanie pravidiel, bude v tomto projekte veľmi jednoduchá. Momentálne pracujeme v textovom režime a vypísanie pravidiel bude pozostávať zo série príkazov System.out.println. Metódu nazvime vypíšPravidlá. Použijeme ju na vhodnom mieste v konštruktore. Pravidlá by sa mali vypísať ako prvé – budú hrať úlohu úvodu do hry. Ak nazrieme do konštruktora, ponúkne sa nám umiestnenie volania tejto metódy pred metódu vypíšStav:

private VlkKozaKapusta()
{
    String príkaz;
 
    // SEM!        <----
    vypíšStav();
 
    do {
    // a tak ďalej…

Po čase by sa nám ukázalo, že vždy po volaní metódy vypíšPravidlá, je zároveň vhodné vypísať pre hráča aktuálny stav, preto môžeme dve činnosti spojiť do jednej. Pozor, stále si však potrebujeme zachovať možnosť vypísania stavu nezávisle od pravidiel. Takže urobíme to, že na konci metódy vypíšPravidlá zavoláme metódu vypíšStav. To nám dá možnosť volania metódy vypíšStav samostatne a zároveň nás odľahčí od povinnosti jej volania vždy tesne po metóde vypíšPravidlá (pretože sa fakticky zavolajú obe „naraz“, resp. jedna zavolá druhú). Nová metóda bude vyzerať takto (uvádzame mierne odľahčenú verziu):

private void vypíšPravidlá()
{
    System.out.println();
    System.out.println("Prievozník vlastní plť, na ktorej prevezie okrem ");
    System.out.println("seba maximálne jedného pasažiera. Jedného dňa sa ");
    System.out.println("ocitol v situácii, keď mal bezpečne previezť na ");
    // Ďalší text príbehu…
 
    System.out.println();
    System.out.println("Ak chcete, aby prievozník na druhú stranu nič " +
        "nepreviezol, zadajte „nič“.");
    System.out.println("Opätovný výpis týchto pravidiel získate príkazom " +
        "„pravidlá“.");
    System.out.println("Na predčasné ukončenie hry zadajte „koniec“.");
    System.out.println("Veľa šťastia!");
    vypíšStav();
}

Výpis príbehu sme skrátili (kompletnú verziu metódy nájdete v skompletizovanom kóde v prílohe) a ako môžete vidieť, do výpisu sme zahrnuli aj informácie o tom, ako nechať prievozníka prejsť na druhú stranu bez nákladu, ako hru predčasne ukončiť a (čo budeme riešiť o chvíľu) ako opätovne počas hry vypísať tieto pravidlá (keby to hráč potreboval).

Teraz môžeme metódu vypíšPravidlá použiť. Prvé použitie bude na začiatku hry a druhé v jej priebehu, čiže vo vnútri cyklu do-while. Druhé volanie musíme podmieniť želaním hráča, ktorý si môže vyžiadať výpis pravidiel zadaním príkazu „pravidlá“. Chceme, aby sa pri vypísaní pravidiel nedialo nič iné, preto zaradíme funkčný celok do zloženej štruktúry if-else-if (ide v podstate o riadiacu štruktúru if-else, ktorá má v alternatívnej vetve else umiestnené podmienené spracovanie if), čiže štruktúra by v rozvinutom tvare vyzerala takto:

if (prváPodmienka)
{
    // príkazy…
}
else
{
    if (druháPodmienka)
    {
        // príkazy…
    }
}

V praxi sa tento zápis často zjednodušuje týmto spôsobom:

if (prváPodmienka)
{
    // príkazy…
}
else if (druháPodmienka)
{
    // príkazy…
}

Tento zápis sme už v rámci tohto projektu použili, vtedy sme však na túto skutočnosť neupozorňovali. Celý konštruktor bude potom vyzerať takto:

private VlkKozaKapusta()
{
    String príkaz;
    vypíšPravidlá();
 
    do {
        príkaz = načítajReťazec("Čo má prievozník previezť?");
 
        if (príkaz.equalsIgnoreCase("pravidlá"))
        {
            vypíšPravidlá();
        }
        else if (!príkaz.equalsIgnoreCase("koniec"))
        {
            prevez(príkaz);
            vypíšStav();
        }
 
    } while (!príkaz.equalsIgnoreCase("koniec"));
}

Úplne poslednou časťou, ktorú potrebujeme doprogramovať, je využitie metód niektoNiekohoZožralhraSkončila na detekciu prípadov neúspešného a úspešného dokončenia hry. Táto detekcia sa musí udiať vždy po prevezení pasažiera (prípadne prechode prievozníka na druhý breh naprázdno), preto obidve metódy využijeme v zloženej štruktúre if-else-if, ktorú umiestnime za príkazy zodpovedné za prevezenie pasažiera a výpis aktuálneho stavu. Z algoritmického hľadiska nie je medzi týmito dvoma ukončeniami rozdiel. Pre hráča však (opäť) hrá významnú rolu výpis, ktorý mu oznámi úspech alebo neúspech. A to je pri terajšom (textovom) prístupe jediný rozdiel. Pri metóde niektoNiekohoZožral (ak si spomínate) sa nemusíme zaoberať problémom výpisu, pretože sme ho (porušiac pravidlá „čistého programovania“) umiestnili priamo do metódy. Zostáva teda vyriešiť výpis oznamujúci úspech. Uvádzame len tú časť konštruktora, ktorej sa to dotýka:

// …pokračovanie
else if (!príkaz.equalsIgnoreCase("koniec"))
{
    prevez(príkaz);
    vypíšStav();
 
    if (niektoNiekohoZožral())
    {
        break;
    }
    else if (hraSkončila())
    {
        System.out.println("Hotovo! Blahoželám!");
        break;
    }
}
// pokračovanie…

Príkaz break ukončí cyklus, ktorý je k nemu najbližšie, momentálne je to cyklus do-while (iný cyklus v našom konštruktore ani nemáme). Ak by sme mali v programe viacero vnorených cyklov, museli by sme hľadať iný spôsob ukončenia. Ponúka sa možnosť zariadiť to tak, aby prestala platiť podmienka na úrovni najvyššieho cyklu (výraz: !príkaz.equalsIgnoreCase("koniec")) a to sa dá v tomto prípade zariadiť nasledujúcim príkazom, ktorým by sme nahradili každý break:

príkaz = "koniec";

Môžete to skúsiť. Efekt bude rovnaký.

(Súbor: text-final.png)

Príloha 6 – finálna fáza textového projektu

Zobraziť | Prevziať