10. week

Intricate Devices of Life and Death

Motivácia

Situácia je naozaj kritická. Chvíľami to vyzeralo, že Ripleyová bude pokorená nástrahami objektovo-orientovaného sveta, no len vďaka tvojej pomoci, kadet, môžeme konštatovať, že ešte vykazuje známky života - má dostatok energie. Misia ešte nie je na konci, no máš plnú dôveru a podporu taktického a strategického tímu na jej úspešné dokončenie. Hoci votrelcom nekoluje v žilách krv ale žieravá kyselina, tiež vykazujú známky života, takže musí existovať spôsob, ako ich poraziť. Ripleyová bude zrejme musieť vyskúšať viacero zbraní, nielen tých ženských, aby zistila, čo na tie nemilé kreatúry naozaj platí. V prvom rade ale jej život závisí na tvojom pláne, ako účinne identifikovať všetkých živých nepriateľov.

Z operačného strediska zdraví Manager.

Ciele

  1. Precvičiť si použitie návrhového vzoru Observer .
  2. Implementovať vlastné funkcionálne rozhranie.
  3. Využiť kompozíciu namiesto dedenia ("Composition over inheritance").
  4. Využiť návrhový vzor Dekorátor na kompozíciu správania aktérov.

Postup

Krok 1: They mostly come at night... Mostly.

Operačné stredisko získalo čerstvé satelitné snímky kolónie na planéte Acheron (LV_426). V oblasti boli spozorované zvláštne tvory doposiaľ neznámeho a evidentne mimozemského (rozumej extraterrestrial, skrátene E.T.) pôvodu. Zrejme sa vôbec nejedná o tie rozkošné Špílbergove stvorenia, ktoré chcú ísť domov. Kontakt s týmito zrejme nebude príjemný...

Trieda Alien a jej vzťahy k existujúcim triedam a rozhraniam.
Obr. 1: Trieda Alien a jej vzťahy k existujúcim triedam a rozhraniam.

Úloha 1.1

Pripravte si mapu pre dnešnú misiu do adresára src/main/resources/maps/ v projekte, novú triedu pre scenár EscapeRoom a v nej vnorenú Factory triedu pre vytváranie aktérov na mape.

Patrične upravte metódu main(), aby ste mali v hre scénu s novou mapou, nastavenou inštanciou EscapeRoom.Factory ako actor factory a EscapeRoom ako scene listener.

Úloha 1.2

Podľa uvedeného diagramu vytvorte v balíku sk.tuke.kpi.oop.game.characters triedu votrelca Alien.

Trieda nech má (zatiaľ) bezparametrický konštruktor. Votrelec nech je reprezentovaný spritom alien.

Animácia alien.png (rozmery sprite-u: 32x32, trvanie snímku: 0.1).
Obr. 2: Animácia alien.png (rozmery sprite-u: 32x32, trvanie snímku: 0.1).

Úloha 1.3

Upravte EscapeRoom.Factory tak, aby ste vedeli vrátiť objekty aktérov pre Ripleyovú, lekárničku, zásobník nábojov a votrelca.

Poznámka

Otvorte si súbor mapy a pozrite si mená a typy aktérov, ktorých budete pre túto misiu potrebovať.

Úloha 1.4

Umožnite votrelcom, aby sa pohybovali náhodne.

Využite tému správ World.ACTOR_ADDED_TOPIC na "odchytenie" pridávania votrelcov do scény a naplánujte im náhodný pohyb s využitím už existujúcich akcií.

Gamelib

Trieda World, ktorú používate ako implementáciu scény, obsahuje Topic pre pridávaných aktérov s názvom ACTOR_ADDED_TOPIC. Do tejto témy sú publikované správy vždy vtedy, keď je do scény pridaný nejaký aktér. Daný aktér je zároveň obsahom poslanej správy.

Poznámka

Keďže metóda scenára sceneInitialized() je volaná až vtedy, keď už je mapa scény inicializovaná a aktéri v nej definovaní sú už vložení do scény, potrebujete sa prihlásiť na odber správ z témy World.ACTOR_ADDED_TOPIC ešte predtým. Preto v rámci triedy scenára prekryte metódu sceneCreated(), ktorá bude zavolaná hneď po vytvorení scény.

Úloha 1.5

Overte svoju implementáciu.

