5. týždeň

Let's Have an Agreement

Motivácia

Kadet! Od získania hodnosti Trainee ťa delí už len posledná tréningová misia. Jej zvládnutie je pre tvoje prežitie vo svete OOP veľmi dôležité! Najskôr ti ukážeme, ako sa pripraviť na prácu bez inšpektora. A - čo je mimoriadne dôležité - dostaneme sa k ďalšej stránke polymorfizmu, ktorou je spôsob, ako sa dá s určitou skupinou typov objektov dohodnúť, ak ich naučíš rozumieť určitému rozhraniu.

V dnešnej misii pre teba náš taktický a strategický tím pripravil dva ciele zamerané na princípy OOP a jeden praktický cieľ, ktorým je refaktorizácia. Všetci dúfame, že v nej preukážeš svoje schopnosti, ktoré si získal počas tréningu a budeš tak pripravený na operácie v teréne.

Veľa šťastia pri zdolávaní cieľov! Z veliteľského mostíka zdraví Manager.

Ciele

  1. Porozumieť rozhraniam a precvičiť si ich implementáciu a využitie v ďalšom kóde.
  2. Porozumieť princípu polymorfizmu a jeho dôsledkom.
  3. Precvičiť si refaktorizáciu kódu.

Postup

Krok 1: Lather, Rinse, Repeat

Určite ste už zaregistrovali, že na overenie správnosti postupu pri riešení misií potrebujete opakovať čím ďalej tým viac operácií po spustení aplikácie: povytvárať potrebné objekty a prepojenia medzi nimi, kontrolovať správanie po volaní metód. Okrem zdĺhavosti celého postupu hrozí aj to, že v reálnom svete bez inšpektora sa budete orientovať s problémami. S cieľom vyhnúť sa tejto situácií teraz predstavíme spôsob, ako scenáre pre testovanie misie vytvárať priamo v kóde.

Úloha 1.1

V balíku sk.tuke.kpi.oop.game vytvorte triedu Gameplay, ktorá bude dediť od triedy Scenario dostupnej v hernej knižnici a prekryte jej abstraktnú metódu setupPlay(Scene scene).

V ďalších úlohách budete v rámci tejto metódy vkladať do scény ďalších aktérov a plánovať akcie, ktoré budú vykonávať. To znamená, že budete písať scenár.

Gamelib

Knižnica vyhľadá pri spustení aplikácie implementáciu triedy Scenario a zaregistruje ju ako tzv. listener (resp. event handler) na scéne. Pripravená metóda setupPlay() je zavolaná hneď po tom, ako je v scéne inicializovaná mapa a aktéri v nej definovaní - zatiaľ je to len postava hráča (Player).

Úloha 1.2

V scenári pridajte do scény reaktor a zapnite ho.

Pre pridanie nového aktéra do scény potrebujete vytvoriť (alebo už mať k dispozícii) inštanciu daného aktéra a pridať ho do scény na nejakú pozíciu (t.j. definovať umiestnenie ľavého dolného rohu animácie aktéra voči ľavému dolnému rohu mapy scény).

Pridanie a zapnutie reaktora teda môže byť zapísané takto:

Reactor reactor = new Reactor();  // vytvorenie instancie reaktora
scene.addActor(reactor, 64, 64);  // pridanie reaktora do sceny na poziciu [x=64, y=64]
reactor.turnOn();

Implementáciu si overte spustením aplikácie. Reaktor by mal byť hneď po štarte umiestnený na danej pozícií a mal by byť zapnutý.

Poznámka

Alternatívny spôsob k písaniu súradníc umiestnenia aktérov pri ich vkladaní do scény je ukázaný v doplnkovej (a voliteľnej) úlohe A.4.

Úloha 1.3

Pridajte do scény vypnutý chladič a zapnite ho 5 sekúnd po spustení scény.

Akciu Invoke, ktorou je možné docieliť zapnutie chladiča, už poznáte z minulého cvičenia. V knižnici je dostupná aj akcia Wait, ktorá v konštruktore akceptuje dĺžku trvania čakania (v sekundách). So znalosťou týchto dvoch akcií by sme mohli chcieť riešiť túto úlohu nasledovne:

new Wait<>(5).scheduleFor(cooler);
new Invoke<>(cooler::turnOn).scheduleFor(cooler);

