Podczas ćwiczeń z Programowania 2 motywem przewodnim zadań będzie pisanie prostej gry RPG w trybie tekstowym. Mam nadzieję, że każdy co najmniej raz w życiu widział jakąkolwiek grę komputerową, a może nawet grę RPG typu Baldur's Gate czy Diablo (w tym miejscu purystów gatunku RPG przepraszam :) ). Naszym celem będzie stworzenie czegoś na kształt Diablo - jedna postać przeciwko hordzie potworów czyhających w lochach. Temat jest na tyle szeroki że pozwoli wykorzystać wszystkie dostępne w OOP możliwości, dodatkowo jest na tyle naturalny, że łatwo budować model problemu - hierarchię dziedziczenia, itp.

W trakcie trwania semestru będę podawał listę czynności, które należny w grze zaimplementować, będę też na zajęciach pokazywał przykładowy kod napisany przezemnie. Kod zostanie potem opublikowany na stronie (przed kolejnymi ćwiczeniami). Proszę jednak starać się pisać samodzielnie - kod nie musi wyglądać i działać dokładnie tak jak mój, można wykorzystywać własną inwencję twórczą.

Na koniec semestru będzie można oddać stworzoną w trakcie zajęć grę jako projekt zaliczeniowy.

Zakładam, że do pisania będziemy używć standardowo Dev-C++. Jeśli ktoś ma inne preferencje np. Microsoft Visual Studio, pisanie w konsoli z makefilem, nie ma problemu, jeśli będzie miał do tych narzędzi dostęp (make jest standardowo instalowany, gorzej z VStudio).

Uwaga do kodów przykładowych po każdych zajęciach.

Jeśli ktoś korzysta z projektu Dev-C++ to powinien po otworzeniu projektu dodać we właściwościach (prawym przyciskiem na nazwie projektu w okienku nawigatora -> właściwości (properties)) ustawić w zakładce directories odpowiednią ścieżkę w kategorii include directories. W plikach zip jest tam ustawiona ścieżka do mojego położenia projektu.

Jeżli ktoś korzysta z make to powinien także ustawić tą ścieżkę w opcjach kompilatora. Tutorial jak to zrobić wkrótce

Te ścieżki są po to, aby można było w projekcie korzystać z #inlcude < _nazwa_lokalnego_pliku_.h > zamiast z cudzysłowu #inlcude " ścieżka/względna/w/projekcie/_nazwa_lokalnego_pliku_.h " . Eliminuje to czasem konieczność użycia niepotrzebnych i brzydko wyglądających ../../ w ścieżce w includzie. Chciałem państwu pokazać jak można to zrobić.

Zadania

czwartek, 3 marca 2011

Omówienie regulaminu / Wstęp do OOP (C++) - dziedziczenie, hierarchia dziedziczenia, typy dziedziczenia, enkapsulacja.

Zadania:

Przed przystąpieniem do pracy stwórz następujące klasy:

  • Creature - to będzie klasa opisująca potwory. Stwórz w niej pola opisujące potwora. Zadbaj o enkapsulację. Dodaj metody pozwalające na dostęp / modyfikację danych. Dodaj pol statyczne służące do liczenia aktualnie istniejących instancji klasy potwór. Stwórz odpowiednie konstruktory i destruktor. Niech co najmniej jeden konstruktor będzie przyjmował jakieś parametry.
  • Adventurer - to będzie klasa opisująca postać gracza. Stwórz w niej kilka pól opisujących gracza, imię atrybuty, ilość punktów życia, itp. Zadbaj o enkapsulacje. Stwórz odpowiednie metody dostępu do danych. Stwórz metodę służącą do atakowania potworów, o sygnaturze np. takiej:
    attack(Creature &c)

Stwórz funkcję main i utwórz tam kilka instancji klasy Creature. Utwórz obiekt klasy Adventurer. Wykonaj kilka razy funkcję attack() na stworzonych potworach.

Gratulacje, napisałeś swoją pierwszą gre RPG! ;)

Jesli pisałeś wszystko w jednym pliku podziel projekt na pliki, jeden z klasą adventurer, jeden z klasą creature, jeden z zawartością main. Zadbaj o zabezpieczenie przed wielokrotnym włączeniem.

  1. Dodaj dziedziczenie do swojego projektu. Jako punkt startu proponuję klasę Creature. Stwórz klasę Monster, która będzie mogła atakować gracza. Pokaż, jak wywołać inny niż domyślny konstruktor klasy bazowej podczas konstrukcji obiektu klasy Monster. Jak dostawać się do składników klasy podstawowej? Do jakich składników klasy podstawowej masz dostęp. Czym różnią się sposoby dziedziczenia: private, protected, public?
  2. Dziedzicząc w klasie Monster z klasy Creature w sposób private pokaż, jak sprawić aby pewne pole / metoda z klasy Creature znów stało/a się publiczne/a.
  3. Stwórz hierarchię dziedziczenia, wywodząc z klasy Monster kilka klas pochodnych będących uszczegółowionymi potworami, np Demon, Goblin, Dragon itp. Z klasy Adventurer możesz wywieść nowe klasy postaci, np. Thief, Mage, Warrior.
  4. Czy w konstruktorze klasy pochodnej od Monster możesz wywołać konstruktor klasy Creature? Dlaczego tak, dlaczego nie?
  5. Które składniki klasy się nie dziedziczą? Dlaczego?
  6. Jakie konwersje są możliwe niejawnie w strukturze hierarchii klas dziedziczenia? Dlaczego akurat te?