Po spustení hry by ste mali vidieť mapu dnešnej misie, na ktorej by už mala by umiestnená Ripleyová, niekoľko predmetov a pobehujúci votrelci.

Krok 2: Signs of (Half-)Life

Taktickému a strategickému tímu sa podarilo nabúrať do tajných záznamov z utajovanej pitvy votrelcov a prekvapivo zistili, že ich existencia závisí od životných funkcií, podobných ako u ľudí. Taktický a strategický tím teda navrhuje, aby si na reprezentáciu týchto životných funkcií vytvoril triedu Health, ktorá nahradí doterajšie premenné pre energiu a zabezpečí väčšiu flexibilitu riešenia. Náš tím ďalej navrhuje, aby si aktérov, ktorí prejavujú známky života, odlíšil od ostatných prostredníctvom rozhrania Alive.

Ripleyová je živým aktérom.
Obr. 3: Ripleyová je živým aktérom.

Úloha 2.1

V balíku sk.tuke.kpi.oop.game.characters vytvorte triedu Health.

Objekty triedy Health budú slúžiť na uchovávanie hodnoty zdravia živých aktérov ako aj na manipuláciu s touto hodnotou.

Úloha 2.2

Vytvorte parametrický konštruktor pre triedu Health, ktorý očakáva 2 celočíselné parametre: počiatočnú hodnotu zdravia a maximálnu hodnotu zdravia.

Hodnoty oboch parametrov konštruktora uchovajte v stave triedy. Keďže budete potrebovať zisťovať aktuálnu hodnotu zdravia, sprístupnite si ju pomocou getter metódy nazvanej getValue().

Častou situáciou bude nastavenie oboch parametrov konštruktora na rovnakú hodnotu, čo bude značiť, že aktér začína s maximálnym zdravím. Pre zjednodušenie použitia triedy v takomto prípade vytvorte v triede Health ešte jeden parametrický konštruktor s jedným celočíselným parametrom, ktorým nastavíte počiatočnú a maximálnu hodnotu zdravia na rovnaké hodnoty.

Úloha 2.3

Pridajte do triedy Health verejné metódy refill(), restore(), drain() a exhaust().

Signatúry metód a ich význam nech je nasledovný:

  • void refill(int amount) - metóda navýši hodnotu zdravia o množstvo určené parametrom. Samozrejme, výsledná hodnota nesmie presiahnuť maximálnu hodnotu zadanú pri vytváraní objektu.
  • void restore() - metóda nastaví hodnotu zdravia na maximálnu možnú.
  • void drain(int amount) - metóda zníži hodnotu zdravia o množstvo určené jej parametrom. Výsledná hodnota nesmie klesnúť pod 0, čo je už stav signalizujúci, že aktér vlastniaci daný objekt zdravia sa úplne vyčerpal a zomrel.
  • void exhaust() - metóda spôsobí okamžité úplné vyčerpanie zdravia, a teda nastaví hodnotu zdravia na 0.

Úloha 2.4

Podľa vyššie uvedeného diagramu vytvorte v balíku sk.tuke.kpi.oop.game.characters rozhranie Alive.

Rozhranie bude označovať živých aktérov a bude obsahovať deklaráciu metódy Health getHealth(), ktorá vráti referenciu na objekt triedy Health asociovaný s daným aktérom.

Úloha 2.5

Upravte implementáciu Ripleyovej podľa vyššie uvedeného diagramu, aby využívala nové rozhranie Alive a objekt triedy Health.

Upravte triedu Ripley, aby implementovala rozhranie Alive.

Ďalej zabezpečte vytvorenie objektu triedy Health v konštruktore triedy Ripley. Počiatočnú aj maximálnu hodnotu zdravia nastavte na 100.

Poznámka

Nezabudnite na to, že metódy getEnergy() a setEnergy(), ako aj s nimi súvisiacu členskú premennú, už viac v triede Ripleyovej nepotrebujete. Takisto všetky miesta, kde ste doteraz používali spomínané metódy patrične upravte na použitie nových metód objektu triedy Health.

Úloha 2.6

Aplikujte návrhový vzor Observer na situáciu, keď sa hodnota zdravia v objekte typu Health nejakého Alive aktéra vyčerpá a dosiahne hodnotu 0. Využite funkciu akceptujúcu tzv. callback.

