Skip to content
Marcin Kostrzewski edited this page May 28, 2021 · 2 revisions

Przygotowanie pojedynku

W konstruktorze klasy Battle tworzymy wszystkie obiekty potrzebne do walki

  • obiekty graczy Player - korzystamy z PlayerFactory
  • koordynator walki - Coordinator
  • obiekt odpowiedzialny za wyliczanie wyniku walki - Outcome
  • recorder - ProcessRecorder przechowujący dane o każdej turze

Konstruowanie Playera - PlayerFactory

Przebieg

Kto wykonuje ruch?

Na początku każdej iteracji głównej pętli wykonujemy metodę next_turn w Coordinatorze. Coordinator ma pełną wiedzę o kolejności wykonywania ruchów przez graczy poprzez TurnsQueue. Pozyskujemy w tej kolejki gracza za pomocą metody turn(). TurnsQueue zaprojektowany jest w taki sposób, że pobrany gracz automatycznie trafia na koniec kolejki.

Ruch gracza

Od gracza potrzebujemy karty, która ma się wykonać. Nie potrzbujemy całego obiektu karty, wystarczą nam same jej efekty, więc pytając gracza o kartę metodą use_card chcemy dostać odpowiedź w postaci listy efektów. Nazwa tej metody może wydawać się dziwna, ale kryje się za nią większa logika która zasługuje na miano użycia karty. To co zwraca jest trochę nieintuicyjne, dlatego zadbaliśmy o zapisania typu returnu.

Użycie karty

Pobieranie karty z Decku odbywa się w dokładnie ten sam sposób co pobieranie gracza z kolejki tur - pobrana karta z tej kolejki automatycznie trafia na koniec. Na zwróconej karcie wykonujemy use, która realizuje zadania:

  • Aktualizacja buffów - Efekty posiadają swoje buffy; trwają one okreśioną liczbę tur i muszą być aktualizowane przed każdym użyciem kart, sprawdzamy czy buff nie jest już nieaktualny. Aktywne buffy aktualizują się metodą tick - 'tyka' wewnętrznymi timerami, które mówią np o czasie aktywności buffa. Buffy same w sobie nic nie robią, są jedynie później wykorzystywane przy obliczaniu wartości wykonanych efektów.
  • Zwrot listy efektów tej karty.

Aktywacja efektów

Dla każdego efektu wykonujemy metodę activate(current_player, opponent, turnsQueue) W środku metody wybierany jest cel - efekty mogą działać zarówno na przeciwnika, jak i na gracza, a następnie metoda abstrakcyjna on_activation(target, turns_queue) (Więcej w sekcji Podmoduł Efektów )

Zapis ruchu w ProcessRecorder

Po wykonaniu efektów, stany obu graczy mogły ulec zmianie. Przekazujemy te dwa obiekty w ProcessRecordera w metodzie record_turn(attacker, defender).

Tworzenie stanu

Dla każdego wykonanego ruchu tworzony jest obiekt State, który tłumaczy obiekty obu graczy na dwa uproszczone modele - klasa SimplifiedPlayer. Metoda ta zna jedynie ID gracza, ID jego kart w kolejności w jakiej są w Decku i jego statystyki. Statystyki są kopiowane żeby zmiana statystyk w jednym obiekcie nie skuktowała zmianą wszystkich stanów.

Podmoduł Efektów

Efekty kart zaprojektowane są z myślą o łatwym rozwoju modularnym aplikacji - programista może łatwo stworzyć nowe efekty korzystając z zastosowanego wzorca Strategy tworząc klasę dziedziczącą z Effect. Podmoduł znajduje się w folderze effects.

Klasa Abstrakcyjna Effect

Klasa ta realizuje metody odpowiedzialne za aktualizacje efektów, wybieranie celu, zarządzanie buffami tak, abyśmy przy tworzeniu klas dziedziczących nie musieli się przejmować tą logiką. Punktem wejścia jest metoda activate, którą wykonuje Battle przy aktywowaniu efektów. Activate wybiera cel i wykonuje abstrakcyjną metodę on_activation.

on_activation

Serce każdego efektu. To jest jedyna metoda abstrakcyjna jaką musimy zaimplementować tworząc własny efekt. Jako parametry ta metoda udostępnia nam widok na cel efektu, czyli instancję BattlePlayer i dostęp do kolejki ruchów w Coordinator. Widoczność na playera umożliwia nam działanie na graczu (modyfikacja statystyk, talii, buffy, itp). Dodatkowo mamy pełen dostęp do kolejki ruchów. Nie jest to najlepsze rozwiązanie patrząc na low coupling, ale dzięki niemu uzyskaliśmy lepszą spójność.

Wyliczanie mocy efektu

W klasie Effect mamy dostęp do atrybutów power, range i buffs. Służą one efektom, które korzystają z jakiś stałych wartości do wykonywania swoich akcji (leczenie, zadawanie obrażeń, tarcza). Inne efekty mogą w ogóle nie potrzebować tych wartości (np. zmiana kolejności talii). Atrybut power oznacza moc efektu, a range oznacza losowość efektu. buffs dodatkowo mogą aplikować mnożnik i stałą wartość do ostatecznej wartości efektu. Tą wartość możemy obliczać w dowolny sposób, ale przygotowaliśmy pewną klasę pomocniczą, która zrobi to za nas - Calculator - zrealizowana jako singleton. Jeżeli chcemy obliczyć faktyczną moc efektu po uwzględnieniu power, range i buffs, możemy pobrać instancje kalkulatora Calculator.get_instance() i wykonać metodę calculate_effect_power(power, range, buffs) aby otrzymać ostateczną wartość efektu.