Ako sa však budú správať takto naplánované akcie? Scéna bude volať metódu execute() všetkých naplánovaných akcií pred každým vykreslením nového snímku. To znamená, že naplánované akcie sa vykonávajú súbežne. V tomto prípade teda nedocielime to, že sa chladič zapne až po ukončení akcie Wait (ktorá tu bude trvať 5 sekúnd). Namiesto toho bude chladič zapnutý hneď po spustení aplikácie.

Poznámka

Medzi vykonaním riadkov new Wait... a new Invoke... v uvedenom príklade neuplynie čas niekoľkých sekúnd nastavený pre Wait. Tieto príkazy sa vykonajú v krátkej následnosti po sebe, keďže sa jedná len o vytvorenie objektov akcií a ich naplánovanie na vykonávanie. Vykonávať ich bude až scéna, tak ako je popísané v dokumentácii hernej slučky.

Pre riešenie úlohy potrebujeme zapezpečiť spustenie akcie zapnutia chladiča až po ukončení akcie čakania. Za účelom zreťazenia vykonávania viacerých akcií vieme použiť ďalšiu akciu z knižnice - ActionSequence, ktorá v konštruktore akceptuje variabilný počet iných akcií, pre ktoré zabezpečí sekvenčné vykonávanie. Na scéne potom naplánujeme len túto sekvenčnú akciu:

new ActionSequence<>(
    new Wait<>(5),
    new Invoke<>(cooler::turnOn)
).scheduleFor(cooler);

Úloha 1.4

Pomocou existujúcich akcií zapíšte scenár pre opravu prehriateho reaktora kladivom v momente, keď teplota reaktora dosiahne hodnotu 3000 stupňov.

Pri riešení tejto úlohy je potrebné akciou zavolať metódu repairWith(Hammer hammer) na reaktore. Táto metóda očakáva jeden argument - kladivo. To nám umožňuje predstaviť ďalší spôsob, ako je možné zapisovať akcie: pomocou anonymných tried, alebo skrátene pomocou lambda výrazov.

Doteraz ste konštruktor akcie Invoke používali s referenciou na bezparametrickú metódu. Signatúra tohto konštruktora je v knižnici zapísaná takto:

public Invoke(Runnable action)

Runnable je štandardné Java rozhranie, ktoré predpisuje jednu metódu so signatúrou void run(). V kombinácii s možnosťou vytvárať anonymné triedy - triedy zapísané napr. priamo v kóde metódy, kde sa z takejto triedy hneď aj vytvorí jedna inštancia - by sme mohli zapísať akciu pre opravu reaktora kladivom takto:

new Invoke<>(new Runnable() {  // zapiseme priamo implementaciu rozhrania
    public void run() {
        reactor.repairWith(hammer);
    }
});

Týmto zápisom dosiahneme vytvorenie objektu typu Runnable s konkrétnou implementáciou metódy run(). Tento objekt je odovzdaný do konštruktora akcie Invoke a táto akcia je implementovaná tak, že vo svojej execute() metóde zavolá run() metódu na tomto objekte.

Takýto zápis nám poskytuje väčšiu flexibilitu ako používanie referencie na metódu, no zároveň je aj dosť zdĺhavý. Keďže je rozhranie Runnable funkcionálnym rozhraním, t.j., predpisuje práve jednu abstraktnú metódu, môžeme zápis skrátiť použitím lambda výrazu, kde zapíšeme namiesto celej triedy len implementáciu tej jednej abstraktnej metódy. Pre náš príklad bude prepis vyššie uvedenej anonymnej triedy vyzerať takto:

new Invoke<>(() -> {
    reactor.repairWith(hammer);
});

Výsledkom je, rovnako ako v predošlom príklade, vytvorenie objektu typu Runnable (na základe signatúry konštruktora akcie Invoke), ktorý má metódu run() s nami definovanou implementáciou.

Pre riešenie druhej časti úlohy (detekcia momentu, kedy teplota reaktora dosiahne danú teplotu) môžeme zas využiť existujúcu akciu When. Prvým argumentom konštruktora akcie When je jednoduchý predikát. Je to objekt typu Supplier<Boolean> s jednou abstraktnou metódou boolean get(), alebo jemu ekvivalentný lambda výraz. Predikát sa testuje neustále od momentu naplánovania akcie When. V momente, keď prvýkrát vráti true, akcia definovaná v druhom argumente konštruktora akcie When bude vykonaná.

Zápis akcie When v kontexte, kde máme k dispozícií objekty reaktora reactor a kladiva hammer, môže vyzerať nasledovne:

