4it101»Adv Postava

Adv Postava

Postavy

Ve hře mohou mít postavy různé schopnosti:

  • postava stále říká stejnou větu,
  • s postavou je možné si vyměňovat věci - dáte trpaslíkovi jídlo a on Vám za ně dá meč,
  • postava poskytuje různé rady v závislosti na tom, jak hráč plní úkoly - toto bude popsáno u úkolů,
  • postava (např. revizor) se náhodně pohybuje (např. po linkách MHD),
  • postavu (např. draka) je možné zabít, k zabití potřebujete mít nějakou věc (např. meč).

Pokud máte ve hře více postav, tak je vhodné, aby kód třídy Postava (nebo Osoba nebo Netvor nebo ...) byla průnikem vlastností všech postav

Postava stále říká stejnou větu

To je asi nejjednodušší případ. Co je potřeba učinit:

1. vytvořit třídu Postava
Třída Postava bude mít dva datové atributy - jmeno a proslov. Asi bych oba nastavil v konstruktoru, doplnil ještě metody getJmeno() a getMluv().
2. doplnit třídu Mistnost o seznam osob
Třída místnost by měla obsahovat seznamOsob v místnosti, což je velmi podobné seznamu věcí v místnosti. K seznam osob patří metody public void vlozOsobu(Osoba osoba) a public Osoba najdiOsobu(String jmeno). Měl se též vypsat seznam osob v místnosti - buď při příchodu do místnosti (tj. při příkazu jdi či v příkazu prozkoumej). Pokud můžete mít v místnosti pouze jednu osobu, ještě se to trochu zjednoduší.
3. příkaz mluv
Je potřeba vytvořit třídu PrikazMluv, která se spustí, pokud uživatel zadá např. příkaz "mluv hostinský". V metodě proved() této třídy je potřeba zkontrolovat, zda byl zadán parametr, zda je dotyčná osoba v aktuální místnosti a poté vrátit/zobrazit proslov příslušné osoby.

S postavou je možné si vyměňovat věci

Předchozí případ se rozšiřuje o věc, kterou má u sebe postava a o věc, kterou chce výměnou. Hráč bude moci zadávat příkazy "mluv postava" a "dej chleba postava".

1. vytvořit třídu Postava
Třída Postava bude mít nyní více datových atributů
String jmeno
jméno postavy,
Vec coChce
věc, kterou postava chce (např. trpaslík chce jídlo),
Vec coMa
věc, kterou dostane hráč po úspěšné výměně,
String mluvPred
řeč, kterou pronese postava před výměnou - po zadání příkazu "mluv postava",
String mluvPo
co postava bude říkat po výměně - po zadání příkazu "mluv postava",
String recNechce
co postava řekne při výměně, když nabízenou věc nechce. Je vhodné nastavit standardní odmítající řeč.
String recChce
co postava řekne při výměně, když si nabízenou věc vezme a vrátí svoji,
boolean probehlaVymena=false
boolovská hodnota udává, zda proběhla výměna,
Třída postava by měla mít i odpovídající metody:
Postava(String jmeno, String proslov)
stejný konstruktor jako v předchozím případě, proslov se uloží do datového atributu mluvPred, to umožní, aby existovali postavy, co pouze mluví (na kterýkoliv příkaz dej odpoví připravenou standardní řečí),
void nastavVymenu(Vec coMa, Vec coChce, String recNechce, String recChce, String mluvPo)
nastaví zbývající datové atributy,
String getMluv()
vrátí odpověď na příkaz mluv, tj. buď obsah proměnné mluvPred či proměnné mluvPo v závislosti na obsahu datového atributu probehlaVymena,
Vec vymena(Vec nabidka)
metoda se použije v PrikazDej, parametrem je věc, kterou hráč nabízí. Metoda vrací buď hodnotu null (pokud byla špatná nabídka) nebo věc vrácenou postavou (obsah datového atributu coMa). Při úspěšné výměně se nastaví datový atribut probehlaVymena na hodnotu true (pokud již jednou výměna proběhla, tak by se neměla opakovat).
String getRecVymena()
metoda by se měla volat po zavolání metody vymena a bude vracet buď obsah datového atributu recChce či recNechce v závislosti na tom, zda výměna byla úspěšná či ne. Optimální by bylo, kdy se tato řeč vracela jako druhá návratová hodnota v metodě vymena. To nejde udělat přímo, musí se vytvořit pomocný typ, který přenese současně dvě hodnoty, instanci třídy Vec a příslušný proslov.
2. seznam osob v místnostech
Je potřeba třídu Mistnost rozšířit o seznam osob a metody, které s tímto seznamem souvisejí - viz předchozí varianta postavy.
3. příkaz mluv
Též je potřeba připravit příkaz mluv, stejné jako v předchozí variantě,
4. příkaz dej
Zde je potřeba doplnit třídu PrikazDej pro příkaz dej (např. "dej chleba trpaslík"). V metodě proveď je potřeba zkontrolovat, že jsou zadány dva parametry. Dále se musí zkontrolovat, zda je v aktuální místnosti zadaná osoba a zda má hráč u sebe věc, kterou nabízí. Pokud ano, tak se zavolá u postavy metoda vymena - pokud se výměna povede, tak se nová věc dá do batohu. Pokud se výměna nepovede, je potřeba vrátit do batohu nabízenou věc. V obou případech se zavolá metoda getRecVymena(), která vrátí řeč postavy k uskutečněné výměně.