Z minulého cvičenia by ste mali mať v metóde setEnergy() okrem nastavenia energie aj test na dosiahnutie hodnoty 0, pri splnení ktorého sa odošle správa o smrti Ripleyovej. Keďže ale objekt triedy Health nemá referenciu na vlastniaceho aktéra, nemôžete túto funkcionalitu presunúť do jeho metódy drain(). Preto pridáme do triedy Health metódu onFatigued(), ktorá umožní na mieste volania registrovať akciu v podobe bloku kódu (funkcie), ktorý sa vykoná v momente dosiahnutia zdravia s hodnotou 0.

Poznámka

Uvedomte si, že použitím tzv. callback-u nerealizujete automaticky implementáciu návrhového vzoru Observer. Callback je ale možné pri tomto vzore použiť v situáciách podobných tu opísanému príkladu: blok kódu odovzdaný ako callback je použitý až v momente, keď nastane udalosť, ktorá má byť observer-om spracovaná.

V našom prípade bude callback reprezentovaný funkciou bez parametrov a bez návratovej hodnoty. V jazyku Java potrebujeme na tento účel využiť tzv. funkcionálne rozhranie: rozhranie, ktoré má práve jednu abstraktnú metódu. Také rozhranie, nazvané FatigueEffect a obsahujúce metódu apply(), si aj vytvoríte. A keďže toto rozhranie budeme používať v konečnom dôsledku len v triede Health, vytvorte toto rozhranie ako vnorené práve v triede Health nasledovne:

public class Health {
    // ...

    @FunctionalInterface
    public interface FatigueEffect {
        void apply();
    }
}

Poznámka

Všimnite si anotáciu @FunctionalInterface vo vyššie uvedenom príklade kódu. Nie je nevyhnutná na fungovanie funkcionálnych rozhraní, no zabezpečí vyvolanie chyby v prípade, že anotované rozhranie nebude mať práve jednu abstraktnú metódu.

Takže, po príprave typu pre náš callback, späť k novej metóde onFatigued(), ktorú je potrebné pridať do triedy Health. Táto metóda bude mať nasledovnú signatúru:

public void onFatigued(FatigueEffect effect);

V tele metódy onFatigued() pridajte objekt poskytnutý v parametri effect do zoznamu všetkých efektov, ktorý si v triede Healh vytvoríte. Zoznam použite z toho dôvodu, aby bolo možné viacnásobným volaním metódy onFatigued() registrovať viac efektov vyčerpania na tom istom objekte Health.

Následne upravte implementáciu metódy drain() tak, aby po dosiahnutí hodnoty zdravia 0 došlo k postupnému zavolaniu metódy apply() na všetkých registrovaných efektoch.

Poznámka

Nezabudnite na to, že aj v prípade volania metódy exhaust() dôjde k úmrtiu aktéra a callback funkcie musia byť zavolané aj v tomto prípade. Vhodne implementujte metódu exhaust(), aby ste sa vyhli duplicite kódu.

Poznámka

Pri implementácii zabezpečte, aby k vyvolaniu callback-ov došlo iba prvýkrát pri dosiahnutí hodnoty zdravia 0.

Úloha 2.7

Využite metódu onFatigued() triedy Health na nakonfigurovanie detekcie poklesu zdravia Ripleyovej na hodnotu 0 a prepošlite správu o jej smrti do zbernice správ na scéne.

Argument metódy onFatigued() zapíšte pomocou lambda výrazu:

health.onFatigued(() -> {
    // reakcia na vycerpanie zdravia
});

Poznámka

Zápis pomocou lambdy je ekvivalentný nasledovnému zápisu pomocou anonymnej triedy, kde priamo na mieste volania metódy onFatigued() implementujeme triedu FatigueEffect s konkrétnou implementáciou metódy apply() a zároveň z nej vytvoríme inštanciu. Takýto zápis by vyzeral nasledovne:

health.onFatigued(new Health.FatigueEffect() {
    @Override
    public void apply() {
        // implementacia metody
    }
});

Poznámka

Pri implementácii reakcie Ripleyovej na nulovú hodnotu zdravia sa vám môže zísť metóda cancelActions(Actor actor) dostupná na scéne, ktorou viete zrušiť všetky naplánované a prebiehajúce akcie asociované s aktérom, ktorého odovzdáte ako argument metódy.

Úloha 2.8

Zovšeobecnite triedu Energy tak, aby bola lekárnička využiteľná akýmkoľvek Alive aktérom a nie iba Ripleyovou.

Krok 3: Know Your Enemy