new When<>(
    () -> {
        return reactor.getTemperature() >= 3000;
    },
    new Invoke<>(() -> {
        reactor.repairWith(hammer);
    })
).scheduleFor(reactor);

A keďže telá oboch metód zapísaných v lambdách pozostávajú len z jedného výrazu, Java nám umožňuje zápis skrátiť do nasledovnej ekvivalentnej podoby:

new When<>(
    () -> reactor.getTemperature() >= 3000,
    new Invoke<>(() -> reactor.repairWith(hammer))
).scheduleFor(reactor);

Úloha 1.5

Navrhnite vlastný scenár, ktorým namodelujete správanie niekoľkých navzájom prepojených aktérov.

Zápisom rôznych prípadov použitia existujúcich aktérov sa snažte minimalizovať použitie inšpektora.

Poznámka

Aj pri nasledujúcich úlohách využívajte možnosti zapísať použitie aktérov do scenára. Scenár čleňte na metódy modelujúce jednotlivé prípady použitia. V metóde setupPlay() potom len uveďte, ktorú metódu chcete použiť.

Krok 2: Switchable

Doteraz sme vedeli ovládať vypínačom len jeden typ zariadení (objektov). Tentokrát sa však pokúsite "prehovoriť" a "dohodnúť" aj s ďalšími objektami, aby sa dali ovládať. Všetko je len otázkou, akú dohodu vo forme rozhrania im ponúknete.

Vzťah tried k rozhraniu Switchable.
Obr. 1: Vzťah tried k rozhraniu Switchable.

Úloha 2.1

V balíku sk.tuke.kpi.oop.game vytvorte rozhranie Switchable.

Signatúry metód v tomto rozhraní a ich význam je nasledovný:

  • void turnOn() - metóda zapne ovládané zariadenie
  • void turnOff() - metóda vypne ovládané zariadenie
  • boolean isOn() - metóda vráti hodnotu, ktorá reprezentuje stav zariadenia (true - zariadenie je zapnuté, false - zariadenie je vypnuté)

Úloha 2.2

Upravte triedu Reactor tak, aby implementovala rozhranie Switchable.

Metódy, ktoré majú byť súčasťou rozhrania, už v triede máte. Správanie metódy isOn() máte ale implementované metódou isRunning(). Použite možnosti refaktorizácie vo vývojovom prostredí na premenovanie tejto metódy.

Úloha 2.3

Premenujte triedu Controller na PowerSwitch. Nech reprezentuje vypínač, ktorý vie zapínať a vypínať akékoľvek zariadenia implementujúce rozhranie Switchable.

Upravte implementáciu triedy PowerSwitch tak, aby mala nasledovné verejné metódy:

  • getDevice() - poskytuje referenciu na pripojené zariadenie
  • switchOn() - zapína pripojené zariadenie
  • switchOff() - vypína pripojené zariadenie

Gamelib

Pre grafické odlíšenie vypínača vo vypnutej polohe (keď je pripojené zariadenie vypnuté) môžete využiť metódu setTint() na jeho animácii, ktorá jej pridá zafarbenie podľa definovanej farby. Napríklad použitím sivej farby sa utlmí farebnosť animácie:

getAnimation().setTint(Color.GRAY);

Zafarbenie zrušíte aplikovaním bielej farby.

Úloha 2.4

Overte správnosť svojej implementácie vytvorením inštancie reaktora a inštancie triedy PowerSwitch, pomocou ktorej budete vedieť reaktor zapínať a vypínať.

Reaktor zapnutý pomocou PowerSwitch-a.
Obr. 2: Reaktor zapnutý pomocou PowerSwitch-a.

Úloha 2.5

Upravte triedy Cooler a Light tak, aby implementovali rozhranie Switchable.

Pokiaľ tieto triedy už majú implementované požadované metódy, pridajte im anotáciu @Override. Ak majú funkcionalitou podobné metódy, len ich premenujte. Ak však tieto metódy vôbec neexistujú, vytvorte ich.

Úloha 2.6

Overte správnosť svojej implementácie.

Správnosť overíte tak, že vytvoríte inštancie uvedených tried a pre každú inštanciu vytvoríte aj príslušný vypínač, pomocou ktorého ho budete vedieť ovládať.

Reaktor, svetlo a inteligentné chladiče ovládané pomocou PowerSwitch-ov.
Obr. 3: Reaktor, svetlo a inteligentné chladiče ovládané pomocou PowerSwitch-ov.

Krok 3: Producer/consumer

