Exerciții
Această pagină cuprinde exerciții care au fost taskuri de laborator și acum înlocuite cu altele.
Puteți folosi aceste exerciții pentru a vă exersa noțiunile învățate la curs și la laboratoare.
Elemente de sintaxă, primitive, stringuri
Numere complexe
Creați un proiect nou cu numele ComplexNumber
.
Creați clasa ComplexNumber.java
. Aceasta va avea două campuri: re
si im
, ambele de tip float
.
Operații
Creați clasa Operations.java
. Această clasă va implementa operațiile de adunare și înmulțire pentru numere complexe. Definiți în clasă Operations
câte o metodă pentru fiecare operație;
Testați metodele implementate.
Biblioteca
Creați un proiect nou cu numele Bookstore
.
Creați clasa Book.java
cu următoarele atribute: title
, author
, publisher
, pageCount
.
Creați clasa
BookstoreTest.java
, pentru a testa viitoarele funcționalități ale bibliotecii. Completați această clasă, așa cum considerați necesar. Apoi, creați un obiect de tip carte și setați atributele introducând date de la tastatură. Pentru această folosiți clasa
Scanner
:
Scanner s = new Scanner(System.in);
String title = s.nextLine();
Verificați ca numărul de pagini introdus să fie diferit de zero. Puteți consulta documentația claselor
String
și
Integer
.
Verificări cărți
Creați o clasă nouă, BookstoreCheck
, ce va conține două metode, cu câte 2 parametri de tipul Book
.
Prima metodă va verifica dacă o carte este în dublu exemplar, caz în care va întoarce adevărat.
A doua metodă va verifica care carte este mai groasă decât altă, și va întoarce cartea mai groasă.
Testați aceste doua metode.
Formatare afișare
BOOK_TITLE: [insert_book_title]
BOOK_AUTHOR: [insert_book_author]
BOOK_PUBLISHER: [insert_book_publisher]
Cu următoarele precizări:
Titlul cărții va fi scris în întregime cu majuscule
Numele editurii va fi scris în întregime cu minuscule
Numele autorului rămânând neschimbat
Specificatori de acces
Implementaţi o clasă MyArrayList, care va reprezenta un vector de numere reale, cu posibilitatea redimensionării automate. Ea va fi definită în pachetul arrays. Clasa va conţine următoarele metode:
un constructor fără parametri, care creează intern un vector cu 10 elemente
un constructor cu un parametru de tipul întreg, care creează un vector de dimensiune egală cu parametrul primit
o metodă numită add(float value)
, care adaugă valoarea value
la finalul vectorului. Dacă se depăşeşte capacitatea vectorului, acesta se va redimensiona la o capacitate dublă
o metodă numită contains(float value)
care returnează true
dacă value
există în cadrul vectorului
o metodă numită remove(int index)
care elimină valoarea aflată în vector la poziţia specificată de index
(numerotarea incepând de la 0); se va da un mesaj dacă indexul este invalid
o metodă numită get(int index)
care va returna elementul aflat în poziţia index
o metodă numită size()
care returnează numarul de elemente din vector
o metodă declarată public String toString()
care va returna o reprezentare a tuturor valorilor vectorului ca un şir de caractere
Scrieţi o clasă de test separată pentru clasa MyArrayList, populând lista cu 3 elemente şi verificând că valorile întoarse de metoda get corespund, într-adevăr, poziţiilor aferente din vectorul intern clasei. Ce condiţii trebuie îndeplinite pentru atingerea scopului propus?
Scrieţi un scenariu de utilizare a clasei MyArrayList, astfel:
iniţializând-o cu o capacitate de 5 de elemente iar apoi inserând 10 elemente aleatoare utilizând doar metoda add
cautând 5 valori aleatoare în vector
eliminând 5 valori aleatoare din vector
De multe ori este bine ca programarea unor aplicaţii mari să se faca modular, un modul fiind gestionat de o anumită clasă. De asemenea, în cadrul acestor aplicaţii, puteţi avea situaţii în care o metodă are nevoie să utilizeze mai multe module, deci să primească referinţele mai multor clase, fapt ce poate deveni dificil de gestionat. De aceea, aceste clase se pot implementa limitând accesul la
o singură instanţă statică, ceea ce permite accesarea acesteia doar pe baza
clasei, fară a mai fi necesară trimiterea ei ca parametru in metode. Aşa descoperim un prim
design pattern orientat-obiect; el se numeşte
Singleton şi puteţi afla câteva despre el
aici. Ne propunem să implementăm o variantă simplă. Creaţi clasa
Singleton, care să
aibă intern o singură instanţă statică
aibă un constructor privat (de ce e nevoie?)
expună o metodă publică de acces la instanţă (un getter)
Agregare și moștenire
Întrucât în ierarhia de clase Java, clasa Object
se află în rădăcina arborelui de moștenire pentru orice clasă, orice clasă va avea acces la o serie de facilități oferite de Object
. Una dintre ele este metoda toString()
, al cărei scop este de a oferi o reprezentare a unei instanțe de clasă sub forma unui șir de caractere.
Definiți clasa Form
cu un membru color
de tip String
, o metoda getArea()
care pentru început va intoarce 0 și o metodă toString()
care va returna acestă culoare.
Clasa va avea, de asemenea:
Din ea derivați clasele Triangle
și Circle
:
Instanțiati clasele Triangle
și Circle
, și apelați metoda toString()
pentru fiecare instanță.
suprascrieti metoda getArea()
pentru a intoarce aria specifica figuri geometrice.
Adăugați metode toString()
în cele două clase derivate, care să returneze tipul obiectului, culoarea si aria. De exemplu:
pentru clasa Triangle
, se va afișa: “Triunghi: rosu 10”
pentru clasa Circle
, se va afișa: “Cerc: verde 12.56”
Modificați implementarea toString()
din clasele derivate astfel încât aceasta să utilizeze implementarea metodei toString()
din clasa de bază.
Adăugați o metodă equals
în clasa Triangle
. Justificați criteriul de echivalentă ales.
Hint: vedeți metodele clasei
Object, moștenită de toate clasele - Object are metoda
equals
, a cărei implementare verifică echivalența obiectelor comparând referințele.
Upcasting.
Downcasting.
Adăugați clasei Triangle
metoda printTriangleDimensions
și clasei Circle
metoda printCircleDimensions
. Implementarea metodelor constă în afișarea bazei si inaltimii respectiv razei.
Parcurgeți vectorul de la exercițiul anterior și, folosind downcasting la clasa corespunzătoare, apelați metodele specifice fiecărei clase (printTriangleDimensions
pentru Triangle
și printCircleDimensions
pentru Circle
). Pentru a stabili tipul obiectului curent folosiți operatorul instanceof
.
Modificați programul anterior astfel încât downcast-ul să se facă mereu la clasa Triangle
. Ce observați?
Modificați programul anterior astfel încât să nu mai aveți nevoie de instanceof
deloc.
Implementați două clase
QueueAggregation
și
QueueInheritance
pe baza clasei
Array
furnizate de noi, utilizând, pe rând, ambele abordări:
moștenire și
agregare. Precizări:
Coada va conține elemente de tip int
.
Clasele QueueAggregation
și QueueInheritance
trebuie să ofere metodele enqueue
și dequeue
, specifice acestei structuri de date.
Clasa Array
reprezintă un wrapper pentru lucrul cu vectori. Metoda get(pos)
întoarce valoarea din vector de la poziția pos
, în timp ce metoda set(pos, val)
atribuie poziției pos
din vector valoarea val
. Noutatea constă în verificarea poziției furnizate. În cazul în care aceasta nu se încadrează în intervalul valid de indici, ambele metode întorc constanta ERROR
definită în clasa.
Metoda main
definită în clasa Array
conține exemple de utilizare a acestei clase. Experimentați!
Metoda enqueue
va oferi posibilitatea introducerii unui număr întreg în capătul cozii (dacă aceasta nu este deja plină), în timp ce metoda dequeue
va înlătura elementul din vârful cozii și îl va întoarce (dacă coada nu este goală). În caz de insucces (coada plină la enqueue
, respectiv goală la dequeue
), ambele metode vor întoarce constanta ERROR
.
Ce problemă poate apărea din cauza constantei ERROR
? (Hint: Dacă în coadă am un element egal cu valoarea constantei ERROR
?) Gândiți-vă la o rezolvare.
Ce puteți spune despre vizibilitatea metodelor get
și set
, în clasele QueueAggregation
și QueueInheritance
, în varianta ce utilizează moștenire? Ce problemă indică răspunsul? Furnizați o soluție la această problemă.
-
Clase abstracte și interfețe
1. (2p) Implementaţi interfaţa Task
(din pachetul first
) în cele 3 moduri de mai jos.
Un task care să afișeze un mesaj la output. Mesajul este specificat în constructor. (OutTask.java
)
Un task care generează un număr aleator și afișează un mesaj cu numărul generat la output. Generarea se face în constructor. (RandomOutTask.java
)
Un task care incrementează un contor global și afișează valoarea contorului după fiecare incrementare (
CounterOutTask.java
).
Notă: Acesta este un exemplu simplu pentru
Command Pattern
2. (3p) Interfaţa Container
(din pachetul second
) specifică interfaţa comună pentru colecţii de obiecte Task, în care se pot adăuga și din care se pot elimina elemente. Creaţi două tipuri de containere care implementează această clasă: 1. (1.5p) Stack
- care implementează o strategie de tip LIFO
2. (1.5p) Queue
- care implementează o strategie de tip FIFO
Evitaţi codul similar în locuri diferite!
Hint: Puteţi reţine intern colecţia de obiecte, utilizând clasa ArrayList din SDK-ul Java. Iată un exemplu de iniţializare pentru șiruri:
ArrayList<String> list = new ArrayList<String>();
3. (2p) Implementaţi interfaţa IFactory
(clasa ContainerFactory
, din pachetul third
) care conţine o metodă ce primește ca parametru o strategie (enum Strategy
) și care întoarce un container asociat acelei strategii. Din acest punct înainte, în programul vostru veţi crea containere folosind doar această clasă (nu puteţi crea direct obiecte de tip Stack sau Queue). Evitaţi instanţierea clasei Factory implementate de voi la fiecare creare a unui container! Notă:Acest mod de a crea obiecte de tip Container elimină problema care apare în momentul în care decidem să folosim o implementare diferită pentru un anumit tip de strategie și nu vrem să facem modificări și în restul programului. De asemenea o astfel de abordare este utilă când avem implementări care urmăresc scopuri diferite (putem avea un Factory care să creeze containere optimizate pentru viteză sau un Factory cu containere ce folosesc minimum de memorie). șablonul acesta de programare poartă denumirea de Factory Method Pattern.
4. (3p) Extindeţi clasa AbstractTaskRunner
(din pachetul fourth
) în 3 moduri:
(
1p)
PrintTimeTaskRunner
- care afișează un mesaj după execuţia unui task în care se specifică ora la care s-a executat task-ul (vedeți clasa
Calendar).
(1p) CounterTaskRunner
- incrementează un contor local care ţine minte câte task-uri s-au executat.
(1p) RedoBackTaskRunner
- salvează fiecare task executat într-un container în ordinea inversă a execuţiei și are o metodă prin care se permite reexecutarea task-urilor.
Schelet: lab4_schelet_2018.zip
Clase interne
Implementați un terminal bash simplu pornind de la scheletul de cod. Comenzile pe care va știi să le execute sunt: echo, cd, ls și history. Bash-ul va citi comenzi de la tastatură până când va primi comanda exit când se va închide (programul se termină).
În clasa BashUtils din pachetul bash vom implementa fiecare comandă ca o clasă internă.
În clasa Bash din pachetul bash vom citi comenzi de la tastatură și le vom trimite spre BashUtils spre a fi executate.
-
Concret: Bash-ul va citi comenzile de la tastatură și le va publica către toți subscriberii săi. Bash-ul va funcționa ca un Publisher.
Utilitarele, ls, echo, cd și history, care știu cum să execute comenzile se vor înregistra la Publisher folosind metoda subscribe a acestuia și vor fi anunțate când o nouă comandă este primită. Ele vor funcționa ca Subscriberi.
În scheletul de cod veți găsi 2 interfețe:
Observăm că un CommandPublisher face publish la un obiect de tip Command, iar un CommandSubscriber primește în metoda executeCommand un astfel de obiect ca parametru.
Găsiți în scheletul de cod implementarea clasei Command. Acesta este obiectul prin care Publisherul și Subscriberii comunică aka își trimit date.
1. (1.5p) Din clasa Bash vom publica comenzile prin interfața CommandPublisher. În acest sens în clasa Bash vom crea o clasă internă BashCommandPublisher ce implementează interfața CommandPublisher.
2. (1p) Un obiect de tipul Bash va avea:
un director current reținut în membrul
currentDirectory care este de tipul
Path
-
3. (2p) În metoda start din Bash vom citi de la tastatură comenzi pe câte o linie folosind Scanner.
Pentru a porni un Thread apelăm metoda start a acestuia. Implementarea metodei run și instantierea Thread-ului nu îl lansează în execuție.
Thread t = new Thread() {
public void run() {
// Do some work
}
};
t.start();
4. (1p) Implementați comanda echo ca o clasă internă în BashUtils.
Clasa va trebui să implementeze interfața CommandSubscriber.
Metoda executeCommand va trebui să:
Creați un obiect de tipul clasei Echo în constructorul clasei Bash. Înregistrați obiectul ca subscriber la instanța de CommandPublisher folosind metoda subscribe.
Testați rulând metoda main din clasa LinuxOS.
5. (1.5p) Implementați comanda cd care schimbă directorul curent.
Clasa va trebui să implementeze interfața CommandSubscriber.
Metoda executeCommand va trebui să:
Creați un obiect de tipul clasei Cd în constructorul clasei Bash. Înregistrați obiectul ca subscriber la instanța de CommandPublisher folosind metoda subscribe.
Testați rulând metoda main din clasa LinuxOS.
6. (2p) Implementați comanda ls care afișează conținutul directorului curent currentDirectory
File folder = currentDirectory.toFile();
Creați un obiect de tipul clasei Ls în constructorul clasei Bash. Înregistrați obiectul ca subscriber la instanța de CommandPublisher folosind metoda subscribe.
Testați rulând metoda main din clasa LinuxOS.
7. (1p) Implementați comanda history care afișează la consolă conținutul membrului StringBuffer history din Bash.
>history
History is: ls | | cd .idea | ls | history |
Clasa va trebui să implementeze interfața CommandSubscriber.
Metoda executeCommand va trebui să:
Creați un obiect de tipul clasei History în constructorul clasei Bash. Înregistrați obiectul ca subscriber la instanța de CommandPublisher folosind metoda subscribe.
Testați rulând metoda main din clasa LinuxOS.
Visitor pattern, Overriding, Overloading
Scheletul de laborator conține implementarea folosind pattern-ul Visitor a scenariului Employee-Manager descris
in laborator. Spre deosebire de exemplele din laborator, clasa Employee conține și câmpul extraHours, relevant pentru exercițiul 3.
a) Rulați codul și observați comportamentul și interacțiunea dintre obiectele vizitate și obiectul de tip Visitor.
b) Nu mai suprascrieți metoda accept
din Manager, rulați și explicați rezultatul execuției.
Obiectele Employee-Manager pot fi reprezentate printr-o structură arborescentă, ce are ca rădăcină un Manager (ceo-ul). Creați un Visitor care să permită parcurgerea ierarhiei și efectuarea unei acțiuni pe fiecare nod. Acea acțiune este practic o operație, implementată într-o altă clasă de tip Visitor, deci TreeVisitor-ul va primi un obiect de tip Visitor pe care să îl aplice pe nodurile parcurse.
fiecare Manager va ţine referinţe către angajaţii aflaţi sub răspunderea lui directă (ce pot fi alţi șefi la rândul lor, sau salariaţi obişnuiţi)
implementați un TreeVisitor care pentru:
implementați un visitor (numit MostHardworkingEmployeeFinder) care compară numărul mediu de ore suplimentare pentru angajați cu cel pentru șefi.
Adăugați încă un tip de obiect vizitabil - Intern. Acesta nu are salariu și extra hours, doar nume și durata (în luni) a internship-ului.
modificați clasele existente deja, pentru a lua în considerare și obiectele Intern
testați operațiile de la exercițiile anterioare pe o colecție care conține și obiecte Intern
Observați modificările pe care le-ați efectuat pentru a adăuga o nouă operație (ex. 2) și pe cele pentru a adăuga un nou tip de obiect în colecție. Ca să merite să aplicăm pattern-ul Visitor, ce situație ar trebui să fie evitată?
Găsiți folosind java.nio
toate fișierele cu extensia “.class” dintr-un director.
-
-
Clasele din
API-ul
nio folosite pentru acest exercițiu sunt disponibile începând cu jdk7.
Colecții
Instanţiati o colecţie care sǎ nu permitǎ introducerea elementelor duplicate, folosind o implementare corespunzǎtoare din bibliotecă. La introducerea unui element existent, semnalaţi eroare. Colecţia va reţine String
-uri şi va fi parametrizatǎ.
Creaţi o clasǎ Student
.
Adǎugaţi urmǎtorii membri:
Folosiţi codul de la exerciţiul anterior şi modificaţi-l astfel încât colecţia aleasǎ de voi sǎ reţinǎ obiecte de tip Student
. Testaţi prin adǎugare de elemente duplicate, având aceleaşi valori pentru toate câmpurile, instanţiindu-le, de fiecare datǎ, cu new
. Ce observaţi?
Prelucraţi implementarea de mai sus astfel încât colecţia sǎ reprezinte o tabelǎ de dispersie, care calculează codul de dispersie al elementelor dupǎ un criteriu ales de voi (puteţi suprascrie funcţia hashCode).
Plecând de la implementarea exerciţiului anterior, realizaţi urmǎtoarele modificǎri:
Supraîncǎrcaţi, în clasa Student
, metoda equals
, cu o variantǎ care primeşte un parametru Student
, şi care întoarce întotdeauna false
.
Testaţi comportamentul prin crearea unei colecţii ce conţine instanţe de Student
şi iteraţi prin această colecţie, afişând la fiecare pas element.equals(element)
şi ((Object)element).equals(element)
(unde element
este numele de variabilă ales pentru fiecare element al colecţiei). Cum explicaţi comportamentul observat? Dacă folosiţi un iterator, acesta va fi şi el parametrizat.
Creați clasa Gradebook
, de tip Map
, pentru reţinerea studenţilor dupǎ medie: cheile sunt mediile și valorile sunt liste de studenți. Gradebook va menţine cheile ordonate descrescǎtor. Extindeţi o implementare potrivitǎ a interfeţei Map
, care sǎ permitǎ acest lucru.
Caracteristicile clasei definite sunt:
Cheile pot avea valori de la 0 la 10 (corespunzǎtoare mediilor posibile). Verificați acest lucru la adăugare.
Valoarea asociată fiecǎrei chei va fi o listǎ (List
) care va reţine toţi studenţii cu media rotunjitǎ egalǎ cu cheia. Considerǎm cǎ un student are media rotunjitǎ 8 dacǎ media sa este în intervalul [7.50, 8.49].
Implementați un
Comparator pentru stabilirea ordinii cheilor. Gradebook va primi un parametru de tip
Comparator
în constructor și îl va da mai departe constructorului clasei moștenite.
Definiţi în clasǎ metoda add(Student)
, ce va adǎuga un student în lista corespunzǎtoare mediei lui. Dacǎ, în prealabil, nu mai existǎ niciun student cu media respectivǎ (rotunjitǎ), atunci lista va fi creatǎ la cerere.
Testați clasa:
instanțiați un obiect Gradebook și adăugați in el câţiva studenţi.
iteraţi pe Gradebook şi sortaţi alfabetic fiecare listǎ de studenţi pentru fiecare notă. Pentru a sorta, se va folosi metoda
Collections.sort, iar clasa Student va implementa o interfață care specifică modul în care sunt comparate elementele.
Creaţi o clasǎ care moşteneşte HashSet<Integer>
.
Definiţi în aceastǎ clasǎ o variabilǎ membru care reţine numǎrul total de elemente adǎugate. Pentru a contoriza acest lucru, suprascrieți metodele add
şi addAll
. Pentru adǎugarea efectivǎ a elementelor, folosiţi implementǎrile din clasa pǎrinte (HashSet
).
Testaţi, folosind atât add
cât şi addAll
. Ce observaţi? Corectaţi dacǎ este cazul.
Modificaţi implementarea astfel încât clasa voastrǎ sǎ moşteneascǎ LinkedList<Integer>
. Ce observaţi? Ce concluzii trageţi?
-
-
Excepții
(2p) Scrieţi o metodă (scurtă) care să genereze
OutOfMemoryError şi o alta care să genereze
StackOverflowError. Verificaţi posibilitatea de a continua rularea după interceptarea acestei erori. Comparaţi răspunsul cu posibilitatea de a realiza acelaşi lucru într-un limbaj compilat, ce rulează direct pe platforma gazdă (ca C).
(2p) Demonstraţi într-un program execuţia blocului finally
chiar şi în cazul unui return
din metoda.
Design Patterns
Singleton, Observer, Factory
Exercițiile din această secțiune și din urmatoarea au ca temă comună realizarea unui joc controlat din consolă. Jocul constă dintr-o lume (aka hartă) în care se plimbă eroi de trei tipuri, colectează comori și se bat cu monștri. În acestă secțiune trebuie să implementați o parte din funcționalitățile jocului folosind patternurile Singleton, Factory și Observer, urmând ca în secțiunea următoare să terminați implementarea.
Schelet: Schelet
Detalii joc:
Harta
reprezentată printr-o matrice. Fiecare element din matrice reprezintă o zonă care poate fi liberă, poate conține obstacole sau poate conține o comoară (în secțiunea următoare poate conține și monștrii).
este menținută în clasa World
.
Eroii
sunt reprezentați prin clase de tip Hero
și sunt de trei tipuri: Mage, Warrior, Priest.
puteți adăuga oricâți eroi doriți pe hartă (cât vă permite memoria :))
într-o zonă pot fi mai mulți eroi
acțiunile pe care le pot face:
move
- se mută într-o zonă învecinată
attack
(de implementat în laboratorul următor)
collect
- eroul ia comoara găsită în zona în care se află
Entry-point-ul în joc îl consitituie clasa Main
.
Folosiți design pattern-ul Factory pentru crearea obiectelor.
Creati clase care mostenesc Hero
pentru fiecare tip de erou.
Uitați-vă la clasele TreasureFactory
și HeroFactory
. Trebuie să implementăm două metode: createTreasure
în TreasureFactory
și o metodă de creare de eroi în HeroFactory
, fie ea createHero
.
Puteți pune orice date doriți în comori, respectiv eroi.
La HeroFactory.createHero
, pasați ca parametru un Hero.Type
și un String
cu numele eroului și întoarceți un subtip de Hero
potrivit pentru tipul de erou.
După ce ați creat factory-urile, folosiți-le:
Completați metoda populateTreasures
din World
. Folosiți-vă de membrii map
și treasures
din World
. Trebuie să marcați pe hartă că aveți o comoară și să adăugați obiectul-comoară în lista de comori.
Uitați-vă apoi la cazul add
din metoda main
. Trebuie să adăugați eroi acolo. Folosiți HeroFactory.createHero
.
Folosiți design pattern-ul Singleton pentru elementele din joc care trebuie să aibă doar o instanță.
Folosiți design pattern-ul Observer pentru a monitoriza ceea ce se întâmplă în joc. Scheletul de cod vă sugerează două tipuri de observatori, pentru bonus puteți adăuga și alții.
-
Înainte să vă apucați să scrieți, citiți comentariile din cod (e.g. TreasureDiscoverer) să vă faceți o idee despre ce vrem să facem în clasele observatoare.
Asigurati-va ca implementati corect functionalitatea din Observable. Mare atentie la faptul ca metoda notifyObservers nu va face nimic daca nu este apelata mai intai metoda setChanged.
Care sunt elementele observatoare și care sunt observabile? Uitați-vă și în comentariile din cod.
Implementați interfețele Observer
și Observable
în clasele potrivite.
Înregistrați observatori la World
. Cazul start
din metoda main
.
Notificați observatorii lui World
când eroii execută o acțiune. Aveți două TODO
-uri în clasa Hero
.
Începeți rezolvarea prin implementarea claselor pentru eroi și implementarea design pattern-ului factory pentru crearea lor. Pentru a putea vizualiza harta trebuie să implementați partea de observare a stării jocului. World
trebuie să fie observabilă și să notifice pe observatorii săi atunci când a început jocul și când se schimbă ceva (e.g. s-a mutat un erou).
Fig. 1
Strategy, Command
Această secțiune și cea precedentă au ca temă comună a exercițiilor realizarea unui joc controlat din consolă. Jocul constă dintr-o lume (aka hartă) în care se plimbă eroi de trei tipuri, colectează comori și se bat cu monștrii. În acestă secțiune terminam jocul inceput in cea precedenă folosind pattern-urile Strategy și Command.
Detalii joc:
Harta
reprezentată printr-o matrice. Fiecare element din matrice reprezintă o zonă care poate fi liberă, poate conține obstacole, monștrii sau o comoară
este menținută în clasa GameState
.
Eroii
sunt reprezentați prin clase de tip Hero
și sunt de trei tipuri: Mage, Warrior, Priest.
puteți adăuga oricâți eroi doriți pe hartă (cât vă permite memoria :))
într-o zonă pot fi mai mulți eroi
acțiunile pe care le pot face:
move
- se mută într-o zonă învecinată
attack
- ataca un monstru cand se afla pe aceeasi pozitie cu el
collect
- eroul ia comoara găsită în zona în care se află
Entry-point-ul în joc îl consitituie clasa Main
.
Folosiți design pattern-ul Command pentru a implementa functionalitatea de undo si redo la comanda move
.
Momentan, aveti erori de compilare in clasele Main si GameState. Dupa ce veti implementa acest exercitiu, se vor rezolva, nu modificati in mod direct acolo.
Va trebui sa completati clasa MoveCommand
care implementeaza interfata Command
. Urmariti TODO-urile din aceasta clasa.
Hint: Pentru Undo, de exemplu, daca v-ati deplasat la dreapta, ar trebui sa va deplasati la stanga. Creati-va o metoda ajutatoare care trateaza astfel de cazuri.
Precum si clasa CommandManager
care va tine evidenta comenzilor si ordinea lor.
Hint: Amintiti-va de la cursul de Structuri de Date cum se implementează operatiile Redo si Undo. Folositi doua stive.
Folosiți design pattern-ul Strategy pentru a implementa logica de atac a unui monstru.
Pentru acest exercitiu va trebui sa implementati 2 strategii: AttackStrategy si DefenseStrategy. Ambele vor implementa Strategy si metodele aferente. Fiecare din aceste Strategy, va retine o referinta interna la un Hero. Abordarea este urmatoarea:
Exista 3 clase Hero, fiecare cu cate un DamageType aferent: Warrior - Blunt, Mage - Magic, Priest - Poison
Fiecare monstru are un weakness (slabiciune la atacurile de tip Blunt, Magic sau Poison)
AttackStrategy: In metoda attack(), veti itera prin inventory-ul eroului si veti verifica daca exista un obiect Treasure care are DamageType-ul identic cu cel al eroului. Daca da, atunci damage-ul pe care il veti da unui obiect Monster este 3 x damage-ul treasure-ului. Daca nu aveti un astfel de Treasure, atunci cautati un Treasure cu un DamageType identic cu cel al mob-ului (mobul poate fi vulnerabil la Magic, de ex.). Daca gasiti un astfel de Treasure, damage-ul pe care il veti imprima mob-ului este 2 x damage-ul treasure-ului. Daca nu, veti scade din HP-ul mob-ului rezultatul apelului metodei getBaseDamage() asupra Hero-ului.
DefenseStrategy: In metoda attack(), veti itera prin inventory-ul eroului si veti verifica, la fel, daca exista un obiect Treasure care are DamageType-ul identic cu cel al eroului. Daca da, atunci veti da un boost de HP egal cu treasure.getBoostHp() + getBaseHpBoost() eroului (getBaseHpBoost este implementata in clasa Hero). Daca nu, veti da doar un boost egal cu ce va intoarce getBaseHpBoost(). Damage-ul imprimat mobului va fi, de asemenea, egal cu ce va intoarce getBaseDamage(). Adaugati log-uri in clasa DefenseStrategy, pentru a va asigura ca i se mareste viata eroului.
Urmariti si TODO-urile din cele doua clase
Implementati metoda attack din clasa Hero astfel incat, daca eroul are mai mult de 50HP, folositi strategia AttackStrategy. Altfel, folositi DefenseStrategy. Urmariti TODO-urile din cod.
Implementați coliziunile cu obstacolele de pe hartă
Va trebui sa creati un nou obiect Obstacle
precum si un ObstacleObserver
Cand eroul ajunge pe un obstacol se va printa un mesaj Can't move there !
si se va apela automat undo pe ultima comanda de move pentru a reveni in pozitia anterioara coliziunii. Acest feature de wall collision va fi implementat in ObstacleObserver