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
- 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 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ý...
Ú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.
Ú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
.
Ú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
.
Ú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.
Ú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
.
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
.
Ú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ú parametromnewAmmo
. 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.
Ú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.
Ú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.
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ávyBehaviour<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
- Java Tutorial: Annonymous Classes
- Callback: odovzdávanie vykonateľného kódu ako argument volania funkcie, wikipedia.org
- Lambda výrazy: wikipedia.org
- Návrhový vzor pozorovateľ (observer): wikipedia.org, sourcemaking.com
- Návrhový vzor dekorátor (decorator): wikipedia.com, sourcemaking.com