Reaktor dokáže v súčasnosti napájať len zariadenia, ktoré sú inštancie triedy Light. Vašou úlohou je pripraviť vhodný "štandard" pre napájanie ľubovoľných zariadení (navrhnúť vzájomne kompatibilné "zásuvky" a "zástrčky").

Vzťah tried k rozhraniu EnergyConsumer.
Obr. 4: Vzťah tried k rozhraniu EnergyConsumer.

Úloha 3.1

V balíku sk.tuke.kpi.oop.game vytvorte rozhranie EnergyConsumer.

Rozhranie EnergyConsumer bude mať len jednu metódu. Jej signatúra a význam je nasledovný:

  • void setPowered(boolean) - pomocou tejto metódy bude výrobca energie oznamovať zariadeniam, že energia je, resp. nie je dodávaná.

Úloha 3.2

Upravte triedu Light tak, aby implementovala rozhranie EnergyConsumer.

Úloha 3.3

V triede Reactor vytvorte z metód addLight() a removeLight() metódy addDevice() a removeDevice(), pomocou ktorých bude možné k reaktoru pripojiť zariadenia implementujúce rozhranie EnergyConsumer.

Úloha 3.4

Overte správnosť svojej implementácie.

Pokiaľ ste postupovali správne, výsledná funkcionalita sa výrazne nezmení - stále bude svetlo jediným spotrebičom, ktorý je možné pripojiť. Tentokrát ho však reaktor nebude vidieť ako objekt typu Light, ale ako objekt typu EnergyConsumer.

Úloha 3.5

Upravte triedu Computer tak aby implementovala rozhranie EnergyConsumer.

Počítač bude fungovať len v prípade, že je napájaný elektrinou. V opačnom prípade pracovať nebude (pozastaví prehrávanie animácie a výsledky všetkých operácií budú mať hodnotu 0).

Úloha 3.6

Upravte reaktor tak, aby mohol napájať viacero zariadení.

Referencie na objekty týchto zariadení nech sa ukladajú do množiny. Využite pritom nasledovný kód na jej vytvorenie:

// deklaracia clenskej premennej pre mnozinu pripojenych zariadeni
private Set<EnergyConsumer> devices;

// vytvorenie instancie mnoziny v konstruktore
// (typovy parameter pre HashSet je odvodeny na zaklade typu premennej devices)
devices = new HashSet<>();

Poznámka

Použitie množiny oproti štandardnému zoznamu (typ List) zabezpečí, že jedno konkrétne zariadenie nebude k reaktoru pripojené viackrát.

Patrične upravte metódy addDevice() a removeDevice() v triede Reactor. Pre pridanie zariadenia do množiny použite volanie metódy add() na objekte množiny. Metódu removeDevice() upravte na parametrickú, pričom parametrom nech je objekt typu EnergyConsumer. Pre odobratie zariadenia z množiny použite volanie metódy remove(), ktorej v parametri predáte konkrétny objekt, ktorý sa má z množiny odobrať.

Úloha 3.7

Overte správnosť svojej implementácie.

Ak ste postupovali správne, budete vedieť k reaktoru tentokrát pripojiť nie len inštancie triedy Light, ale aj inštancie triedy Computer. Tu sa prejavuje polymorfizmus využitím rozhraní.

Reaktorom napájaný počítač.
Obr. 5: Reaktorom napájaný počítač.

Krok 4: Useful generalization

V rámci prípravy na reálny svet je určite potrebné zamerať sa aj na "prečistenie" už implementovaného kódu a precvičenie refaktorizácie (zmena štruktúry už implemetovaného kódu pri zachovaní správania). V tomto prípade pôjde o zovšeobecnenie nástrojov na použiteľných aktérov a o otočenie vzťahu medzi iniciátorom použitia a používaným objektom. Rozhraním rozlíšime aj skupinu aktérov, ktoré je možné opraviť.

Úloha 4.1

Do balíka sk.tuke.kpi.oop.game.tools pridajte rozhranie Usable reprezentujúce použiteľných aktérov - nástroje.

Poznámka

Aj v doterajšej implementácii vznikol pri použiteľných zariadeniach nejaký vzťah medzi tým, ktorý typ objektu mohol byť použitý na opravu ktorého iného objektu (len kladivo môže opraviť reaktor). Keďže chceme teraz použiteľnosť aktérov zovšeobecniť, zíde sa nám možnosť definovať vlastné typové parametre.

Rozhranie Usable bude používať typový parameter A, ktorý bude viazaný na podtypy rozhrania Actor:

interface Usable<A extends Actor> {
    // ...
}

V rozhraní potom definujte metódu so signatúrou void useWith(A actor). Aktér dodaný v parametri metódy useWith() bude slúžiť na dodefinovanie kontextu použitia Usable aktéra.

Úloha 4.2

Upravte abstraktnú triedu BreakableTool tak, aby implementovala rozhranie Usable a aby umožnila dodefinovať typový parameter vo svojich konkrétnych implementáciách (podtriedach).

Poznámka

BreakableTool teda nebude dosadzovať konkrétny typ za typový argument rozhrania Usable, ale použije vlastný typový parameter, ktorým odovzdá konkrétny typ použitý v jej podtriedach rozhraniu Usable.

Metódu use() z pôvodnej implementácie BreakableTool upravte tak, aby prekrývala metódu useWith() z rozhrania Usable. Nezabudnite na anotáciu @Override.

Úloha 4.3

Upravte konkrétne implementácie triedy BreakableTool tak, aby boli kompatibilné s úpravami z predošlej úlohy.

Typovým parametrom pre BreakableTool špecifikujte typ aktéra, s ktorým môže daný nástroj pracovať (ktorý vie opraviť; napr. kladivo vie opraviť reaktor).

V jednotlivých nástrojoch prekryte implementáciu metódy useWith() a vykonajte príslušnú opravu.

Nezabudnite upraviť metódy opravy reaktora repairWith() a extinguishWith(), ktoré teraz premenujte na repair() a extinguish(), keďže opravu vyvolajú samotné nástroje kladivo, resp. hasiaci prístroj. Pri týchto upravených metódach využite návratovú hodnotu boolean na signalizáciu úspešnosti, resp. neúspešnosti použitia nástroja.

Úloha 4.4

Pridajte rozhranie Repairable reprezentujúce opraviteľných aktérov.

V rozhraní definujte metódu so signatúrou boolean repair(). Návratová hodnota bude vyjadrovať úspešnosť, resp. neúspešnosť opravy.

Úloha 4.5

Upravte triedu Reactor nech implementuje rozhranie Repairable.

Úloha 4.6

Upravte triedu DefectiveLight tak, aby tiež implementovala rozhranie Repairable.

Oprava svetla (svetlo prestane blikať) však vydrží len 10 sekúnd a potom sa svetlo opäť pokazí.

Gamelib

Metódy scheduleAction() na scéne a scheduleFor() na akcii vracajú objekt typu Disposable. Zavolanie metódy dispose() na takomto objekte zruší naplánovanie, resp. preruší vykonávanie akcie, ktorá bola volaním danej schedule* metódy naplánovaná. Možnosť zrušiť skôr naplánované akcie sa vám zíde pri riešení tejto úlohy.

Úloha 4.7

Vytvorte triedu Wrench pre francúzsky kľúč, ktorý bude podobne, ako kladivo, použiteľný na opravu pokazených zariadení - konkrétne zariadenia DefectiveLight.

Nech Wrench rozširuje triedu BreakableTool a má 2 použitia. Pre jeho grafickú reprezentáciu použite obrázok wrench.

Krok 5: Repository

Úloha 5.1

Nahrajte (cez commit a push) váš zdrojový kód do repozitára na GitLab-e. Spravte tak do nasledujúceho cvičenia. Zároveň si pripravte otázky, ktoré by ste na cvičení chceli vyriešiť.

Zdrojový kód nahrajte aj v prípade, ak ste nestihli dokončiť všetky úlohy. Rozpracované časti, ktoré by mohli spôsobiť kompilačné chyby, odporúčame zakomentovať.

Doplňujúce úlohy

Úloha A.1

Vytvorte triedu TimeBomb, ktorá bude predstavovať časovanú bombu.

Trieda by mala obsahovať:

  • parametrický konštruktor, pomocou ktorého nastavíte čas (v sekundách, typ float), ktorý má uplynúť od aktivácie bomby po jej detonáciu,
  • verejnú metódu activate(), ktorá bude slúžiť na aktiváciu bomby, pri ktorej sa spustí odpočítavanie do výbuchu. Pre animáciu bomby pred aktiváciou použite obrázok bomb. Pri aktivácii nech bomba začne iskriť (použite obrázok bomb_activated).
  • verejnú metódu boolean isActivated(), ktorá vráti, či je bomba aktuálne aktivovaná.