Postava se pohybuje po plánu

Může být několik variant pohybu postav po plánu:

  • hráč s postavou promluví (něco ji dá, zabije ji) a ta poté zmizí z celé hry - toto je nejjednodušší, po provedené akci se postava odebere z místnosti,
  • postava přechází mezi místnostmi. Postava by měla evidovat místnosti, mezi kterými může přecházet (není nutné, pokud může procházet všemi místnostmi). Je nutné dále rozhodnout:
    • zda postava přechází náhodně či po nějaké akci (např. pokud s ní hráč promluví, tak přejde). Při náhodném pohybu se musí též určit okamžik, kdy se pohnou - po každém příkazu zadaném uživatelem, v příkazu jdi (asi nejčastější) či někdy jindy. Při náhodném pohybu musí existovat v herním plánu seznam postav.
    • zda se budou postavy evidovat v místnostech či zda atributem postavy je místnost, ve které se nachází,

Já rozšířím základní adventuru o dvě postavy - uklízečku a vrátného, které se budou náhodně pohybovat při zadání příkazu jdi - těsně před tím, než hráč přejde do nové místnosti. U postav se bude evidovat místnost, ve které se nacházejí, třída HerniPlan bude vracet seznam osob v aktualní místnosti.

a. třída Postava - datové atributy
Třída Postava bude mít tři datové atributy
    private String jmeno;
    private Mistnost aktMistnost;
    private List <Mistnost> mistnosti;  // místnosti, do kterých může chodit
b. třída Postava - metody
Přes konstruktor budu nastavovat jméno a počáteční místnost, ve které bude postava umístěna. Dále zde budou dvě přístupové metody a metoda pro nastavení místností, do kterých může postava vstoupit. V této metodě použiji proměnlivý počet parametrů.
    public Postava(String jmeno, Mistnost pocatecni) {
        this.jmeno = jmeno;
        aktMistnost = pocatecni;
        mistnosti = new ArrayList<Mistnost>();
    }

    public String getJmeno() {
        return jmeno;
    }

    public Mistnost getAktMistnost() {
        return aktMistnost;
    }

    public void pridejMistnosti(Mistnost ... dalsiMistnosti) {
        mistnosti.addAll(Arrays.asList(dalsiMistnosti));
    }