V predošlom kroku ste vyškolili Ripleyovú ako sa má starať o svoje zdravie, no už prišiel čas, aby sa popasovala so svojimi nepriateľmi. Rozpozná ich vďaka rozhraniu Enemy.

Trieda Alien
Obr. 4: Trieda Alien

Úloha 3.1

V balíku sk.tuke.kpi.oop.game.characters vytvorte podľa vyššie uvedeného diagramu prázdne rozhranie Enemy (marker interface), pomocou ktorého bude možné rozlišovať nepriateľov. Zatiaľ je jediným nepriateľom bežne sa vyskytujúci votrelec.

Úloha 3.2

Upravte triedu Alien tak, aby implementovala rozhranie Enemy a Alive.

Vykonajte aj všetky potrebné úpravy vyplývajúce z implementácie rozhrania Alive.

Úloha 3.3

V metóde addedToScene() votrelca naplánujte znižovanie energie každému Alive aktérovi, ktorý zároveň nie je Enemy, a ktorý príde do kontaktu s votrelcom.

Úloha 3.4

Vytvorte triedu MotherAlien pre matku votrelcov.

Táto trieda bude rozšírením triedy bežného votrelca. Matka votrelcov bude mať vyššiu hodnotu maximálneho aj počiatočného zdravia ako ostatní votrelci, napríklad hodnotu 200.

Ako animáciu použite obrázok mother.

Animácia mother.png (rozmery sprite-u: 112x162, trvanie snímku 0.2).
Obr. 5: Animácia mother.png (rozmery sprite-u: 112x162, trvanie snímku 0.2).

Úloha 3.5

Upravte továrenskú triedu pre vytváranie aktérov, aby vytvorila inštanciu aj pre matku alienov.

Matka votrelcov má meno alien mother.

Na palube sa nachádza príliš veľa votrelcov.
Obr. 6: Na palube sa nachádza príliš veľa votrelcov.

Krok 4: Lieutenant, what do those pulse rifles fire?

Ripleyová už efektívne identifikuje nepriateľov, no to jej nepomôže, kým nemá účinnú zbraň. Hoci by sa neurazila, aj keby sme jej do rúk vložili obyčajné páčidlo, oveľa bezpečnejší je boj z diaľky, a na to sa už žiada použiť kvalitnú strelnú zbraň. Predtým, ako Ripleyovú vybavíte zbraňou pomocou rozhrania Armed, musí prejsť expresným školením o zaobchádzaní so zbraňami a strelivom, čo umožní abstraktná trieda Firearm a rozhranie Fireable.

Ripleyová získava zbraň
Obr. 7: Ripleyová získava zbraň

Úloha 4.1

V balíku sk.tuke.oop.game.weapons vytvorte abstraktnú triedu Firearm, ktorá bude definovať všeobecnú funkcionalitu strelnej zbrane.

Konštruktor triedy bude mať (podobne ako v prípade triedy Health) 2 celočíselné parametre: počiatočný počet nábojov v zbrani a maximálny možný počet nábojov v zbrani. Pre situáciu, kedy je počiatočný počet a maximálny počet nábojov zhodný, pridajte tiež preťažený konštruktor s jedným parametrom.

Úloha 4.2

V triede Firearm implementujte verejné metódy getAmmo() a reload().

Signatúry metód a ich význam nech je nasledovný:

  • int getAmmo() - metóda vráti počet momentálne dostupných nábojov.
  • void reload(int newAmmo) - metóda zvýši počet nábojov zbrane o hodnotu definovanú parametrom newAmmo. Hodnota sa ale môže zvýšiť len natoľko, aby neprekročila maximálny počet nábojov zbrane definovaný pri jej vytvorení.

Úloha 4.3

V balíku sk.tuke.kpi.oop.game.weapons vytvorte marker rozhranie Fireable podľa vyššie uvedeného diagramu.

Rozhranie Fireable bude predpisom pre všetkých aktérov, ktorí budú môcť figurovať ako strelivo do zbrane.

Úloha 4.4

V balíku sk.tuke.kpi.oop.game.weapons vytvorte na základe vyššie uvedeného diagramu triedu Bullet reprezentujúcu vystrelený náboj.

V metóde startedMoving() z rozhrania Movable nezabudnite nastaviť správne otočenie animácie vystreleného náboja. Rýchlosť náboja nastavte napríklad na 4.

