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.

Upozornenie

Skôr než začnete prácu na tomto cvičení, aktualizujte si hernú knižnicu, ktorú v projekte používate, na verziu 2.4.2 podľa postupu na tejto stránke.

Ciele

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

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 actorov 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.png.

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 actorov 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 actorov, ktorých budete pre túto misiu potrebovať.

Úloha 1.4

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

Trieda World, ktorú používate ako implementáciu scény, obsahuje Topic pre pridávaných actorov s názvom ACTOR_ADDED_TOPIC. Do tejto témy sú publikované správy vždy vtedy, keď je do scény pridaný nejaký actor. Daný actor je zároveň obsahom poslanej správy. Túto tému využite na "odchytenie" pridávania votrelcov do scény a naplánujte im náhodný pohyb s využitím už existujúcich akcií.

Poznámka

Keďže metóda scenára sceneInitialized() je volaná až po tom, čo je mapa scény inicializovaná a actori v nej definovaní 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 actorov, ktorí prejavujú známky života, odlíšil od ostatných prostredníctvom rozhrania Alive.

_Ripleyová_ je živým actorom.
Obr. 3: Ripleyová je živým actorom.

Ú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 actorov 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 actor 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 actor 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 actorov a bude obsahovať deklaráciu metódy Health getHealth(), ktorá vráti referenciu na objekt triedy Health asociovaný s daným actorom.

Ú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 actora 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 actora, nemôžete túto funkcionalitu presunúť do jeho metódy drain(). Preto pridáme do triedy Health metódu onExhaustion(), 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 reprezentovať funkcia bez parametrov a bez návratovej hodnoty. Nevýhodou jazyka Java je, že toto nevieme zapísať priamo pomocou nejakého funkcionálneho typu. Namiesto toho musíme využiť tzv. funkcionálne rozhranie: rozhranie, ktoré má práve jednu abstraktnú metódu. Také rozhranie, nazvané ExhaustionEffect 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 ExhaustionEffect {
        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 onExhaustion(), ktorú je potrebné pridať do triedy Health. Táto metóda bude mať nasledovnú signatúru:

public void onExhaustion(ExhaustionEffect effect);

V tele metódy onExhaustion() 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 onExhaustion() 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. Využite metódu forEach() nad kolekciou efektov.

Poznámka

Nezabudnite na to, že aj v prípade volania metódy exhaust() dôjde k úmrtiu actora 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 onExhaustion() 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 onExhaustion() zapíšte pomocou lambda výrazu:

health.onExhaustion(() -> {
    // 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 onExhaustion() implementujeme triedu ExhaustionEffect 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.onExhaustion(new Health.ExhaustionEffect() {
    @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() dostupná na scéne, ktorou viete zrušiť všetky naplánované a prebiehajúce akcie asociované s actorom, 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 actorom 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 actorovi, ktorý zároveň nie je Enemy, a ktorý príde do kontaktu s votrelcom.

Úloha 3.4

Vytvorte triedu AlienMother 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.png.

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 actorov, 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 zameriava 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 však 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.

Ú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 actorov, 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.png.

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

Úloha 4.5

Zabezpečte pohyb náboja Bullet a jeho odstránenie zo scény po zásahu.

Vystrelený náboj sa má po umiestnení do scény začať pohybovať v definovanom smere a pokiaľ zasiahne ľubovoľný objekt typu Alive, zníži jeho aktuálnu hodnotu zdravia o 10. 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 actormi 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 actorov.

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 actorov 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 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ť actorov, ktorí majú zbraň (majú referenciu na inštanciu triedy rozširujúcu Firearm), a bude obsahovať metódy na získanie a zmenu zbrane actora:

  • 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 Ripleyová po stlačení tlačidla SPACE 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á actorom 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 actora pomocou akcie Move. Konštruktor akcie bude bezparametrický, keďže pri implementácii si vystačíme s actorom, na ktorom sa akcia naplánuje.

Poznámka

Pri implementácii budete potrebovať zistiť smer pohybu (Direction) na základe uhlu otočenia actora. Na tento účel vytvorte v enumerácii Direction statickú metódu Direction fromAngle(float angle).

ShooterController bude mať parametrický konštruktor s parametrom typu Armed, ktorý bude reprezentovať ovládaného actora. 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 actorom 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 actorov
Obr. 9: Správanie actorov

Ú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.png resp. hdoor.png) 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 actorov vo forme akcií.

Rozhranie bude mať:

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

Úloha 5.5

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

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

Ú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ň actorov 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 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 actora, 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 actor 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 actorov, 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 actor 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.

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,
        actor -> actor instanceof Ammo,
        new ChasingRipley()
   )
);

Ú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