c. třída Postava - metoda prejdi
První varianta metody prejdi umožňuje náhodný přesun mezi místnostmi nezávisle na na existenci východů. Ve druhé variantě se berou v úvahu dveře. Navíc postava může zůstat v místnosti - závisí na náhodě.
    // nejjednodušší varianta - postava přelétne do náhodné
    // přípustné místnosti bez ohledu na existenci dveří či průchodů
     public void prejdi() {
         if (! mistnosti.isEmpty()) {
             Collections.shuffle(mistnosti); // náhodné setřídění
             aktMistnost = mistnosti.get(0);
         }
     }
    // postava přejde do libovolné sousední místnosti,
    // pokud je na seznamu přípustných místností,
    public void prejdi() {
        if (random.nextDouble() < PRAVDEPODOBNOST_PRESUNU && ! mistnosti.isEmpty()) {
            Collections.shuffle(mistnosti); // náhodné setřídění
            for (Mistnost mozna : mistnosti) {
                Mistnost soused = aktMistnost.sousedniMistnost(mozna.getNazev());
                if (soused != null) {
                    aktMistnost = soused;
                    break;
                }
            }
        }
    }
d. třída HerniPlan - seznam postav a vytvoření postav
V herním plánu se vytvoří datový atribut pro seznam postav, vytvoří se jednotlivé postavy. K postavám se přiřadí

místnosti, po kterých se mohou pohybovat, a postavy se vloží do seznamu postav. Pro seznam postav používám mapu, konkrétně instanci třídy HashMap.

    // datový atribut
    private Map<String,Postava> seznamPostav;
        // inicializace postav a přiřazení do seznamu postav
        Postava uklizecka = new Postava("uklízečka", chodba);
        Postava vratny = new Postava("vrátný",ucebna);

        uklizecka.pridejMistnosti(hala,ucebna,chodba);
        vratny.pridejMistnosti(hala,chodba,ucebna,kancelar,bufet);

        seznamPostav = new HashMap<String,Postava>();
        seznamPostav.put(uklizecka.getJmeno(),uklizecka);
        seznamPostav.put(vratny.getJmeno(),vratny);
e. třída HerniPlan - metody pro seznam postav
Třídu HerniPlan je potřeba rozšířit o metody, která pracují se seznamem osob, konkrétně o:
String popisPostavVMistnosti()
metoda vrací text se jmény postav v aktuální místnosti, pokud v aktuální místnosti žádné postavy nejsou, vrátí řetězec "V místnosti nikdo není".
void presunPostavy()
všechny postavy ve hře se pohnou
boolean jePostavaVMistnosti(String jmeno)
vrací true, pokud postava danného jména je v aktuální místnosti,
public Postava vratPostavuVMistnosti(String jmeno)
vrací odkaz na postavu, pokud postava danného jména je v aktuální místnosti,
    /**
     * metoda vrátí text se jmény postav v místnosti.
     */
    public String popisPostavVMistnosti() {
        String vysledek = "";
        for (Postava postava : seznamPostav.values()) {
            if (postava.getAktMistnost() == aktualniMistnost) {
                vysledek += " " + postava.getJmeno();
            }
        }
        if (vysledek.length() == 0) {
            vysledek = "V místnosti nikdo není";
        }
        else {
            vysledek = "V místnosti jsi uviděl postavy:"+vysledek;
        }
        return vysledek;
    }
    /**
     * Zjisti, zda je postava v aktuální místnosti.
     */
    public boolean jePostavaVMistnosti(String jmeno) {
        return seznamPostav.containsKey(jmeno)
           && seznamPostav.get(jmeno).getAktMistnost() == aktualniMistnost;
    }
    /**
     * vrátí postavu zadaného jména, pokud je v aktuální místnosti.
     */
    public Postava vratPostavuVMistnosti(String jmeno) {
        if (jePostavaVMistnosti(jmeno)) {
            return seznamPostav.get(jmeno);
        }
        else {
            return null;
        }
    }
    /**
     * Metoda pohne se všemi postavami ve hře
     */
    public void presunPostavy() {
        for (Postava postava : seznamPostav.values()) {
            postava.prejdi();
        }
    }

