Laboratorium 6 #
Polimorfizm i Wyjątki #
1. Wprowadzenie #
Programowanie obiektowe (OOP) w C++ opiera się na kilku fundamentalnych zasadach, które umożliwiają tworzenie modułowego, wielokrotnego użytku i łatwego w utrzymaniu kodu. Cztery kluczowe filary OOP to abstrakcja, hermetyzacja, dziedziczenie i polimorfizm.
- Abstrakcja (ang. abstraction) polega na ukrywaniu szczegółów implementacyjnych i eksponowaniu jedynie istotnych cech obiektu, co pozwala użytkownikowi skupić się na tym, co dana klasa robi, a nie jak to robi.
- Hermetyzacja (ang. encapsulation) polega na ukrywaniu szczegółów działania obiektu przed jego użytkownikiem oraz na oddzieleniu interfejsu od implementacji.
- Dziedziczenie (ang. inheritance) pozwala na tworzenie nowych klas (klas pochodnych) na bazie istniejących klas (klas bazowych), co promuje ponowne wykorzystanie kodu.
- Polimorfizm (ang. polymorphism), czyli inaczej wielopostaciowość, umożliwia traktowanie obiektów różnych klas poprzez wspólny interfejs (klasy bazowej).
Zadanie polega na stworzeniu prostego systemu walk pomiędzy różnymi klasami postaci.
Skorzystaj z dostarczonego pliku main.cpp, w którym umieszczone zostały testy do wszystkich etapów zadania.
Po zakończeniu implementacji konkretnego etapu odkomentuj odpowiadającą mu część funkcji main() i porównaj swój wynik z oczekiwanym (pamiętaj o załączeniu stworzonych przez ciebie plików w main.cpp).
Tym razem nie został dostarczony żaden plik startowy do żadnego z etapów zadania. Klasy powinny być napisane od zera.
Etap 1: Podstawowe Dziedziczenie i Polimorfizm #
W pliku Character.hpp zdefiniuj abstrakcyjną klasę bazową Character. Powinna ona przechowywać podstawowe informacje o postaci (name, health). Kluczowym elementem jest czysto wirtualna metoda attack(), która pozwala na polimorficzne zachowanie różnych typów postaci.
Pola prywatne:
std::string name- nazwa postaciint health- poziom życia postaciint maxHealth- maksymalny/początkowy poziom życia postaci
Konstruktor:
- Konstruktor przyjmujący dwa argumenty, nazwę postaci oraz ilość punktów życia - domyślnie 100.
Metody:
getName(),getHealth(),isAlive()- gettery na pola klasytakeDamage(int)- metoda do zadawania obrażeńheal(int)- metoda do leczenia postaciattack(Character* target)- wirtualna metoda
Ponieważ jest to klasa abstrakcyjna, pamiętaj również o zadeklarowaniu wirtualnego destruktora!
Dodaj dwie klasy dziedziczące po Character: Warrior oraz Mage.
W klasie Warrior dodaj prywatne pole int meleeDamage oraz konstruktor przyjmujący:
const std::string& nameint health, domyślnie wartość 120int damage, domyślnie wartość 15
W klasie Mage dodaj prywatne pole int spellDamage oraz konstruktor przyjmujący:
const std::string& nameint health, domyślnie wartość 80int mana, domyślnie wartość 150int damage, domyślnie wartość 20
Dla każdej z tych klas zaimplementuj metodę attack, w której zadawane są odpowiednio obrażenia przeciwnikowi o wartościach meleeDamage oraz spellDamage.
Etap 2: Wielodziedziczenie i Wyjątki #
Rozszerz system o wielodziedziczenie. Zdefiniuj dwie nowe “klasy cech”: CanCastSpells oraz CanUseMelee.
Zdefiniuj klasę wyjątku NoManaException, dziedziczącą po std::exception. Zaimplementuj metodę what() zwracającą opis błędu.
Klasa CanCastSpells posiada dwa pola:
int mana- informacja o aktualnej ilości manyint maxMana- maksymalna ilość many postaci
Oraz metody:
getMana()- getter dla manyaddMana(int amount)- metoda dodaje wskazaną ilość many zachowując limitmaxManauseMana(int amount)- metoda zużywa wskazaną ilość many. W przypadku zbyt małej ilość powinna rzucać wyjątekNoManaExceptioncastSpell(Character* target)- metoda czysto wirtualna, będziemy ją później implementować w klasie postaci
Klasa CanUseMelee posiada jedynie metodę:
performMeleeAttack(Character* target)- tak samo jakcastSpell, metoda czysto wirtualna
Dodaj do klas Warrior oraz Mage odpowiednio dziedziczenie po CanUseMelee oraz CanCastSpells i zaimplementuj wymagane metody. Zmodyfikuj metody attack() tak by używały metod castSpell oraz performMeleeAttack. Rzucenie zaklęcia powinno zabierać 10 many.
Zdefiniuj klasę BattleMage dziedziczącą po Character, CanCastSpells, CanUseMelee.
Zaimplementuj konstruktor BattleMage:
std::string nameint health, domyślnie 100int mana, domyślnie 100int meleeDmg, domyślnie 10int spellDmgdomyślnie 15
Zaimplementuj polimorficzną metodę attack() w BattleMage. Wewnątrz tej metody losuj, czy użyć magii czy ataku fizycznego. Obsłuż potencjalny NoManaException wewnątrz metody attack, w przypadku braku many wykonaj atak wręcz performMeleeAttack. Załóż, że rzucenia zaklęcia zużywa 8 many (castSpell).
Etap 3: RTTI (typeid, dynamic_cast)
#
Dodaj klasę Rogue dziedziczącą po Character oraz po CanUseMelee.
Klasa ta posiada dwa pola:
int basicAttackDamageint backstabDamage
Konstruktor przyjmuje:
std::string nameint health, domyślnie 90int basicDmg, domyślnie 12int backstabDmgdomyślnie 30
Zmodyfikuj metodę attack() w Mage aby używała dynamicznego rzutowania do sprawdzenia, czy cel jest Wojownikiem lub posiada zdolności walki wręcz, zadając bonusowe obrażenia w takim przypadku (+10).
Zaimplementuj specjalną metodę backstab(Character* target) w klasie Rogue. Użyj mechanizmu RTTI, aby sprawdzić, czy cel nie jest Magiem/Czarującym. Jeśli warunek spełniony, zadaj duże obrażenia (backstabDamage). W przeciwnym razie, wypisz komunikat, że dźgnięcie w plecy (backstab) się nie powiódł.
Zaimplementuj polimorficzną metodę attack() w klasie Rogue. Wewnątrz tej metody losuj, czy użyć zwykłego ataku wręcz czy spróbować wykonać dźgnięcie w plecy.