Przykładowa gra po 1 zajęciach

czwartek, 10 marca 2011

OOP (C++) - konstruktory, destruktory, rzutowanie, wskaźniki, referencje, dynamic_cast, wstęp do wirtualności.

Zadania:

  1. Napisz kod, który symuluje grę - w kółko tworzy nowych przeciwników i atakuje ich stworzoną postacią gracza.
  2. Dodaj możliwość wybrania na początku gry klasy postaci - czy będzie to wojownik, mag, itp. Wykorzystaj wirtualność, aby nie zmieniać kodu z poprzedniego punktu.
  3. Dodaj możliwość atakowania gracza przez przeciwników. Co musisz zaimplementować w klasie gracza, aby to było możliwe (punkty życia). Czy można wykorzystać dziedziczenie, aby ułatwić sobie pracę (hint: klasa Creature i Adventurer)
  4. Dodaj mechanizm losowego tworzenia potworów. Zaenkapsuluj ten mechanizm w klasie, której jedynym zadaniem będzie dostarczanie nowych potworów. Klasa powinna zwracać wskaźnik do nowo stworzonego potwora, programista korzystający z tej "Faryki potworów" nie powinien się przejmować jakiego typu potwór to jest, do szczęscia powinna wystarczyć mu klasa potwora (Monster).
  5. Zastanów się, czy zamiast tworzyć dla każdego rodzaju ataku osobną funkcję (np. magicalAttack, rangedAttack, normalAttack) nie lepiej stworzyć obiekt, reprezentujący atak, a w klasie do tej pory wykonującej ataki stworzyć jedną funkcję attack() przy użyciu aktualnie wybranego ataku (zapamiętanego jako pole w klasie). Jakie są plusy a jakie minusy takiego podejścia? Co powinna posiadać klasa ataku (Attack), aby mogła wykonywać te same czynności co funkcje wbudowane w klasę "atakującą" (zastanów się skąd brać np. wartości atrybutów czy poziom bohatera? Czy może jakoś inaczej to realizować? Jaka jest najbardziej naturalna interpretacja (model świata) dla Ataku?)
Przykładowa gra po 2 zajęciach

czwartek, 17 marca 2011 oraz czwartek, 24 marca 2011

OOP (C++) - wirtualność cd., klasy abstrakcyjne, wyodrębnianie wielu poziomów abstrakcji