Ako animáciu reprezentujúcu náboj použite súbor bullet.

Animácia bullet.png (rozmery spritu: 16x16).
Obr. 8: Animácia bullet.png (rozmery spritu: 16x16).

Úloha 4.5

Zabezpečte odstránenie náboja Bullet zo scény po zásahu Alive aktérov alebo steny.

Vystrelený náboj sa má po umiestnení do scény začať pohybovať v definovanom smere (samotný pohyb budete riešiť v jednej z ďalších úloh) a pokiaľ zasiahne ľubovoľný objekt typu Alive, zníži jeho aktuálnu hodnotu zdravia o 15. Následne náboj zmizne zo scény. Pri kolízii so stenami (pevnými prekážkami) sa náboj tiež stratí.

Zatiaľčo kolíziu s Alive aktérmi je možné detegovať jednoducho pomocou ich metódy intresects(), pri zisťovaní kolízie so stenou nastáva problém. Takáto kolízia je totiž "skrytá" akciou Move (ktorú by mala trieda Bullet na pohyb využívať) a mimo jej metódy execute() ju nie je možné odsledovať. Implementovať odstránenie objektov Bullet do tejto metódy však nie je vhodné riešenie, pretože porušuje princíp jednej zodpovednosti a zbytočne vnáša nežiaduce závislosti medzi triedami. Úlohu detekcie kolízie so stenou vyriešime nasledovne a pritom to riešenie zovšeobecníme pre všetkých Movable aktérov.

Do rozhrania Movable pridajte metódu collidedWithWall() so štandardnou (default) prázdnou implementáciou:

public interface Movable extends Actor {
    // ...

    default void collidedWithWall() {}
}

Táto metóda bude pre Movable aktérov reprezentovať udalosť narazenia do steny. Preto ju potrebujete volať v akcii Move pri detekcii kolízie so stenou. Prekrytím tejto metódy v triede Bullet potom viete dosiahnuť požadované odstránenie náboja zo scény.

Úloha 4.6

Do triedy Firearm pridajte verejnú metódu fire() a protected abstraktnú metódu createBullet().

Obe metódy budú bezparametrické a ich návratový typ bude Fireable.

Metóda createBullet() je vyhradená na vytvorenie inštancie streliva určeného pre danú zbraň. Bude abstraktná a protected preto, lebo jej implementáciu - vytvorenie konkrétneho typu streliva - doplní konkrétna zbraň.

Metóda fire() má 2 možné priebehy:

  • V prípade, že zbraň ešte obsahuje náboje, ich počet sa dekrementuje a metóda vráti novú inštanciu streliva vytvorenú pomocou volania metódy createBullet().
  • Ak v zbrani už náboje nie sú, metóda vráti null.

Úloha 4.7

V balíku sk.tuke.kpi.oop.game.weapons vytvorte podľa vyššie uvedeného diagramu triedu Gun, reprezentujúcu zbraň, ktorú bude používať Ripleyová.

Konštruktor by mal mať dva parametre zodpovedajúce konštruktoru nadtriedy.

Zbraň bude vystreľovať náboje typu Bullet.

Úloha 4.8

V balíku sk.tuke.kpi.oop.game.characters vytvorte rozhranie Armed a upravte triedu Ripley tak, aby ho implementovala.

Rozhranie Armed bude identifikovať aktérov, ktorí majú zbraň (majú referenciu na inštanciu triedy rozširujúcu Firearm), a bude obsahovať metódy na získanie a zmenu zbrane aktéra:

  • Firearm getFirearm()
  • void setFirearm(Firearm weapon)

Implementujte toto rozhranie v triede Ripley a zabezpečte, aby Ripleyová mala k dispozícií zbraň (Gun) hneď po vytvorení jej inštancie.

Úloha 4.9

Zabezpečte, aby po stlačení tlačidla **SPACE** Ripleyová vystrelila. Na tento účel implementujte v balíku sk.tuke.kpi.oop.game.actions akciu Fire a do balíka sk.tuke.kpi.oop.game.controllers pridajte ovládač ShooterController.

Akcia Fire má byť použiteľná aktérom implementujúcim Armed a má zabezpečiť umiestnenie novej inštancie streliva použitej zbrane do scény a naplánovať pre neho "nekonečný" pohyb v smere otočenia Armed aktéra pomocou akcie Move. Konštruktor akcie bude bezparametrický, keďže pri implementácii si vystačíme s aktérom, na ktorom sa akcia naplánuje.