Pri detonácii bomby použite animáciu small_explosion (stiahnite si obrázok k ostatným animáciám v projekte). Zabezpečte, aby sa animácia prehrala jedenkrát (použite vhodný playMode animácie) a objekt potom zmizol z hernej scény (použite vhodné metódy na objekte animácie pre určenie toho, či animácia už skončila).

Poznámka

Pri implementácii odstránenia aktéra bomby zo scény odporúčame využiť akciu When.

Úloha A.2

Vytvorte triedu ChainBomb, ktorá bude potomkom triedy TimeBomb. Zabezpečte, aby sa pri výbuchu aktivovali všetky bomby typu ChainBomb, ktoré sú vzdialené 50 (a menej) jednotiek od stredu aktuálnej aktivovanej bomby.

Konštruktor triedy ChainBomb bude tiež akceptovať v parametri čas od aktivácie do detonácie.

Uvedomte si, že každý následný výbuch môže spôsobiť ďalšie výbuchy pre dominový efekt: v momente detonácie jednej bomby sa aktivujú všetky ešte neaktivované bomby v dosahu.

Pre rádius výbuchu môžete využiť triedu Ellipse2D.Float z balíka java.awt.geom a pre dočasnú reprezentáciu animácie okolitých aktérov triedu Rectangle2D.Float (z toho istého balíka). Následne je možné použiť metódu intersects nad útvarom elipsy pre zistenie vzájomného prekrytia.

Pri implementácii dbajte na to, aby ste neopakovali už implementovanú funkcionalitu predka. V prípade potreby ho refaktorujte.

Úloha A.3

Vytvorte triedu Teleport, ktorou budete modelovať teleportovanie hráča medzi dvoma miestami vo svete.

Funkcia teleportu má byť nasledovná:

  • Ak hráč vojde na teleport A, bude okamžite presunutý na cieľový teleport B. Hráč vojde na teleport až vtedy, keď sa bod v strede animácie hráča dostane do oblasti definovanej animáciou teleportu.
  • Ak bol hráč práve presunutý na teleport A z iného teleportu, nebude presunutý na cieľový teleport teleportu A pokiaľ z celého priestoru teleportu A najskôr nevyjde a nevráti sa späť.
  • V prípade, že teleport A nemá pripojený cieľový teleport, hráč nebude nikam teleportovaný.
  • Teleport A nemôže mať ako cieľ nastavený sám seba (teleport A).

Trieda Teleport by mala obsahovať:

  • Konštruktor, ktorý umožní prostredníctvom parametra nastaviť cieľový teleport, a ktorý použije obrázok lift pre animáciu teleportu.
  • Metódy getDestination() a setDestination(Teleport destinationTeleport), ktorým je možné získať referenciu na cieľový teleport, resp. zmeniť cieľový teleport.
  • Metódu teleportPlayer(Player player), ktorou cieľový teleport nastaví novú pozíciu hráča pri teleportovaní. Hráč má byť premiestnený tak, aby súradnice stredu cieľového teleportu a súradnice stredu hráča boli totožné.

Úloha A.4

Využite značky (angl. markers) v mape na umiestňovanie aktérov na preddefinované pozície. (Voliteľná úloha)

Mapa scény, ktorú používame, obsahuje niekoľko značiek, ktoré môžu uľahčiť umiestňovanie aktérov. Tieto značky sú typu MapMarker a je ich možné získať z mapy scény metódou getMarkers():

Map<String, MapMarker> markers = scene.getMap().getMarkers();

Poznámka

Typ Map (java.util.Map<K, V>) reprezentuje údajovú štruktúru zobrazenie obsahujúcu záznamy pozostávajúce z kľúča (key) typu K a hodnoty (value) typu V. V uvedenom prípade toto zobrazenie obsahuje kľúče definované reťazcami (String) s názvami jednotlivých značiek, ku ktorým sú priradené dané značky (MapMarker).

Mapa scény obsahuje niekoľko takýchto značiek, ktorých mená a umiestnenie sú zobrazené na nasledovnom obrázku.

MapMarker objekty v mape scény.
Obr. 6: MapMarker objekty v mape scény.

Umiestnenie reaktora do scény teda môžeme pomocou značiek prepísať takto:

// ziskanie referencie na marker nazvany "reactor-area-1"
MapMarker reactorArea1 = markers.get("reactor-area-1");

//umiestnenie reaktora na poziciu markera
scene.addActor(reactor, reactorArea1.getPosX(), reactorArea1.getPosY());

Doplňujúce zdroje