Zadania:

  1. Lekko zmodyfikowana gra, przed 3 zajęciami
  2. W klasie Monster (reprezentującej potwory) dodaj wirtualną metodę attack, która będzie służyła do atakowania graczy jednym z dostępnych dla potwora ataków. Funkcja ta powinna być czysto wirtualna - to sam potwór powinien zdecydować jaki jest jego ulubiony atak! Np. Goblin powinien atakować w zwarciu, Goblin łucznik z dystansu a mag swoimi czarami.

  3. Zmodyfikuj swój kod, tak aby możliwa była interakcja z graczem, np. przed każdym atakiem mógł wybrać jaki atak stosuje przeciwko potworowi (Hint: stwórz w klasie Adventurer funkcję attack, która za każdym razem na początku zapyta, który atak wybrać, po czym wywoła odpowiednią funkcję (magicAttack, normalAttack, rangedAttack)

  4. Zmodyfikuj kod przykładowej gry tak aby korzystał z nowych funkcji - gra zaczyna już być interaktywna!

  5. Gra robi się też coraz bardziej zagmatwana... Spróbuj dodać do możliwości gracza umiejętność leczenia. Dodaj umiejętność leczenia się niektórym potworom. Co się stanie jeśli będziesz musiał dodać wiele takich specjalnych umiejętności?

  6. Jak widać, za każdym razem musisz zmodyfikować kod w wielu miejscach, np. w klasie Bazowej i we wszystkich klasach pochodnych. Czy nie da się tego zrobić lepiej? Zastanów się czy wszystkie umiejętności specjalne nie mają pewnych wspólnych cech? Czy wszystkie ataki nie mają wspólnych cech? Czy nie da się tych wspólnych cech przekształcić we wspólną abstrakcyjną klasę bazową (albo interface)?

  7. Stwórz klasę Attack, która pozwala atakować potwory, niech posiada jedną funkcję wirtualną doAttack(Monster& m). Wywiedź z niej klasy MagicAttack, NormalAttack, RangedAttack. W konstruktorach, klasy te powinny w jakiś sposób mieć możliwość otrzymania parametrów, które zdecydują jak dużo obrażeń zadają. Możesz w tym celu w konstruktorze przekazać referencję do obiektu klasy Adventurer.

  8. Usuń z klasy Adventurer (i pochodnych) specjalistyczne funkcje [yyy]Attack(..), zostaw tylko funkcję attack(...). W klasie adveturer stwórz pole currentAttack. Niech każda z klas pochodnych: Thief, Mage i Warrior zainicjalizuje to pole odpowiednią wartością w swoim konstruktorze.

  9. W klasie Attack dodaj wirtualną metodę const char* getDescription(); która zwraca nazwę ataku. Rozszerz przykład z poprzedniego podpunktu: w klasie Adventurer dodaj tablicę (albo vector z stla - dla wygody), która będzie trzymać wszystkie dostępne dla postaci ataki. Pobaw się i stwórz kilka podklas MagicAttack, RangedAttack, NormalAttack (np. HolySmiteAttack, którzy zadaje dwa razy więcej obrażeń, ale zużywa manę :) ). Niech każda nowa klasa w metodzie getDescription() zwraca krótką nazwę np. poprzez return "Mój super wyjątkowy atak!";

  10. W klasie Attack znów dodaj opcję wyboru ataku, przy każdym wywołaniu niech funkcja wylistuje tablicę ataków wykorzystując funkcję getDescription(), po czym zapyta a numer ataku.

  11. Teraz nasuwa się pytanie: czy możesz hierarchię klasy ataków wykorzystać dla potworów? Póki co one dalej korzystają z gotowych funkcji, co nie jest elastycznym rozwiązaniem? Co zrobić, aby klasy ataków były równie dobre przeciwko poszukiwaczowi przygód? Hint: stwórz nową (lub wykorzystaj istniejącą) abstrakcję (klasę nadrzędną), która pozwoli traktować w pewnych przypadkach klasy Adventurer i Monster tak samo. Hint 2: uzyj klasy Creature.

  12. Spraw aby klasa Adventurer dziedziczyła po creature. Teraz możesz np. usunąć metody getHitPoints(), setHitPoints(), oraz odpowiadające im pola z klasy Adventurer! Dzięki abstrakcji, kod staje się krótszy i mniej podatny na błędy.

  13. Zmień metodę doAttack() tak aby przyjmowała referencję do Creature. W konstruktorach klas pochodnych zmień przyjmowane parametry tak, aby i potwory mogły korzystać ze stworzonych ataków (albo stwórz nowe konstruktory!).

  14. W klasie monster usuń metody [yyy]Attack, zmodyfikuj klasę Goblin tak aby wykonywała zawsze atak normalny. Klasę maga zmodyfikuj tak, aby mag pamiętał 3 rózne ataki i wykonywał je na zmianę (w kółko zapętlając modulo 3).

  15. Pobaw się teraz kodem, stwórz nowe rodzaje ataków, stwórz ataki dedykowane tylko dla konkretnego rodzaju potwora, dla konkretnego rodzaju postaci itp. Przy tworzeniu postaci dodaj możliwość wyboru posiadanych ataków. Podczas awansu dodaj możliwość wyboru kolejnych rodzajów ataków.

  16. Rozwiążemy teraz kolejny problem: umiejętności specjalne, inne niż ataki. Do tej pory były to funkcje w klasie adventurer. Czy nie dałoby się stworzyć podobnej struktury dla umiejętności, jak dla ataków? Co więcej, czy nie dałoby się ich jakoś podłączyć do hierchii ataków?

  17. Wykonaj eksperyment. Skoro teraz atak przyjmuje jako cel obiekt klasy Creature, z której wywodzi się również Adventurer, spróbuj atakiem zaatakować samego bohatera (player.attack(player)). Udało się? Co jeśli stworzyłbyś klasę HealingAttack : publick Attack, która zamiast zadawać rany leczyłaby? Co taka sytuacja znaczy z punktu widzenia abstrakcji? Czy klasy reprezentujące umiejętności specjalne i ataki mają jakieś wspólne cechy? Jak należny wyodrębnić z nich wspólną abstrakcję?

  18. Stwórz klasę Action reprezentującą dowolną akcję podejmowaną przez istoty w świecie gry. Dla ciekawostki stworzymy w niej funkcję-operator: virtual void operator(Creature& target) = 0;. Tak, takie rzeczy też są możliwe w C++! :)

  19. Stórz przykładową akcję: ExamineAction : public Action; której zadaniem będzie wypisanie na ekran liczby punktów życia celu. Jak wygląda wywołanie takiej akcji? ExamineAction a; a(player); Można by powiedzieć, że Action jest obiektem funkcyjnym - abstrakcją funkcji!

  20. Dodaj Action do hierarchii dziedziczenia attack. Jak to zrobić szybko i bezboleśnie, nie zmieniając wiele kodu? Rozszerz Attack : public Action; W klasie Attack zaimplementuj teraz operator (): void operator(Creature& t) { this->doAttack(t); }

  21. Plusami takiego rozwiązania są:

    1. Nie musisz zmieniać hierarchi dziedziczenia klasy Attack
    2. Możesz sprawić, aby wywołanie player.attack(player); nie czyniło krzywdy graczowi, a nawet wyświetlało stosowny komunikat, o bezsensowności takiej akcji.

  22. Teraz dodaj kilka akcji nie związanych z atakami, np. HealingAction, która zabiera manę a przywraca punkty życia. Możesz spróbować dodać odwrotną umiejętność, np. SacrificeAction, która zabiera życie a dodaje manę, itp

  23. Zmodyfikuj kod głównej pętli programu, tak aby od teraz działał na akcjach, a nie na atakach. Dodaj w konstruktorach klas postaci przydzielanie umiejętności, w awansie także. Od teraz gra powinna stać się dużo ciekawsza.

  24. Przykładowa gra po poprzednim podpunkcie
  25. Spraw aby potwory też korzystały z systemu akcji

  26. Zauważ, że gracze inaczej korzystają z akcji niż potwory - potwory algorytmicznie wybierają swoją następną akcję (są sterowane przez komputer), dla gracza musimy napisać jakiś mechanizm komunikacji z wejściem/wyjściem od gracza. Mimo to, obie te funkcje mają robić w zasadzie to samo, tylko w inny sposób. Mogą mieć wspólną nazwę itp. Wszystko to sprawia że można umieścić wspólny interface tej operacji (funkcję wirtualną) we wspólnej klasie bazowej Creature. Od teraz wszystkie istoty w świecie mogą działać!