Poznámka

Pri implementácii budete potrebovať zistiť smer pohybu (Direction) na základe uhlu otočenia aktéra. Na tento účel vytvorte v enumerácii Direction statickú metódu Direction fromAngle(float angle), ktorá vráti smer zodpovedajúci danému uhlu.

ShooterController bude mať parametrický konštruktor s parametrom typu Armed, ktorý bude reprezentovať ovládaného aktéra. Plánovanie akcie Fire realizujte v metóde keyPressed() po stlačení klávesy **SPACE**.

Úloha 4.10

Zovšeobecnite triedu Ammo tak, aby bol zásobník nábojov využiteľný akýmkoľvek Armed aktérom a nie iba Ripleyovou.

Krok 5: Watch your behaviour!

Hlavnou nástrahou pre Ripleyovú budú v tejto misii, samozrejme, votrelci, ktorí číhajú za zavretými dverami. Udalosť otvorenia dverí ich však prebudí, následkom čoho môže nastať zmätok. Aby sa ale situácia úplne nevymkla spod kontroly, bude tvojou úlohou, kadet, naučiť skupiny votrelcov vhodne sa správať. Zachovávajúc jednu z hlavných tém dnešnej misie, ktorou je composition over inheritance, ťa touto časťou prevedie náš taktický a strategický tím za pomoci návrhového vzoru Dekorátor.

Správanie aktérov
Obr. 9: Správanie aktérov

Úloha 5.1

Upravte triedu Door tak, aby sa dala použiť nielen pre vertikálne, ale aj pre horizontálne orientované dvere.

Do triedy Door pridajte enumeračný typ Orientation s hodnotami HORIZONTAL a VERTICAL, ktoré sa použijú na rozlíšenie orientácie dverí. Pridajte orientáciu dverí ako parameter konštruktora a na základe jeho hodnoty nastavte správnu animáciu (vdoor resp. hdoor) pre dvere.

Animácia vertikálnych dverí vdoor.png (rozmery spritu: 16x32, trvanie snímku: 0.1)
Obr. 10: Animácia vertikálnych dverí vdoor.png (rozmery spritu: 16x32, trvanie snímku: 0.1)

Animácia horizontálnych dverí hdoor.png (rozmery spritu: 32x16, trvanie snímku: 0.1)
Obr. 11: Animácia horizontálnych dverí hdoor.png (rozmery spritu: 32x16, trvanie snímku: 0.1)

Poznámka

Nezabudnite skontrolovať, či spôsob, akým vytvárate na mieste zatvorených dverí stenu, pracuje správne pre obe orientácie dverí. V prípade potreby ho upravte.

Úloha 5.2

Pridajte dverám konštruktor so signatúrou Door(String name, Orientation orientation), v ktorom bude možné definovať okrem orientácie dverí aj ich názov.

Názov dverí odovzdajte ako argument konštruktoru nadtriedy AbstractActor.

Úloha 5.3

Upravte factory triedu tak, aby vytvárala správne orientované dvere pre všetky dvere v mape.

Úloha 5.4

V novom balíku sk.tuke.kpi.oop.game.behaviours vytvorte rozhranie Behaviour, ktoré bude reprezentovať predpis správania sa aktérov vo forme akcií.

Rozhranie bude mať:

  • typový parameter (napr. A), ktorým sa vymedzí, pre aký typ aktérov je dané správanie aplikovateľné,
  • metódu bez návratovej hodnoty setUp(A actor), ktorá dostane aktéra ako parameter a ktorej úlohou bude definovať správanie sa daného aktéra.

Úloha 5.5

V balíku sk.tuke.kpi.oop.game.behaviours vytvorte triedu RandomlyMoving, ktorá bude reprezentovať správanie sa Movable aktéra - bude sa náhodne pohybovať.

Pomôžte si implementáciou náhodného pohybu votrelca, ktorú by ste už mali mať v rámci scenára. Plánované akcie zovšeobecnite pre ľubovoľného Movable aktéra.

Úloha 5.6

Triedam Alien a MotherAlien pridajte parameter konštruktora typu Behaviour, ktorým bude možné definovať danému votrelcovi jeho správanie.

Keďže chceme mať možnosť použiť správania určené pre typ Alien a všetky jeho nadtypy (typy od ktorých Alien dedí, napr. Movable, Alive, atď.), zapíšte signatúru parametra pre správanie nasledovne:

Behaviour<? super Alien> behaviour

Získanú referenciu na objekt správania využite v metóde addedToScene(), kde zavoláte jeho metódu setUp() na nastavenie správania sa daného votrelca.

Úloha 5.7

Upravte továreň aktérov tak, aby ste pre votrelca typu "running" odovzdali inštanciu správania RandomlyMoving.

Pre ostatné typy votrelcom zatiaľ nastavte správanie na null.

Poznámka

Nezabudnite zrušiť plánovanie náhodného pohybu pre votrelcov priamo v scenári.

Úloha 5.8

Overte svoju implementáciu.

Votrelec s typom "running" by sa mal náhodne pohybovať. Ostatní votrelci by mali byť nehybní.

Úloha 5.9

V balíku pre správania vytvorte triedu Observing, ktorá bude reprezentovať správanie aktéra, ktorý čaká na publikovanie špecifickej správy (objektu s určitými vlastnosťami) do konkrétnej témy správ.

Táto trieda správania bude slúžiť ako dekorátor pre iné správanie.

Poznámka

Zamýšľané použitie je nasledovné: ak aktér dostane v tomto správaní zaobalené správanie náhodného pohybu, bude najskôr čakať na publikovanie vyhovujúcej správy a až potom sa aplikuje správanie, ktoré bolo zaobalené.

Keďže trieda Observing bude zaobaľovať, resp. dekorovať iné správanie, bude mať typový parameter (napr. A) pre typ aktérov, pre ktorých bude použité dekorované správanie vytvorené. Navyše, keďže bude pracovať aj s nejakým typom tém správ, bude mať ďalší typový parameter (napr. T) pre objekty posielané v správach:

public class Observing<A extends Actor, T> implements Behaviour<A> {
    // ...
}

Trieda bude mať konštruktor s troma nasledovnými parametrami:

  • Topic<T> topic - téma správ, ktorú bude aktér sledovať
  • Predicate<T> predicate - predikát (podmienka) na overenie prijatej správy
  • Behaviour<A> delegate - zaobalené správanie

V metóde setUp() sa prihláste na odoberanie témy správ topic, pričom po jej prijatí skontrolujte podmienku definovanú predikátom predicate a v prípade jej splnenia zavolajte setUp() metódu zaobaleného správania delegate.

Poznámka

Rozhranie Predicate<T> je štandardné funkcionálne rozhranie určené na testovanie podmienok. Definuje jednu abstraktnú metódu test(), ktorej parameter je typu T a typ návratovej hodnoty je boolean.

Poznámka

Zamýšľané použitie správania Observing je teda možné demonštrovať na nasledovnom príklade. Ak by sme chceli votrelcovi definovať správanie, kedy po použití zásobníka nábojov (bude odstránený zo scény) začne prenasledovať Ripleyovú (ak by sme na to mali definované správanie napr. ChasingRipley), zapísali by sme to nasledovne:

new Alien(
    new Observing<>(
        World.ACTOR_REMOVED_TOPIC,  // cakame na spravu v tejto teme
        actor -> actor instanceof Ammo,  // kontrolujeme, ci bol prave odstraneny zasobnik
        new ChasingRipley()  // odovzdavame spravania, ktore sa ma pouzit v pripade, ze predikat bude splneny
   )
);

Úloha 5.10

Zabezpečte "prebudenie" spiacich votrelcov po otvorení dverí do miestnosti, v ktorej sa nachádzajú.

Prebudením votrelcov sa myslí to, že sa začnú náhodne pohybovať.

Využite vytvorené triedy správaní na definovanie správania votrelcov, ktorí majú v mape definované typy "waiting1" a "waiting2". Majú reagovať na otvorenie dverí s menami "front door", resp. "back door". Správanie, samozrejme, potrebujete definovať už pri ich vytváraní v factory triede.

Úloha 5.11

Overte svoju implementáciu.

Votrelci v jednotlivých miestnostiach by sa mali začať hýbať po tom, čo Ripleyová otvorí príslušné dvere.

Úloha 5.12

Hru ukončite, keď Ripleyová otvorí dvere, ktoré majú v mape definované meno "exit door".

Ukončenie hry môžete reprezentovať napríklad vypísaním nejakého textu (napr. Well done!) do Overlay vrstvy hry.

Doplňujúce zdroje