f. PrikazJdi - úprava metody proved:V metodě proveď je potřeba přesunout postavy mezi jednotlivými místnostmi, tj. zavolat metodu presunPostavy() z herního plánu. Já vypisuji seznam postav místnosti. Na toto místo by bylo možné umístit podmínky týkající se potkání konkrétní osoby - např. když potkám vrátného, tak mne vyhodí ze školy a hra končí.

        if (sousedniMistnost == null) {
            return "Tam se odsud jit neda!";
        }
        else {
            plan.setAktualniMistnost(sousedniMistnost);
            plan.presunPostavy();
            return sousedniMistnost.dlouhyPopis()
                 +"\n"+plan.popisPostavVMistnosti();
        }

Zabití draka

Postavu (např. draka) je možné zabít, k zabití potřebujete mít nějakou věc (např. meč). Je potřeba se rozhodnout, co se stane s mrtvou postavou:

  • mrtvola zůstane ležet na místě - třídu Postava je potřeba rozšířit o datový atribut označující, zda je postava ještě naživu. Dále je potřeba třídu Postava doplnit odpovídající metody (pro zabití a pro zjištění stavu) k tomuto datovému atributu. V metodě, ve které se vypisuje seznam postav v místnosti by se mělo u zabité postavy uvést, že je to mrtvola. S mrtvolou by nemělo jít mluvit, dávat ji nějaké věci apod.
  • po zabití se mrtvola vypaří - do třídy Mistnost je potřeba doplnit metodu pro zrušení postavy v místnosti.

Dále je potřeba vyřešit, čím se zabije která postava. Řešení závisí na počtu postav, které je potřeba zabít, a na počtu možných zbraní. Pokud je jenom jedna postava či jedna zbraň, např. pouze zabití draka příkazem "použij meč", tak nejjednodušším řešením je do metody proved ve třídě PrikazPouzij začlenit odpovídající kód. Přibližně by mohl vypadat takto (po ověření, že v batohu má meč):

        if (aktMist.getNazev().equals("jeskyně")) {
            Postava drak = aktMist.getPostava("drak");
            if (drak.jeZivy()) {
               drak.zabij();
               return "nastal dlouhý boj ......... zabil jsi draka";
            }
            else {
               return "několikrát jsi píchnul mečem do mrtvoly draka .....\n"
                     +"Ve svém věku by jsi již mohl tušit, že mečem mrtvolu neoživíš.";
            }
        }
        else {
           return "Mečem jsi se pokusil vyndat jídlo, co ti uvízlo mezi zuby.\n"
                 +"Meč ale není párátko a tak máš o jeden zub méně. Pokud to budeš\n"
                 +"zkoušet dál, tak za chvíli budeš bezzubý či přímo mrtvola";
        }

Do výsledku boje můžete též začlenit náhodu - použít generátor náhodných čísel a na základě vygenerovaného čísla rozhodnout, zda se podařilo zabít draka.

Masoví vrazi využívající různé zbraně by se měli nad řešením více zamyslet. Mám následující náměty:

  • rozšířit třídu Postava o zbraň, kterou je možné postavu zabít,
  • rozšířit třídu Postava o texty, které se použijí při zabití postavy a při zabití hráče,
  • vytvořit třídu Zbran (pravděpodobně potomka třídy Vec), která bude mít jako datový atribut útočnou sílu zbraně. U zbraně se může evidovat i obranná sílu či zvýhodnění vůči určitým typům postav (např. kopí bude mít při boji s draky zvýšenou sílu o 25%).
  • vytvořit samostatný příkaz pro zabíjení (např. "zab draka" či "zaútoč na vílu"),