Przykładowa gra po 4 zajęciach

czwartek, 31 marca 2011

OOP (C++) - dziedziczenie wielokrotne

Zadania:

Przed przystąpieniem do wykonywania ćwiczeń upewnij się że masz co najmniej stworzone klasy: Adventurer, Warrior : public Adventurer, Mage : public Adventurer

Wymagane minimum kodu do rozwiązania ćwiczeń
  1. Stwórz klasę dziedziczącą z klas Warrior i Mage jednocześnie (nazwijmy ją Witcher)
  2. W funkcje main na początku potestuj klasę: spróbuj wywołać operacje getHitPoints(), getManaPoints(), attack() itp. (ważne aby były to funkcje z klasy bazowej Adventurer). Co się dzieje?
  3. Zmodyfikuj kod, aby korzystał z wywołań funkcji za pomocą operacji zakresu :: - niech raz wywoła funkcję getHitPoints() poprzez obiekt klasy Witcher z klasy Mage a raz z klasy Warrior. Co się dzieje? Dlaczego tak się dzieje?
  4. Dodaj jakąś funkcję tylko w klasie Mage: niech będzie to funkcja identify(Item& item); która identyfikuje przedmiot. Niech klasa Item wygląda tak:
    class Item{
    	const char* getHiddenMeaning(){
    		return "this item can destroy the whole world!";
    	}
    public:
    	const char* getDescription(){
    		return "this is something...";
    	}
    friend class Mage;
    };
    
  5. Wypróbuj nową metodę, bez operatora zakresu. Działa? Co by się stało, gdyby funkcję o identycznej sygnaturze stworzyć w klasie Warrior?
  6. Napraw klasę Mage i Warrior, tak aby naprawić problemy z wywołaniem funkcji (wywołania funkcje z punktu 2.). Co należy zrobić?
  7. Dlaczego kompilator protestuje przy tworzeniu klasy Witcher? Jak temu zaradzić? Jakie są zasady wywoływania konstruktorów klas bazowych przy wielokrotnym dziedziczeniu?
  8. Kiedy już uporałeś się z poprzednim podpunktem, spróbuj przesłonić metodę getHitPoints() w klasie Warrior, niech zwraca zawsze 10000. Czy wywołanie witcher.getHitPoints(); nadal działa? Co zwraca?
  9. Dodaj getHitPoints() także w klasie Mage. Co się dzieje?
  10. Na zakończenie usuń kod testowy ze swojego projektu
  11. Korzystanie z wielokrotnego dziedziczenia wymaga sporo uwagi. Podaj kiedy jego użycie może być proste i na pewno nie stworzy problemów w przyszłości? (Hint: jeśli znasz język Java, jak tam jest zrealizowane dziedziczenie?)
Przykładowa gra po 5 zajęciach (może wymagać małych zmian do poprawnej kompilacji).

czwartek, 07 kwietnia 2011

OOP (C++) - polimorfizm statyczny (szablony)

Zadania:

Ponieważ szablony są dość skomplikowanym pojęciem, szczególnie, jeśli używa się ich w dużych projektach, przykłady które zrealizujemy będą raczej klasyczne. Zadaniem jest utworzenie klasy Pojemnika (Container). Klasy Container będziemy mogli potem użyć w naszym projekcie do przechowywania kolekcji rozmaitych obiektów.
  1. Stwórz przykładowy kontener. Załóżmy na początek, że Container ma przechowywać liczby całkowite (int). Ma mieć funkcje stosu (push(int), int pop()), operator[](int) (do pobrania elementów), oraz funkcję int getElementsCount() zwracającą ilość elementów w pojemniku. Wszelkie operacje niech wykonuje na tablicy o z góry zadanym rozmiarze, powiedzmy SIZE, zadeklarowaną jako static const. Stwórz także zaprzyjaźniony operator wypisania na ostream. Umieść deklarację klasy w pliku container.h a definicje funkcji w container.cpp. Stwórz plik main.cpp, dołącz container.h i wypróbuj stworzoną klasę.

  2. Gotowa klasa Container
  3. Przypomnij sobie z wykładu sposób tworzenia szablonu. Spraw aby Container stał się szablonem kontenerów, który umożliwią przechowywanie dowolnego typu danych. Stwórz w mian.cpp obiekty kontenera dla typów int, double, itp. Skompiluj program...
  4. Upsssss... Coś jest nie tak. Czy potrafisz wyjaśnić dlaczego tak jest (hint: jak działają template'y)? Co należy zrobić? Popraw swój kontener tak aby zaczął działać.
  5. Funkcje template'owe: na rozgrzewkę stwórz funkcje my_min, my_max, my_avg, dwuargumentowe, które dla dowolnych typów liczą minimum, maximum, średnią arytmetyczną. Gotowe? Wywołaj je kilka razy dla testów w programie main. Jeśli jeszcze tego nie zrobiłeś możesz stworzyć funkcję template'ową do wypisywania zawartości kontenera na ostream. Jak zaprzyjaźnić funkcję template'ową z klasą?
  6. Stwórz teraz metodę template'ową, która pobiera parametry template'owe, a która służy do przekopiowania zawartości tablicy statycznej do naszej tablicy (hint: użyj parametru template'a, który jest liczbą). Aby zabezpieczyć się przed przekroczeniem rozmiaru tablicy możesz skorzystać z funkcji my_min.
  7. Stwórz template konstruktora analogiczny do funkcji kopiującej tablicę statyczną
  8. Stwórz metodę, która przyjmuje dwa wskaźniki do typu T (powiedzmy begin i end) i kopiuje do tablicy wszystkie elementy pomiędzy begin a end, chyba że się nie zmieszczą. Wtedy kopiuje tyle ile się da.
  9. Przeładuj konstruktor który przyjmuje parametr rozmiaru taki sam jak rozmiar kontenera, tak aby wpisywał elementy od tyłu i aby był bardziej wydajny (hint: np. bez niepotrzebnych wywołań funkcji my_min)
  10. Odziedzicz template kontenera jako nową klasę SortContainer, który po każdej operacji push zawsze posiada tablice posortowaną.
  11. spróbuj stworzyć SortContainer dla jakiegoś typu będącego klasą (np. klasa Item, przedmiotów w grze). Co się dzieje na etapie kompilacji? Jak temu zaradzić?
  12. Zmodyfikuj klasę Item - dodaj pole double value (wartość przedmiotu) i spraw aby SortContainer potrafił sobie radzić z sortowaniem przedmiotów według wartości.
Przykładowa gra po 6 zajęciach - z wykorzystaniem szablonu klasy Container.

czwartek, 14 kwietnia 2011

OOP (C++) - wyjątki

Na początek weźmy naszą klasę Container z poprzednich ćwiczeń. Może być wersja bez template'ów - dla uproszczenia. Potem i tak proszę zrobić wersję z szablonem.

  1. Kilka funkcji w Container aż prosi się o to aby dodać obsługę sytuacji wyjątkowych - zastanów się które i jakie sytuacje wyjątkowe mogą nastąpić.
  2. Dodaj obsługę sytuacji wyjątkowych w funkcjach: push() i pop().
  3. Dodaj obsługę sytuacji wyjątkowych w operatorze pobrania elementu.
  4. (dla template'a) Dodaj obsługę sytuacji wyjątkowych w konstruktorze - jeśli rozmiar tablicy SIZE zostanie podany jako 0 niech konstruktor rzuca wyjątek.
  5. Do tej pory używaliśmy (i tak się zazwyczaj robi) wyjątków do obsługi sytuacji błędnych - odniesienie poza obszar tablicy, dodanie elementu ponad stan, itp. Wyjątki jednak, jak sama nazwa wskazuje nie służą tylko do obsługi błędów! (inaczej mielibyśmy mechanizm obsługi błędów). Do czego więc można jeszcze uzyć wyjątków?

  6. W klasie (template'owej) Container dodaj w konstruktorze z tablicą statyczną obsługę sytuacji, w której tablica jest krótsza lub dłuższa niż SIZE, niech wyjątek informuje o tym ile elementów rzeczywiście dodano, lub ile elementów pominięto ze względu na brak miejsca.
  7. Wróćmy teraz do samej gry RPG. Napiszmy mechanizm umozliwiający awans postaci. Gdzie umieścilibyśmy kod takiego awansu? Do tej pory awans polegał tylko na zwiększeniu poziomu (zmienna level). Jak dodać mozliwość wyboru nowych umiejętności, jak zmieniać maksymalną liczbę punktów wytrzymałości, itp? Jak zrobić, aby każda z klas miała własną obsługę awansu (różne listy umiejętności, inne atrybuty się zwiększają, itp.)?

    Teoretycznie możemy to dodać właśnie w funkcji nextLevel(), w niej pytać uzytkownika o wybór umiejętności, itp. Funkcja mogłaby być wirtualna i moglibyśmy ją przeładowywać w podklasach...

    ... jednak jest pewien problem: zamykamy na przyszłość drogę do zmiany zasad w sposobie awansu, oraz zamykamy duża funkcjonalność związaną z interakcją z graczem w klasie, która powinna mieć z tym jak najmniej do czynienia (dlaczego?).

    W tym miejscu możemy się zastanowić nad wyborem wyjątków do obsługi tego mechanizmu - w końcu awans postaci to bardzo wyjątkowa sytuacja. Możemy z klasy gracza wyrzucić wyjątek informujący o tym, że nastąpił awans. W ten sposób przekażemy sterowanie programu do nadrzędnego mechanizmu - w naszym przypadku będzie to RPGEngine. I właśnie on powinien się zając awansem (jesli ktoś grał kiedyś w papierową wersję RPG to wie, że wszystkie zasady związane z awansem są opisane właśnie w silniku gry - Zasadach:) )!

    W ten sposób awansem postaci zajmie się podsystem, który jest do tego najlepiej przygotowany. W przyszłości, możemy wydać naszą grę RPG (pewne jej komponenty) jako bibliotekę, a innym programistom zostawić mozliwość zaimplementowania mechaniki awansu, itp. lub sami będziemy mogli tworzyć wiele różnych gier RPG, nie pisząc wiele kodu (złota zasada DRY - don't repeat yourself).

  8. Dodaj wyjątek NextLevelException do projektu gry, niech będzie rzucany, wtedy kiedy nastąpi awans postaci. Niech zawiera podstawowe informacje -kto awansował, na jaki poziom, itp.
  9. Przechwyć wyjątek w głównej pętli gry i obsłuż awans w tym miejscu (możesz napisać funkcję która to obsługuje, albo klasę np. class Rulebook).
  10. Jakie inne sytuacje mogą wymagać specjalnego traktowania? Zastanów się i pomyśl jakie problemy mogą one rozwiązać (hint: zastanów się jak wygląda kod w którym wiele potworów atakuje gracza, co dzieje się kiedy gracz zginie, a kolejne potwory próbują go atakować).
Przykładowa gra po 7 zajęciach - z wykorzystaniem wyjątków do obsługi róznych sytuacji.

czwartek, 28 kwietnia 2011

OOP (C++) - STL i Pojemniki (Containers)

  1. Zapoznaj się pobieżnie z dokumentacją STL - zwróć uwagę szczególnie na opis poszczególnych kontenerów, złożonośc operacji, kolejność przeglądania.
  2. Ściągnij aktualny projekt gry RPG. W stosunku do wersji z końca ćwiczeń 7 w projekcie dodano w klasie creature właściwość Speed opisującą prędkość poruszania się istot. Będzie to porzebne do wykonania ćwiczeń.
  3. Przerób funkcję RPGEngine::nextTurn, tak aby korzystała z stl::vector zamiast z klasy container. W trakcie wykonywania tego zadania zauważysz, że musisz zmodyfikować funkcję action(Container<Creature*, SIZE> targets) w klasie creature i pochodnych. Zamiast zmieniać sygnaturę funkcji spróbuj stworzyć nową funkcję templateową, której parametrem templatu będzie iterator do początku i do końca kolekcji (hint: zobacz jak to jest zrealizowane w funkcji std::sort). Dzięki temu wyonywanie kolejnych zadań będzie łatwiejsze.
  4. W sytuacji wyjątkowej śmierci obiektu klasy Creature (CreatureIsDeadException) spraw aby obiekt został usuniety z kolekcji. Dzięki temu będziesz mógł się pozbyć liczenia dodanego doświadczenia na koniec walki - przenieś po prostu zwiększanie zmiennej experienceGained do obsługi sytuacji wyjątkowej. Usuwasz w ten sposób dodatkową iterację i zmniejszasz złożoność kodu. Dodatkowo uzytkownik będzie zabezpieczony przed możliwością ataku martwych przeciwników - możesz usunąc sprawdzenie funkcji isDead() przed wykonaniem akcji.
  5. Wykorzystując funkcję std::sort sortuj po wstawieniu wszystkich istot do kolekcji, kolekcję actors. Niech sortowanie odbywa się według malejącej wartosi Speed. W jaki sposób możesz sprawić aby obiekty klasy były ze sobą porównywalne na podstawie jeakiegoś pola?
  6. Teraz do automatycznego sortowania wykorzystaj klasę Map lub MultiMap, łącząc obiekty z kluczami.
  7. Teraz do automatycznego sortowania wykorzystaj klasę Set lub MultiSet, abyś nie musiał pamiętać po czym sortujesz i explicite wstawiać klucza do kolekcji. Czego potrzebuje klasa Creature, aby mozna było ją wkładać do kontenera Set lub MultiSet?

Czwartek 12 maja 2011

Programowanie orientowane zdarzeniami na przykładzie Listener'ów Javy

  1. Ściągnij przykłdowy program w Javie: Okno.java, skompiluj (w linii komend wpisz: javac Okno.java), uruchom (w linii komend: java Okno). Jak widać aplikacja nie robi nic wielkiego. Obejrzyj kod. Zauwazysz, że klasa Okno ma tylko funkcję main i funkcję rysującą liniję (drawLine).
  2. Wykorzystując interface MouseMotionListener spraw aby przy każdym ruchu myszką rysowała się linia od poprzedniego do obecnego punktu. Dokumentację i tutorial (MouseMotionListener) pomocne przy tym zadaniu znajdziesz tu i tu.
  3. Wykorzystując interface MouseListener zmodyfikuj aplikację, tak aby rysowanie następowało tylko przy wciśniętym klawiszu myszki. Dokumentację i tutorial (MouseListener) pomocne przy tym zadaniu znajdziesz tu i tu.
  4. Pamiętaj, że musisz dodać każdy Listener do konkretnego obiektu interface'u - w naszym przypadku do okienka (można przez this.addYYYListener(listener)).
  5. W ramach ćwiczeń możesz napisać aplikację SimplePaint. Aplikacja powinna umożliwiać wybór koloru rysowania linii i mieć kilka narzędzi do wyboru np. elipsy, prostokąty, linie, aerograf (losowe "rozpylanie" punktów). Wybór kolorów i narzedzi powinen się odbywać za pomocą przycisków (np. JButton) umieszczonych gdzieś w oknie (np. na komponencie JPanel). Za wykonanie tego zadania możesz uzyskać 5 pkt z aktywności wysyłając do mnie gotową klasę. (Hint: zadanie będzie łatwiejsze, jesli podzielisz sobie okno na podkomponenty za pomocą JPanel i managera layoutu np. BorderLayout, pamietaj że Listenery możesz dodawać w dowolnym komponencie okienkowym Javy!)

Czwartek 19 maja 2011

PERL Składnia

  1. Super program w PERLu z przykładami zastosowania większości przydatnych poleceń.
  2. Napisz Hello World w PERLu
  3. Dodaj do programu pytanie o imię użytkownika, niech wyświetla powitanie dopasowane do tego imienia.
  4. Niech teraz program w kółko pyta o imię użytkownika, za każdym razem wyświetlając powitanie. Program zakończy działanie, gdy użytkownik wpisze tekst "Trąbka", przed zamknięciem program wypisze na ekran tekst: "Jaka trąbka?" (może bć bez polskich znaków).
  5. Niech teraz program zapamiętuje wszystkie imiona które poda użytkownik. Przed zakończeniem programu niech wypisze wszystkie imiona.
  6. Teraz dodaj następującą funkcjonalność? Niech po każdym nowym imieniu (poza pierwszym) program zapyta użytkownika, czy zna użytkownika, którego imie było podane wcześniej, wypisując: "Czy znasz [[tu imię osoby podanej wcześniej]]?", niech zapamięta informację użytkownika (T/N). Na koniec programu powinien wypisać wszystkie osoby, które powiedziały że znają osobę wcześniejszą, wraz z imieniem tej osoby w formacie: [[osoba i]] zna [[osoba i-1]].
  7. Stwórz teraz tablicę hash'ującą która dla każdego imienia pamiętać będzie listę określeń tej osoby. Niech teraz przy każdym nowym użytkowniku system pyta o określenie, którym chciałby określić osoby wcześniej podane. Niech zapamięta te określenia i wypisze je pod koniec programu w formacie [[osoba i]] jest określana jako: [[określenie 1]], [[określenie 2]], [[itd...]].
  8. Zamiast wypisywać wszystkie określenia, to po podaniu ostatniego imienia w sekcji powitań (po wpisaniu przez użytkownika słowa "Trąbka") niech system znów pyta o imiona, ale tym razem po wczytaniu imienia, niech wypisze cechy osoby, której imię podano.

Czwartek 26 maja 2011

PERL - wyrażenia regularne

Poniżej znajdują się zadania na wyrażenia regularne. Rozwiązanie każdego z nich można wysłać do mnie na mail (najlepiej na robsontpm[wiadomo]gmail[co]com), temat powinien być następujący: [dwucyfrowy numer zadania]. [Imię i nazwisko], np. 01. Robert Szczelina.
Spośród osób, które wyślą rozwiązania danego zadania jeden punkt z aktywności otrzyma osoba, która poda najkrótsze poprawne rozwiązanie. Jeśli będzie kilka najkrótszych rozwiązań, punkt otrzyma nadesłane najwcześniej.
Liczą się tylko rozwiązania wysłane w trakcie ćwiczeń (14:00 - 16:00).
Osoby, które wyślą co najmniej 5 poprawnych rozwiązań, bez względu na długość otrzymają dodatkowe 3 pkt. z aktywności.
Uwaga 1: w rozwiązaniach należy podać cały regex w notacji perlowej, tzn z / lub |, np. m/^[A-Z][a-z]*$/, czy s/^[A-Z][a-z]*$/to jest imie/g. Proszę pamiętać, że regexy mają przełączniki (np. /g) które zmieniają zachowanie i wolno, a nawet czasem trzeba je stosować.
Uwaga 2: Jeśli w którymś zadaniu jest napisane, że należy podać program w perlu, to do liczenia najkrótszego kodu liczy się suma długości użytych regexów.
Uwaga 3: Długością regexu "s/^[A-Z][a-z]*$/to jest imie/g" jest długość napisu pomiędzy " i ", w tym przypadku 30. W szczególności wliczają się parametry typu /g /i itp.

  1. Napisz regex, który dopasowuje adresy IP, wraz z numerem portu postaci 192.168.1.1:8080, proszę pamiętać, że wielkości między kropkami mogą przyjmować wartości od 0 do 255 oraz że numer portu jest liczbą od 0 do 65535, bez zer wiodących
  2. Napisz regex, który dopasowuje adresy e-mail, dodatkowo powinien posiadać prosty mechanizm obchodzenia zabezpieczeń użytkownika, a mianowicie powinien wykrywać próby zmiany @ na [AT] lub (AT), a kropek na [DOT] lub (DOT) z wariacjami na temat dużych i małych liter w napisach AT i DOT. Adres e-mail może zawierać jako nazwę konta litery, cyfry, podkreślenie, kropkę, pauzę, jako domenę to na co pozwala standard URL, musi zawierać dokładnie jeden znak @, domena musi być co najmniej dwuczłonowa (z co najmniej jedną kropką), człony domeny nie mogą być napisami pustymi.
  3. Napisz regex, który rozpoznaje GUID tj. napis złożony z kolejnych grup po 8, 4, 4, 4, 12 znaków, które tworzą w ramach każdej z grup liczbę zapisaną w systemie szesnastkowym, grupy oddzielone są znakiem -. Dodatkowo GUID może, ale nie musi być objęty znakami { i }. Np. {e02fa0e4-01ad-090A-c130-0d05a0008ba0}
  4. Napisz regex, który zamienia w tekście będącym stroną html wszystkie znaczniki w ten sposób, że usuwa z ich wnętrza atrybut style i class, wraz z całą ich zawartością. Przykład: dla znacznika <table border = "0" style = "text-decoration: none;" class = "Klasa_1" cellspacing = "0" > wykonanie regexu powinno zwrócić <table border = "0" cellspacing = "0" >. Proszę zwrócić uwagę na to, że standard html nie określa ile białych znaków może być naokoło znaku =. Dodatkowo proszę założyć że znaczniki nie rozciągają się na wiele linii, oraz że wartości atrybutów zawsze są objęte " i "
  5. Napisz regex, który zamienia wystąpienia daty w formacie dd/mm/rrRR na format RR-mm-dd. Uwaga: użyłem RR aby podkreślić która część ma się gdzie znaleźć.
  6. Napisz regex, który w tekście strony html przed każdym wystąpieniem odnośnika do dokumentu z podanego zbioru {.doc, .pdf, .odt, .ps} wstawi obrazek tj poprawny znacznik IMG, który będzie wskazywał na plik o nazwie [rozszerzenie bez kropki].png gdzie [rozszerzenie bez kropki] to odpowiednie słowo ze zbioru {doc, pdf, odt, ps}.
  7. Napisz regex, który w tekście zawierającym posortowane liczby oddzielone spacjami usunie powtarzające się liczby. Np.: 1 1 2 3 3 3 4 wynikiem jest 1 2 3 4.
  8. Napisz regex, który usunie wszystkie komentarze w stylu C++ (// i /* */) z pliku.
  9. Napisz regex, który w pliku logu systemowego znajdzie zapisane wielolinijkowe zapytania SQL (tak, aby program napisany w perlu mógł je odczytać!). Każdy wpis w logu systemowym zaczyna się od daty i godziny w formacie [dd/mm/rrrr:gg.mm] (razem z nawiasami [ i ]!), po czym następuje treść logu. W przypadku zapytania sql może to być kilka linii. Zapytania SQL zaczynają się od jednego ze słów: select, update, insert. Napisz program który wypisuje wszystkie znalezione zapytania SQL.
  10. Korzystając tylko z regexów napisz program w perlu, który sortuje listę liczb maksymalnie dwucyfrowych oddzielonych pojedynczą spacją. Przykład: wejście = 12 4 8 10, wyjście = 4 8 10 12.

Tutaj można poczytać co nieco o regex'ach. Jako pomoc polecam także google.

Czwartek 2 czerwca 2011

Perl - programowanie obiektowe

Jeśli nie wykonałeś zadań z poprzedniego tygodnia, możesz to zrobić, jeśli skończysz pracować nad poniższymi zadaniami.

  1. Przydatna strona, choć czasem definicje mogą wydawać się lekko "dziwne"... Ale to przez "urok" języka.
  2. Stwórz moduł w Perlu zawierający dwie funkcje min, max, avg, nazwij go Extra. Eksportuj funkcje min i max jako publiczne API. Zaimportuj moduł w nowym programie w perlu (require)i spróbuj uzyc funkcji max i min. Jak użyć funkcji avg?
  3. Stwórz pakiet, który będzie klasą w Perlu o nazwie Worker. Niech posiada metody doWork() i isTired() i rest(). Metoda do work niech zakłada, że dostaje tablicę i niech ją przegląda (np. wypisując na ekran), za kazdym razem zmieniając licznik (pole w klasie) o 1. Kiedy licznik przekroczy przekroczy wartośc progrową (ustalone), niech funkcja isTired zwraca true. Funkcja rest przyjmuje jedną liczbę i zmiejsza o jej wartość licznik (może spaść poniżej zera).
  4. Stwórz obiek klasy Worker (bless) w jakimś innym pliku (np. main.pl). Wywołaj po olei wszystkie funkcje, aby sprawdzić działanie.
  5. Stwórz konstruktor w klasie Worker (new) który jako argument przyjmuje górny limit pracy, taki jak w jednym z poprzednich punktów. Zmień tworzenie workera z poprzedniego pliku, tak aby wykorzystywało new.
  6. (Dziedziczenie) odziedzicz (@ISA) klasę Worker w klasie OptimalWorker. Niech OptimalWorker przeładowuje doWork, aby jedna praca kosztowała a jednostek, gdzie a jest liczbą rzeczywistą z przedziału 0..1. Liczba a powinna być ustalana w konstruktorze klasy OptimalWorker (razem z limitem!).
  7. ("Oszukiwany" konstruktor) Wykorzystaj mozliwości języków dynamicznych. Stwórz klasę Idler, która ma takie same metody jak Worker ale nie dziedziczy z Workera. Niech doWork nie robi nic, isTired zawsze zwraca true, a rest niech robi cokolwiek. Podmień kod operatora new w klasie Worker, aby zwracał obiekt typu Idler. Czy program główny nadal działa?

Czwartek 9 czerwca 2011

Podsumowanie - przykładowy test

  1. Test
  2. Cipher.cpp
  3. Newton.cpp
  4. Scheme.scm