Difference between revisions of "Opis zadania — bitewne AI dla VCMI"

From VCMI Project Wiki
Jump to: navigation, search
(Pierwotna wersja)
 
(Szarża na strzelców ("Kill bloody elves!"))
 
(103 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
Przebieg bitwy - jak to wygląda od strony AI
 
Przebieg bitwy - jak to wygląda od strony AI
  
== komunikacja AI <-> silnik ==
+
<blockquote><font size="tiny" color="black">Lasciate ogni speranza, voi ch'entrate.</font></blockquote>
 +
 
 +
= Informacje ogólne =
 +
== Zadanie ==
 +
Przedmiotem zadania jest napisanie programu, który będzie sterował graczem w czasie bitwy w otwartej reimplementacji Heroesa 3 — [[VCMI]]. Sprowadza się to do napisania w języku C++ dynamicznie ładowanej biblioteki (zależnie od platformy .dll bądź .so), zawierającej klasę implementującą interfejs dla AI.
 +
 
 +
== Skrótowy opis bitwy ==
 +
Uwaga: opis w tej sekcji jest mocno uproszczony. Ma za zadanie wprowadzić do zadania i wyrobić podstawowe intuicje, uszczegółowiony jest przez reguły zawarte dalej. W razie rozbieżności, to one są decydujące.
 +
 
 +
=== Podstawy ===
 +
Bitwa toczona jest pomiędzy dwiema armiami, które zaczynają rozstawione po przeciwległych krańcach planszy. Armia może (choć nie musi) być dowodzona przez bohatera. Gracza „lewego” nazywamy „atakującym”, „prawego” zaś „broniącym się”. Armia składa się z oddziałów (jednostek) — każdy oddział jest charakteryzowany przez typ (np. pikinier albo czerwony smok) oraz liczebność. Dodatkowo każda jednostka posiada szereg zmiennych współczynników opisujących jej parametry bojowe, do najważniejszych zaliczają się:
 +
* Atak
 +
* Obrona
 +
* Zakres zadawanych obrażeń
 +
* Wytrzymałość (punkty życia — HP)
 +
* Szybkość
 +
 
 +
Oddział ginie, gdy jego liczebność spadnie do zera. Gracz, który straci wszystkie oddziały, przegrywa bitwę.
 +
 
 +
=== Ruch ===
 +
Bitwa podzielona jest na tury. Każda jednostka rusza się raz na turę. Oddziały wykonują ruchy po kolei, w porządku malejącej szybkości. Za każdym razem, gdy wypada kolej ruchu jednostki sterowanej przez AI, następuje wywołanie metody activeStack. Zadaniem AI jest zwrócenie struktury opisującej, co dana jednostka ma uczynić. Podstawowe akcje to:
 +
* Atak — jednostka może zaatakować sąsiadującą jednostkę w zwarciu, bądź — jeśli umie strzelać — dowolną jednostkę na mapie.
 +
* Ruch — jednostka może przesunąć się o tyle pól na mapie, ile wynosi jej szybkość. Ruch może zostać zakończony atakiem na osiągnięty oddział wroga.
 +
* Czekanie — jednostka spróbuje się ruszyć później w tej turze (najwyżej raz na turę).
 +
* Obrona — jednostka rezygnuje z akcji, aby czasowo poprawić swój współczynnik obrony.
 +
 
 +
=== Pole bitwy ===
 +
Pole bitwy składa się z heksagonalnych pól ułożonych w 11 linii po 17 pól, ponumerowanych jak pokazano na rysunku.
 +
 
 +
[[File:Bfield_grid.gif]]
 +
 
 +
Pola w dwóch skrajnych kolumnach nie są dostępne dla zwykłych jednostek. Ponadto niektóre z heksów (tj. pól) mogą być zablokowane ze względu na umieszczone na nich przeszkody. Heksy na których stoi już inne jednostka także traktowane są jak zablokowane. Na takim polu żadna z jednostek nie może zakończyć ruchu, przekraczać zaś to pole mogą wyłącznie jednostki latające.
 +
 
 +
Pozycją jednostki jest numer heksa, na którym stoi. Każda jednostka zajmuje jednego lub dwa sąsiadujące w poziomie heksy.  W przypadku jednostki dwuheksowej jej pozycją jest pozycja PRZODU jednostki (wojska atakującego są zawsze zwrócone w prawo, broniącego się zaś w lewo).
 +
 
 +
=== Bohater ===
 +
Jak wskazano wcześniej armia może być dowodzona przez bohatera. Wiąże się to paroma korzyściami:
 +
* Bohater może posługiwać się magią (p. niżej)
 +
* Bohater może posiadać specjalne machiny wojenne
 +
* Jednostki otrzymują premie do atrybutów, zależne od parametrów bohatera
 +
 
 +
=== Czary ===
 +
Bohater, jeżeli jest obecny na polu bitwy, może raz na turę, przed przesunięciem oddziału (w czasie, gdy ten jest aktywny) rzucić zaklęcie. Każdy bohater może posiadać księgę zaklęć, określającą, jakie czary są dostępne (bohater nie posiadający księgi nie może czarować). Rzucenie czaru wymaga poświęcenia pewnej liczby punktów many. Bohater, który wyczerpie swoją manę, traci możliwość rzucania czarów.
 +
 
 +
== Jak AI komunikuje się z grą ==
 +
Do komunikacji służą dwa interfejsy:
 +
* CBattleGameInterface — główna klasa AI musi dziedziczyć po tej klasie. Silnik gry wywołuje jej metody, by informować AI o wydarzeniach w grze bądź by zapytać, jaką akcję chce podjąć.
 +
** Poprzez interfejs IBattleEventsReceiver odziedziczono szereg metod pozwalających otrzymywać informacje o wydarzeniach w bitwie [http://vcmi.eu/pc/class_i_battle_events_receiver.html].
 +
** Zaimplementowanie wirtualnej i abstrakcyjnej metody activeStack (wołanej, ilekroć otrzymuje się kolejkę) jest kluczowym aspektem implementacji AI. P. [[#Co_AI_.2Amusi.2A_robić|dalej]].
 +
 
 +
* CBattleCallback — interfejs zaimplementowany w silniku, udostępniany AI. AI może wywoływac jego metody, by pobierać informacje o stanie bitwy oraz by podejmować niektóre specjalne akcje. Dziedziczy on po dwu interfejsach:
 +
** CBattleInfoCallback — zbiór metod pozwalających odpytywać silnik o stan bitwy. P. [http://vcmi.eu/pc/class_c_battle_info_callback.html]
 +
** IBattleCallback — zbiór (a właściwie) para metod. Pierwsza, battleMakeAction jest stosowana do rzucania czarów przez bohatera. Druga, battleMakeTacticAction, w czasie [[#Faza_taktyczna fazy|fazy taktycznej]]. P. [http://vcmi.eu/pc/class_i_battle_callback.html]
  
 
Każde AI jest kompilowane do dynamicznie ładowanej biblioteki, która musi eksportować następujące funkcje:
 
Każde AI jest kompilowane do dynamicznie ładowanej biblioteki, która musi eksportować następujące funkcje:
void GetAiName(char* name); //powinno wpisać nazwę AI
+
* <code class="inline">void GetAiName(char* name);</code> — powinno wypełnić pamięć pod zadanym wskaźnikiem ciągiem znaków z nazwą AI.
CGlobalAI* GetNewAI(); //zwrca obiekt klasy wydziedziczonej z abstrakcyjnej klasy CGlobalAI. Wywoływanie metod na zwróconym obiekcie stanowi podstawę komunikacji silnik -> AI
+
* <code class="inline">CBattleGameInterface* GetNewBattleAI();</code> — powinno stworzyć nowy obiekt głównej klasy AI (dziedziczącej po CBattleGameInterface), które pokieruje nadchodzącą bitwą. Obiekt powinien być stworzony operatorem new, tak aby po zakończeniu bitwy poprawna na nim była operacja delete. Silnik na otrzymanym obiekcie będzie wywoływał stosowne metody. Stanowić one będą podstawę komunikacji silnik -> AI. Pierwszym wywołaniem będzie metoda init, poprzez którą AI otrzyma wskaźnik na implementację interfejsu CBattleCallback, poprzez który AI może „odpytywać silnik”.
 +
 
 +
== Co AI *musi* robić? ==
 +
Choć interfejs dla AI jest bogaty i zawiera wiele metod, tak naprawdę koniecznie wymagane jest zaimplementowanie tylko jednej. Jest to:
 +
virtual BattleAction activeStack(const CStack * stack)=0;
 +
Metoda ta jest wołana, ilekroć AI musi podjąć akcję dla jakiejś jednostki. Należy zwrócić poprawnie wypełnioną strukturę BattleAction, opisującą przedsiębraną akcję [[#Akcje_jednostek_i_ich_wydawanie|akcję]].
 +
 
 +
Implementacja wszystkich innych metod jest opcjonalna — służą one informowaniu AI o wydarzeniach w bitwie, niemniej AI może te informacje samodzielnie (acz żmudnie) pozyskiwać odpytując interfejs ICallback. Do tego jednak konieczne jest zapamiętanie jego adresu — drugą funkcją, którą należy więc przeciążyć jest:
 +
virtual void init(CBattleCallback * CB);
 +
Nie jest to „konieczne” w sensie ścisłym, niemniej bez tego AI nie będzie w stanie sensownie działać.
 +
 
 +
= Ważne klasy i ich ważne atrybuty =
 +
== Węzeł systemu bonusów — CBonusSystemNode ==
 +
Wszystkie opisane niżej klasy (i wiele innych) dziedziczą po CBonusSystemNode. Oznacza to, że są zarządzane przez [[Bonus_system|system bonusów]]. Najkrócej ujmując, system bonusów pozwala określić dla każdego z węzłów wartość szeregu atrybutów (np. liczba punktów ataku), obecność flag i efektów.
  
Komunikacja AI -> silnik odbywa się albo poprzez zwracanie odpowiednich wartości, albo, na żądanie AI, przez wywoływanie metod na obiekcie klasy CCallback, który jest dostarczany przez wskaźnik w wywołaniu metody init() AI.
+
== Oddział — CStack ==
 +
Podstawowa klasa opisująca oddział na polu bitwy. Żaden z oddziałów nie zostanie skasowany w trakcie bitwy — obiekty tej klasy trwać będą nawet po śmierci oddziału. Natomiast w czasie bitwy mogą pojawić się nowe oddziały, wtedy AI otrzyma wywołanie battleNewStackAppeared.
  
  
== Informacje ogólne. ==
+
=== Ważne pola ===
 +
* TQuantity count — liczebność oddziału
 +
* THex position — numer heksa na którym stoi oddział (lub jego przód, jeśli zajmuje dwa heksy)
 +
* ui32 firstHPleft — ile punktów zdrowia potrzeba odebrać, by ubić pierwszego stwora w oddziale.
  
Pole bitwy składa się z heksagonalnych pól ułożonych w 11 linii po 17 pól, przy czym pola w dwóch skrajnych kolumnach nie są dostępne dla zwykłych jednostek. Ponadto niektóre z hexów (pól heksgonalnych) mogą być blokowane przeszkodami i jednostki nie mogą na nich stawać. Każda jednostka zajmuje jednego hexa lub dwa sąsiadujące w poziomie hexy. Bitwa jest podzielona na tury, w których każda jednostki ruszają się sekwencyjnie w kolejności zależnej od ich inicjatywy (w przypadku zwykłych jednostek jest ona równa prędkości jednostki). Ponadto AI, zależnie od rodzaju bohatera i posiadanych punktów many, raz na turę może rzucić wybrany czar z księgi zaklęć.
+
=== Ważne metody ===
 +
* ui32 Speed(int turn = 0) — oblicza szybkość stwora (opcjonalnie — za ileś tur)
  
 +
== Obiekt z armią — CArmedInstace ==
 +
Obie armie uczestniczące w bitwie nie biorą się znikąd. Silnik gry musi je skojarzyć z jakimś uzbrojonym obiektem. CArmedInstance stanowi bazową klasę dla bohatera, miasta (opisanych niżej) oraz szeregu mniej znaczących klas reprezentujących rozmaite obiekty z armią.
 +
 +
== Bohater — CGHeroInstance ==
 +
CGHeroInstance to podstawowa klasa reprezentująca bohatera - obiekt posiadający w sensie systemu bonusów wszystkie jednostki AI. W trakcie rozgrywki każde AI ma dokładnie jednego bohatera, żyjącego przez cały czas bitwy. Jest to węzeł pośredniczący w przekazywaniu pewnych bonusów jednostkom, jednak sam również generuje pewne premie. Jego obecność umożliwia AI rzucanie czarów zapisanych w księdze zaklęć bohatera.
 +
 +
=== Ważne pola ===
 +
* si32 mana — liczba punktów many (za te punkty rzuca się czary).
 +
* std::set<ui32> spells — identyfikatory znanych czarów.
 +
 +
=== Ważne metody ===
 +
* int getPrimSkillLevel(int id) — pozwala sprawdzić wartość umiejętności drugorzędnej. Przykład: h->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
 +
* ui8 getSecSkillLevel(SecondarySkill skill) — pozwala sprawdzić wartość umiejętności drugorzędnej (wyniki: 0 — brak; ...; 3 — ekspert)
 +
 +
== Miasto — CGTownInstance ==
 +
 +
Niektóre bitwy są oblężeniami miast. W takim przypadku broniące się AI (to po prawej stronie pola bitwy) dostaje dodatkowe premie z tego powodu. Przede wszystkim miasto może być wyposażone w fort/cytadelę/zamek, które powodują, że obrońca jest otoczony murem z opcjonalną fosą i wieżyczkami strażniczymi (zależne od poziomu ulepszenia). Ponadto niektóre miasta dodają inne premie broniącej się armii - patrz np. [http://h3.heroes.net.pl/budynki/zamek.html tutaj]. AI atakujące miasto z murami otrzymuje katapultę mogącą niszczyć mury i wieżyczki strażnicze.
 +
 +
= Informacje szczegółowe =
 
== Początek bitwy ==
 
== Początek bitwy ==
  
Line 26: Line 115:
  
 
Opcjonalnie, zależnie od artefaktów posiadanych przez bohatera, zaraz po tym wywołaniu możliwe są wywołania takie jak przy rzucaniu czaru.
 
Opcjonalnie, zależnie od artefaktów posiadanych przez bohatera, zaraz po tym wywołaniu możliwe są wywołania takie jak przy rzucaniu czaru.
 +
 +
== Faza taktyczna ==
 +
Jeżeli jeden z bohaterów ma lepiej rozwiniętą zdolność drugorzędna „taktyka” od rywala, ma możliwość ustawienia swoich oddziałów przed rozpoczęciem bitwy.
 +
 +
AI otrzymuje wywołanie metody yourTacticPhase. Wewnątrz tej funkcji może robić wywołania cb->battleMakeTacticAction. Dozwolone akcje to zakończenie taktyki oraz przesunięcie jednostki. Parametrem yourTacticPhase jest int distance — określa szerokość pasu dostępnych pól (liczone od skrajnej kolumny heksów po stronie gracza z taktyką).
 +
 +
Po wykonaniu wszystkich przesunięć jednostek, należy zgłosić zakończenie fazy taktycznej — wywołać battleMakeTacticAction z akcją typu END_TACTIC_PHASE (warto wykorzystać pomocniczą funkcjęBattleAction::makeEndOFTacticPhase).
  
 
== Przebieg tury ==
 
== Przebieg tury ==
  
Każda tura zaczyna się od dwóch wywołań:
+
Każda tura zaczyna się od dwu wywołań:
void battleNewRoundFirst(int round){};
+
void battleNewRoundFirst(int round)
void battleNewRound(int round){};
+
void battleNewRound(int round)
 
z których najpierw wykonywane jest pierwsze, a potem drugie (obydwa dostają jako parametr numer tury). Różnią się tym, że pierwsze jest robione przed, a drugie po naniesieniu na stan gry zmian wynikających z rozpoczęcia się nowej rudny (np. zakończenie działania pewnych efektów czarów).
 
z których najpierw wykonywane jest pierwsze, a potem drugie (obydwa dostają jako parametr numer tury). Różnią się tym, że pierwsze jest robione przed, a drugie po naniesieniu na stan gry zmian wynikających z rozpoczęcia się nowej rudny (np. zakończenie działania pewnych efektów czarów).
  
Następnie, dla każdej jednostki w kolejności wyliczonej inicjatywy, może nastąpić następująca sytuacja:
+
Następnie po kolei dla każdej jednostki następuje jedna z poniższych sytuacji:
 
* Jednostka traci turę z powodu złego morale - szansa zależna od wartości morale danej jednostki. AI dostaje wywołania o początku i końcu akcji BAD_MORALE.
 
* Jednostka traci turę z powodu złego morale - szansa zależna od wartości morale danej jednostki. AI dostaje wywołania o początku i końcu akcji BAD_MORALE.
 
* Jednostka jest pod wpływem berserku i automatycznie atakuje najbliższą jednostkę pozostającą w zasięgu (AI otrzymuje informację o początku i końcu akcji ataku na najbliższą jednostkę - WALK_AND_ATTACK). W przypadku braku jednostek w zasięgu jednostka nic nie robi (AI dostaje informację o początku i końcu akcji DO_NOTHING).
 
* Jednostka jest pod wpływem berserku i automatycznie atakuje najbliższą jednostkę pozostającą w zasięgu (AI otrzymuje informację o początku i końcu akcji ataku na najbliższą jednostkę - WALK_AND_ATTACK). W przypadku braku jednostek w zasięgu jednostka nic nie robi (AI dostaje informację o początku i końcu akcji DO_NOTHING).
 
* Jeżeli jednostka jest balistą, a bohater AI nie posiada umiejętności "artyleria", to AI otrzymuje informację o początku i końcu akcji strzału balisty.
 
* Jeżeli jednostka jest balistą, a bohater AI nie posiada umiejętności "artyleria", to AI otrzymuje informację o początku i końcu akcji strzału balisty.
 
* Jeśli jednostka jest namiotem medyka, a bohater AI nie posiada umiejętności "pierwsza pomoc", to AI otrzymuje informację o początku i końcu akcji STACK_HEAL (jeśli jakaś jednostka jest ranna) lub DO_NOTHING (jeśli żadna nie jest ranna).
 
* Jeśli jednostka jest namiotem medyka, a bohater AI nie posiada umiejętności "pierwsza pomoc", to AI otrzymuje informację o początku i końcu akcji STACK_HEAL (jeśli jakaś jednostka jest ranna) lub DO_NOTHING (jeśli żadna nie jest ranna).
* Jeśli nie zaszła żadna z poprzednich możliwości, AI będące posiadaczem jednostki (lub wrogie, jeśli jednostka jest zahipnotyzowana) jest proszone o wykonanie akcji dla niej. Zapytanie to może zostać powtórzone, jeśli jednostka ma wysokie morale (szansa zależna od wartości premii). Zapytanie o akcję realizowane jest za pomocą funkcji BattleAction activeStack(int stackID)
+
* Jeśli nie zaszła żadna z poprzednich możliwości, AI będące posiadaczem jednostki (lub wrogie, jeśli jednostka jest zahipnotyzowana) jest proszone o podanie akcji, którą oddział winien podjąć.  
gdzie jako parametr występuje unikalny identyfikator jednostki. Funkcja ma zwrócić obiekt opisujący ruch jednostki lub rzucany czar. Możliwe akcje są następujące:
+
* Po wykonaniu akcji, jeżeli jednostka jest żywa i ma dodatnie morale, istnieje szansa na uzyskanie przez nią powtórnego ruchu — AI wtedy ponownie jest pytane o akcję (jak wyżej).
 +
 
 +
Jeżeli po przejściu tej sekwencji bitwa ciągle nie jest skończona (obie strony posiadają żywe oddziały, następuje kolejna tura.
 +
 
 +
== Akcje jednostek i ich wydawanie ==
 +
Zapytanie o akcję realizowane jest za pomocą funkcji BattleAction activeStack(int stackID) gdzie jako parametr występuje unikalny identyfikator jednostki. Funkcja ma zwrócić obiekt opisujący ruch jednostki lub rzucany czar. Możliwe akcje są następujące:
 
* rzucenie czaru przez bohatera (nie powoduje utraty tury przez jednostkę, można rzucać czar tylko raz na turę - o ile bohater AI ma taką możliwość w ogóle)  
 
* rzucenie czaru przez bohatera (nie powoduje utraty tury przez jednostkę, można rzucać czar tylko raz na turę - o ile bohater AI ma taką możliwość w ogóle)  
 
* przejście jednostki na inne pole
 
* przejście jednostki na inne pole
Line 47: Line 148:
 
* poddanie się AI
 
* poddanie się AI
 
* zaatakowanie pieszo (ang. melee) jednostki wroga znajdującej się w zasięgu, z wybranego pola sąsiadującego
 
* zaatakowanie pieszo (ang. melee) jednostki wroga znajdującej się w zasięgu, z wybranego pola sąsiadującego
* strzał do dowolnej jednostki (może być niemożliwy, nie każda jednostka strzela, stojąca koło jednostki wroga jednostka z reguły blokuje możliwość strzelania)
+
* strzał do dowolnej jednostki (może być niemożliwy, nie każda jednostka strzela, stojąca koło jednostki wroga jednostka z reguły blokuje możliwość strzelania; każdy strzał zmniejsza liczbę pocisków jednostki, chyba, że na polu bitwy jest wóz z amunicją (Ammo Cart). Jednostka z liczbą pocisków równą zero nie może strzelać)
 
* czekanie (jednostka będzie się ruszała na końcu tury, po wszystkich jednostkach z niższą inicjatywą)
 
* czekanie (jednostka będzie się ruszała na końcu tury, po wszystkich jednostkach z niższą inicjatywą)
 
* rzucenie czaru przez jednostkę (nieliczne jednostki to potrafią, jest to jeszcze nieobsługiwane, ale powinno w końcu się pojawić)
 
* rzucenie czaru przez jednostkę (nieliczne jednostki to potrafią, jest to jeszcze nieobsługiwane, ale powinno w końcu się pojawić)
Line 54: Line 155:
 
== Informacja o początku / końcu akcji ==
 
== Informacja o początku / końcu akcji ==
  
TBD
+
Każde polecenie wysłane przez AI do silnika jest analizowane pod kątem możliwości jego wykonania. Jeśli uzna, że polecenie jest wykonalne (czyli np. nie jest poleceniem rzucenia nieposiadanego czaru lub próbą ataku własną jednostką na inną własną jednostkę), przystępuje do jego wykonania. Wykonanie akcji zawsze zaczyna się od poinformowania o jej początku przez wywołanie <code class="inline">void actionStarted(const BattleAction *action){}</code>, następnie, jeśli jest taka potrzeba, następują wywołania o efektach akcji (np. jednostka ruszyła się na inne pole / zaatakowała jakąś inną / czar został rzucony), całość zaś kończy wywołanie <code class="inline">void actionFinished(const BattleAction *action){}</code> oznaczające, że wszystkie efekty związane z daną akcją zostały obsłużone.
  
 
== Wywołania AI -> silnik ==
 
== Wywołania AI -> silnik ==
  
Obecnie zaimplementowane są następujące wywołania a callbacku:
+
Obecnie zaimplementowane są następujące wywołania w callbacku:
 +
 
 +
[http://sourceforge.net/apps/trac/vcmi/browser/branches/programmingChallenge/lib/IGameCallback.h Plik nagłówkowy]
 +
 
 +
[http://vcmi.eu/pc/class_c_battle_info_callback.html Dokumentacja wygenerowana Doxygenem]
  
TBD
+
Umożliwiają one dostęp do każdej mechanicznie istotnej informacji o stanie gry, do jakiej dostęp ma AI. Część informacji zdobywana jest jednak w sposób pośredni, przez wywoływanie odpowiednich metod na obiektach dostarczanych przez wymienione metody. Pewne niezmienne w czasie elementy mechaniki sa także dostępne przez specjalny obiekt klasy VLC (dostępne dla AI po zainclude'owaniu pliku /lib/VCMI_Lib.h).
  
 
== Czary bohaterów ==
 
== Czary bohaterów ==
  
W grze na obecną chwilę zaimplementowane jest 48 z 59 czarów bitewnych dostępnych w oryginalnej grze. Wśród nich są czary zadające jednostkom wroga bezpośrednie obrażenia, zwiększające parametry jednostek AI, obniżające parametry wrogich jednostek lub specyficzne czary pozwalające np. przejąć kontrolę nad wrogą jednostką na pewien czas. Pełną lista jest dostępna w Internecie.
+
Bohaterowie mogą rzucać w trakcie bitwy czarować. Aby rzucić zaklęcie konieczne jest jednak spełnienie następujących warunków:
 +
* Bohater musi posiadać księgę zaklęć oraz nie rzucił jeszcze w tej turze czaru. Aby sprawdzić, czy nasz bohater w danym momencie bitwy jest zdolny do czarowania, można posłużyć się metody <code class="inline">bool CCallback::battleCanCastSpell()</code>.
 +
* Zaklęcie jest dostępne dla bohatera: ma je zapisane w księdze zaklęć bądź uzyskał w drodze bonusu (np. dzięki artefaktowi). Aby sprawdzić, czy konkretny czar jest dostępny dla bohatera, należy na nim wywołać metodę <code class="inline">bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const</code>. Wektor wszystkich czarów w grze jest dostępny np. jako <code class="inline">VLC->spellh->spells</code>
 +
* Bohater ma nie mniej punktów many niż wynosi koszt zaklęcia. Aby sprawdzić koszt rzucenia czaru, należy użyć metody <code class="inline">int CCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const</code>, podając jako argumenty wybrany czar i naszego bohatera-dowódcę. Liczba punktów many bohatera jest publicznie dostępnym polem klasy <code class="inline">CGHeroInstance</code> o nazwie <code class="inline">mana</code>
 +
* Czar można rzucić tylko, gdy nasza jednostka oczekuje na wykonanie akcji: musi się to odbyć po wywołaniu metody activeStack, a przed jej zwróceniem. Rzucenie czaru nie zwalnia od obowiązku zwrócenia akcji dla obecnego oddziału. [TODO: opisać synchronizację oraz przypadek utraty akcji, jeśli oddział zginie w efekcie rzuconego czaru.
 +
 
 +
Aby rzucić czar, należy wywołać metodę <code class="inline">int CCallback::battleMakeAction(BattleAction* action)</code>, gdzie struktura BattleAction opisuje rzucony czar: typ akcji wynosi <code class="inline">BattleAction::HERO_SPELL</code>, pole <code class="inline">additionalInfo</code> zawiera ID czaru, pole <code class="inline">destinationTile</code> docelowy heks na który ma być rzucony czar (jeśli dotyczy). Pamiętać również trzeba o polu <code class="inline">side</code> — musi być ustawione zgodnie z naszą stroną (0 — atakujący, 1 — obrońca).
 +
 
 +
W grze obecnie zaimplementowane jest 48 z 59 czarów bitewnych dostępnych w oryginalnej grze. Listę zaimplementowanych czarów można znaleźć [https://spreadsheets.google.com/ccc?key=pRhYM0YkAF9lIpLe4raNAWA#gid=1 tutaj], a opisy działania czarów np. [http://www.heroesofmightandmagic.com/heroes3/spellsair.shtml tutaj] (wyświetla tylko czary magii powietrza; aby obejrzeć inne, trzeba wybrać inną opcję z menu na górze!) Wśród nich są czary zadające jednostkom wroga bezpośrednie obrażenia, zwiększające parametry jednostek AI, obniżające parametry wrogich jednostek lub specyficzne czary pozwalające np. przejąć kontrolę nad wrogą jednostką na pewien czas.
  
 
== Koniec bitwy ==
 
== Koniec bitwy ==
Line 71: Line 184:
 
void battleEnd(const BattleResult *br);
 
void battleEnd(const BattleResult *br);
 
Zawierające informacje o typie zwycięstwa, wygranej stronie, ofiarach, doświadczeniu zdobytym przez bohatera oraz przejętych artefaktach.
 
Zawierające informacje o typie zwycięstwa, wygranej stronie, ofiarach, doświadczeniu zdobytym przez bohatera oraz przejętych artefaktach.
 +
 +
= Uruchamianie bitwy =
 +
== Uczestniczące programy ==
 +
Bitwa rozgrywa się między dwoma AI. AI jest jednak biblioteką dynamiczną, jej samej nie da się uruchomić. Konieczna jest do tego aplikacja, która ją załaduje. Do przeprowadzenia bitwy potrzebna jest współpraca kilku aplikacji:
 +
* VCMI_server — serwer rozgrywki sterujący jej przebiegiem i logiką. Łączy się z runnerami, odbiera od nich żądania akcji i informuje je o ich skutkach.
 +
* VCMI_runner — aplikacja pośrednicząca między serwerem oraz AI. Runner przechowuje stan bitwy i aktualizuje go w oparciu o informacje otrzymywane z serwera. Jeden runner ładuje jedną bibliotekę AI, którą obsługuje (wywołuje jej call-iny oraz dostarcza interfejsu zwrotnego).
 +
* odpalarka — pomocniczy program, który odpala wszystkie powyższe aplikacje w odpowiedniej liczbie i z odpowiednimi argumentami.
 +
 +
== Odpalarka ==
 +
=== Parametry odpalarki ===
 +
Aby uruchomić bitwę trzeba skorzystać z odpalarki. Przyjmuje ona argumenty określające:
 +
* plik JSON z parametrami bitwy. Domyślnie wykorzystywany jest plik b1.json z folderu z VCMI.
 +
* nazwy AI (lub pełne ścieżki do nich) uczestniczących w bitwie. Domyślnie dostępne jest StupidAI będące równoważne udostępnionej przykładowej implementacji AI.
 +
* folder, w którym maja być umieszczone logi
 +
* plik, do którego ma być dopisany rezultat bitwy
 +
 +
Składnia argumentów:
 +
-h [ --help ]                        Display help and exit
 +
-l [ --aiLeft ] arg (=StupidAI)      Left AI path
 +
-r [ --aiRight ] arg (=StupidAI)      Right AI path
 +
-b [ --battle ] arg (=b1.json)        Duel file path
 +
-o [ --resultsOut ] arg (=./results.txt)
 +
                                        Output file when results will be
 +
                                        appended
 +
-d [ --logsDir ] arg (=.)            Directory where log files will be
 +
                                        created
 +
-v [ --visualization ]                Runs a client to display a
 +
                                        visualization of battle
 +
 +
Przykładowa komenda (wydana w folderze VCMI):
 +
odpalarka.exe -l"C:\Documents and Settings\Administrator\Pulpit\Programming challenge\YourAI\YourAI_Standalone.dll" -d../logs -o../logs/results_list.txt
 +
Uruchomi bitwę, gdzie lewe AI będzie wczytane z podanej ścieżki, zaś logi trafią do fodleru nadrzędnego „logs”.
 +
 +
 +
=== Plik JSON z bitwą ===
 +
Plik opisujący bitwę zapisany jest w tekstowym formacie [http://pl.wikipedia.org/wiki/JSON JSON] ([http://www.json.org/ strona oficjalna]). Ogólnie format JSON obsługuje:
 +
* obiekty — są to pary "nazwa" : wartość wewnątrz klamer {}.
 +
* tablice (wektory) — są to ciągi wartości w klamrach []
 +
* literały — liczby i stringi w cudzysłowach ""
 +
 +
Plik zawiera jeden obiekt o następujących polach:
 +
* "terType" — liczba, [http://bbs.wakeofgods.com/erm_help/format/format_tr.htm identyfikator terenu], na którym odbywa się bitwa. Jednostki znajdujące się na swoim [http://heroes.thelazy.net/wiki/Native_Terrain ojczystym terenie] otrzymują niewielkie premie.
 +
* "bfieldType" — liczba, [http://bbs.wakeofgods.com/erm_help/format/format_bi.htm typ pola bitwy]. Nie ma znaczenia mechanicznego, określa grafikę w tle.
 +
* "obstacles" — wektor zawierający liczby lub dwuelementowe wektory liczb. Opisuje rozmieszczenie przeszkód. Pojedyncza liczba oznacza umieszczenie na danym polu pniaka (będzie on zablokowany). Pary mają postać [id_przeszkody, nr_pola] — przeszkoda danego typu zostanie umieszczona na wskazanej pozycji. Spis identyfikatorów przeszkód jest dostępny [http://bbs.wakeofgods.com/erm_help/receivers/receiver_bf.htm np. tu].
 +
* "sides" — dwuelementowy wektor zawierający struktury z opisem stron uczestniczących w bitwie.
 +
* "creatures" — wektor struktur pozwalających zmienić parametry danej jednostki na czas bitwy.
 +
 +
Struktura opisująca stronę walki zawiera następujące pola:
 +
* "side" — liczba, określa, która to jest strona (0 — lewa, 1 — prawa)
 +
* "army" — wektor par liczb postaci [[http://bbs.wakeofgods.com/erm_help/format/format_c.htm id_stwora], liczebność]. Każda para opisuje jeden oddział. Liczba oddziałów musi być w przedziale [1—7].
 +
* "heroid" — liczba, [http://bbs.wakeofgods.com/erm_help/format/format_h.htm identyfikator bohatera]. Jeśli brak tego parametru, strona nie będzie miała bohatera.
 +
* "heroPrimSkills" — czteroelementowy wektor liczb, określających wartości kolejnych umiejętności pierwszorzędnych boahtera (atak, obrona, siła czarów, wiedza).
 +
* "spells" — wektor zawierający [http://bbs.wakeofgods.com/erm_help/format/format_sp.htm identyfikatory czarów] znanych przez bohatera.
 +
 +
Struktura opisująca zmianę parametrów jednostki:
 +
* "id" — liczba określająca identyfikator jednostki, która otrzyma nowe parametry
 +
* "attack" — liczba, wartość punktów ataku stwora. Analogicznie: "defense" (obrona), "HP" (punkty wytrzymałości), "dmg" (zadawane obrażenia), "shoots" (strzały), "speed" (szybkość).
 +
 +
 +
== Wynik bitwy i logi ==
 +
=== Logi ===
 +
W wyniku przeprowadznia bitwy w folderze z logami pojawią się następujące pliki:
 +
* VCMI_Server_log.txt — log serwera. Można w nim m.in. sprawdzić, jakie PID dostały runnery obsługujące poszczególne AI (ctrl+f za „PID=”).
 +
* VCMI_runner_log_N.txt — dwa lub trzy takie pliki powstają, gdzie za N podstawiony jest PID programu. Odpowiadają runnerom obsługującym AI. Jeśli AI spowoduje błąd, logu należy szukać właśnie w zapisie runnera, który je wczytał.
 +
* first_runner.txt, second_runner.txt, third_runner.txt — zapisy standardowego wyjścia runnerów. Zwykle zawierają podzbiór ich logów, ale przy niektórych błędach mogą zawierać wskazówkę.
 +
* result.vdrst — binarny plik (nie do odczytu ludzkiego) z informacjami o rezultacie bitwy. Zawiera zserializowany obiekt BattleResult.
 +
* duel_log.vdat — binarny plik z zapisem przebiegu bitwy. Klient VCMI może go wykorzystać do wizualizacji bitwy.
 +
 +
=== Wyniki ===
 +
Podany w argumencie odpalarki plik z wynikami po przeprowadzeniu bitwy otrzyma dodatkową linię. Będzie wyglądała podobnie do poniższej:
 +
b1.json C:\Documents and Settings\Administrator\Pulpit\Programming challenge\YourAI\YourAI_Standalone.dll StupidAI 0 13622 SIDE_DEFEATED Sat Dec 10 00:06:16 2011
 +
 +
Kolejne pola wyniku (oddzielone tabulacją) to:
 +
* nazwa pliku z bitwą
 +
* nazwa lewego (atakującego) AI
 +
* nazwa prawego (broniącego się) AI
 +
* kto wygrał: 0 oznacza lewego, 1 prawego
 +
* z jakimi stratami wygrał (im większa liczba, tym gorsze zwycięstwo)
 +
* przyczyna zakończenia bitwy. SIDE_DEFEATED oznacza normalne zwycięstwo (możliwa jest również dyskwalifikacja którejś ze stron)
 +
* czas bitwy
 +
 +
= Przebieg rozgrywek i punktacja =
 +
Punkty będą przyznawane w trzech fazach. Max. do zdobycia: 8 pkt, przy czym:
 +
# 0—5 pkt — za implementację konkretnych (wskazanych) strategii. AI będzie musiało stoczyć ciąg bitew, za każde zwycięstwo otrzymuje punkt.
 +
# 0—3 pkt — za ogólną skuteczność AI. Moduł będzie musiał pokierować znaczną armią w bitwie, otrzyma punkty zależne od strat poniesionych przy zwycięstwie.
 +
# 0—? pkt — po zakończeniu i ocenie zasadniczej części „Wyzwania” przeprowadzony zostanie turniej między najlepszymi AI. Te, które się wyróżnią (zakładając satysfakcjonujący ogólny poziom projektów) otrzymają dodatkowe punkty.
 +
 +
Spełnienie wymogów formalnych ([[#Wymogi_techniczne_i_formalne]]) jest niezbędne, aby program był wzięty pod uwagę w ocenianiu.
 +
 +
 +
== Strategie wymagane w pierwszej fazie ==
 +
=== Zaatakuj pierwszy ("Pojedynek samurajów") ===
 +
Często w bitwie przewagę zyskuje ten, kto zada pierwszy cios. Osłabiony atakiem przeciwnik kontratakuje mniej groźnie.
 +
AI powinno więc tak manewrować swoimi oddziałami, by te nie wchodziły w zasięg wrogich oddziałów i były zdolne do zadania uderzenia jako pierwsze.
 +
Pomocne może być użycie akcji WAIT, by wróg zdradził się ze swoimi zamiarami.
 +
 +
=== Zwinny strzelec ("Elf - kill the Dwarf!") ===
 +
Nawet pojedynczy strzelec może pokonać wielekroć silniejszy oddział, jeżeli ma odpowiednią przewagę szybkości. Wystarczy biegać naokoło i strzelać, samemu pozostając w bezpiecznej odległości.
 +
Uwaga! Część pól może być zablokowana przez przeszkody, trzeba uważać, by się nie zapędzić w ślepy zaułek.
 +
 +
Przydatne czary:
 +
* Precision — zwiększa atak jednostki strzelającej w walce dystansowej.
 +
* Haste — zwiększa szybkość jednostki.
 +
* Slow — zmniejsza szybkość wroga.
 +
 +
=== Szarża na strzelców ("Kill bloody elves!") ===
 +
Jeżeli wróg dysponuje grupą strzelców, należy działać roztropnie. Nie wolno przedwcześnie zanadto się zbliżyć, gdyż przy znacznej odległości strzały tracą połowę efektywności (p. funkcja callbacku battleHasDistancePenalty). Tu przydatny jest WAIT — niech wróg najpierw strzeli.
 +
 +
Gdy już się dotrze do wroga, należy możliwie efektywnie go „zablokować” (oddział strzelający traci tę zdolność, gdy stoi obok niego wróg, zaś walcząc wręcz zadaje połowę obrażeń i traci niektóre zdolności, takie jak wielokrotny atak). Jeśli jest kilka wrogich oddziałów, możliwe, że będziemy w stanie zablokować więcej niż jeden na raz. (Tylko czasem warto zablokować jeden silny zamiast dwu słabych...)
 +
 +
Pomocne też mogą być czary:
 +
* Air Shield — zmniejsza obrażenia otrzymywane w wyniku ostrzału
 +
* Forgetfulness — przeklęty oddział traci zdolność strzelania i musi walczyć wręcz (połowa efektywności)
 +
* Shield — zmniejsza obrażenia w walce wręcz. Do stosowania, gdy już się do wroga i zaangażuje w bezpośrednią walkę.
 +
 +
=== Czarodziej ===
 +
Do przejścia tego testu wystarczy wspomaganie swoich oddziałów czarami i osłabianie nimi wrogów. Przydadzą się także zasady sterowania oddziałami z pozostałych scenariuszy taktycznych.
 +
Poza czarami wymienionymi w pozostałych taktykach, warto wesprzeć:
 +
* Bless/Curse — jednostka zadaje maksymalne/minimalne obrażenia. Efektywne względem jednostek o dużym „rozrzucie” zadawanych obrażeń.
 +
* czary ofensywne: magic arrow, ice bolt, lightening bolt, implosion — działają dość identycznie: zadają wrogowi obrażenia.
 +
 +
=== Mistrz taktyki ===
 +
Najlepsi dowódcy potrafią tak ustawić swoje armie przed bitwą, aby wykorzystać nawet najsłabsze z nich. Zadaniem gracza jest chronienie mało wytrzymałego (ale silnego w ataku) strzelca przed atakiem w pierwszej turze. Strzelec zaś, gdy przyjdzie jego czas, powinien uderzyć najgroźniejszego wroga.
 +
 +
== Wskazówki odnośnie drugiej fazy ==
 +
Ogłoszone zostaną czary i umiejętności jednostek, którymi warto się zainteresować.
 +
 +
== Trzecia faza — turniej ==
 +
Po zakończeniu obu poprzednich faz, pojawią się tu szczegóły.
 +
 +
= Wymogi techniczne i formalne =
 +
Celem zadania jest implementacja modułu bitewnego AI. Całość musi być napisana w języku C++. Wolno korzystać wyłącznie ze standardowej biblioteki języka, biblioteki silnika VCMI oraz dostępnego na serwerze student zbioru bibliotek Boost. Nie wolno wykorzystywać innych bibliotek, w tym systemowych.
 +
 +
== Budowa ==
 +
Folder ze źródłami powinien być zatytułowany NazwiskoImie.
 +
Do kodu źródłowego biblioteki musi być dołączony plik Makefile. Wydanie polecenia make w folderze z nazwiskiem ma stworzyć wewnątrz tego folderu plik NazwiskoImie.so. Budowanie musi odbywać się wewnątrz folderu (tzn. nie można tworzyć ani pisać do plików poza nim). Kod musi się kompilować bez żadnych dodatkowych zależności na serwerze student (GCC 4.3).
 +
 +
== Ogólne wymogi ==
 +
Modułowi AI nie wolno tworzyć procesów potomnych, tworzyć ani pisać do żadnych plików ani komunikować się z jakimikolwiek procesami. (Wyjąwszy, oczywiście, interfejsy udostępnianie przez silnik VCMI.) Modułowi AI nie wolno w żaden sposób zakłócać działania serwera rozgrywki ani procesu go hostującego. W szczególności nie wolno zakłócać pomiarów czasu i uzycia pamięci.
 +
 +
Limity czasowe nałożone na moduł:
 +
* 50 ms na stworzenie (konstruktor i metoda init ŁĄCZNIE)
 +
* 100 ms na inicjalizację (wykonanie metody battleStart)
 +
* 1000 ms (jedna sekunda) na przeprowadzenie fazy taktycznej.
 +
* 150 ms na podjęcie decyzji o ruchu (wykonanie metody activeStack)
 +
* 5 ms na odnotowanie wydarzenia w bitwie (wszystkie pozostałe metody interfejsu IBattleEventsReceiver)
 +
 +
Czas jest mierzony od wywołania procedury do jej „zwrócenia się”. Pomiar będzie dokonany na serwerze Student, tak więc trzeba się liczyć z jego szybkością. Na waszych komputerach AI może działać szybciej lub wolniej.
 +
Jednocześnie zwracam uwagę, że przesunięcie jednostki w fazie taktycznej na studencie trwa ok. 80 ms, zaś rzucenie czaru ok 40 ms. Pozostałe wywołania powinny być wyraźnie szybsze.
 +
 +
Limit dostępnej pamięci dla modułu wynosi 16 MB.
 +
 +
Postać wysyłanych projektów:
 +
Projekt musi być spakowany do postaci NazwiskoImie.tar.gz. W jego wnętrzu musi się znajdować jeden folder o nazwie NazwiskoImie zawierający źródła AI oraz plik Makefile. Aby program mógł zostać poprawnie oceniony, niezbędne jest spełnienie tych wymagań! Archiwum musi zostać wgrane przed upływem [[#Terminy|terminu]] na platformę Moodle.
 +
 +
= Terminy =
 +
Czas na zadawanie pytań: 11 grudnia 2011.
 +
"Zerowy" termin: do końca 23 grudnia 2011.
 +
Pierwszy termin: do końca 30 grudnia 2011.
 +
 +
Planowany jest także drugi termin, w którym jednak liczba punktów do zdobycia będzie wyraźnie mniejsza. Ogłoszony zostanie po sprawdzeniu i ocenieniu programów z pierwszego terminu. Na drugi termin można ponownie przysłać poprawiony program z pierwszego, bez żadnego ryzyka utraty zdobytych punktów (będzie się liczył lepszy z wyników).
 +
 +
= Materiały =
 +
== Serwer Student ==
 +
Po zalogowaniu się na serwer i przejściu do '''katalogu domowego''', proszę wydać następujące polecenia:
 +
 +
wget http://vcmi.eu/pc/YourAI.zip
 +
mkdir ~/challenge_out
 +
unzip YourAI.zip
 +
cd YourAI/
 +
make
 +
chmod 700 run.sh
 +
./run.sh
 +
cat ~/challenge_out/results_1.txt | tail -1
 +
 +
Uwaga — można też całość postawić w podfolderze (nie w katalogu domowym), trzeba wtedy dostosować też skrypt run.sh.
 +
 +
Folder YourAI będzie zawierał źródła i plik Makefile rozwijanego AI. Komenda make buduje plik libYourAI.so. Aby go wypróbować, użyty jest skrypt run.sh, który uruchamia odpalarkę, podając w argumencie ścieżkę do pliku .so oraz do folderu z logami. Ostatnie  poelcenie wypisuje ostatnią linię pliku z wynikami bitwy. Ma ona postać:
 +
b1.json StupidAI /home/infsgrp/mwutow/YourAI/libYourAI.so 0 13622 SIDE_DEFEATED Sun Dec  4 16:04:59 2011
 +
 +
por. [[#Wyniki|opis pliku z wynikami]].
 +
 +
== Windows ==
 +
=== Budowa ===
 +
Aby zbudować AI na Windowsie z użyciem środowiska Visual studio należy:
 +
* pobrać poniższe archiwa
 +
** [http://vcmi.eu/pc/challenge_lib_pack.7z Paczka z bibliotekami] — 33 MB, 7zip
 +
** [http://vcmi.eu/pc/YourAI.zip Paczka z przykładowym AI] — 8 kB, zip
 +
** [http://vcmi.eu/pc/zasoby.7z Paczka z zasobami tekstowymi (reguły gry)] — 600 kB, 7zip
 +
** [http://vcmi.eu/pc/grafiki.7z Paczka z zasobami graficznymi] — 144 MB, 7zip — opcjonalnie, jeżeli chcemy korzystać z wizualizacji bitwy.
 +
** [http://vcmi.eu/pc/grafiki2.7z Paczka z zasobami graficznymi, part 2] — 80 MB, 7zip — opcjonalnie, jeżeli chcemy korzystać z wizualizacji bitwy.
 +
* wypakować do wspólnego folderu. Po tej operacji powinien zawierać podfoldery include, libs, VCMI, YourAI
 +
* w folderze VCMI tworzymy repozytorium SVN ustawione na adres
 +
https://vcmi.svn.sourceforge.net/svnroot/vcmi/branches/programmingChallenge/
 +
* otwieramy Visualem solucję VCMI_VS10.sln z podfolderu VCMI. Wybieramy konfigurację, jaką chcemy (Debug lub RD, czyli Release). Budujemy solucję. Czekamy chwilę — powinniśmy w końcu otrzymać informację o poprawnym zbudowaniu ośmiu projektów.
 +
* otwieramy Visualem solucję YourAI_Standalone.sln z podfolderu YourAI. Budujemy, powinno przejść poprawnie.
 +
* voila! Powinniśmy w podfolderze YourAI uzyskać plik YourAI_Standalone.dll. Jest to biblioteka z AI, którą można już uruchomić odpalarce.
 +
 +
=== Uruchamianie ===
 +
Wykonanie komendy F5 (Start Debugging) uruchomi odpalarkę, a ta bitwę. We właściwościach projektu w karcie „Debugging” w polu „Command Arguments” znajdują się parametry, z jakimi zostanie odpalona. Domyślnie za lewe AI zostanie wzięty wasz projekt, zaś wyniki trafią do nadrzędnego folderu logs.
 +
 +
Więcej o parametrach odpalarki i odczytywaniu wyników bitwy w sekcji [[#Uruchamianie_bitwy]].
 +
 +
UWAGA! Żeby AI poprawnie działało, musi być zbudowane w tej samej konfiguracji (Debug lub RD) co solucja VCMI_VS10! Ich pomieszanie może powodować dziwnie wyglądające błędy, prowadzące najczęściej do dyskwalifikacji AI.
 +
 +
== „Własny” Linux ==
 +
Nie polecam, bo na różnych dystrybucjach się może zachowywać nieprzewidywalnie.
 +
 +
mkdir vcmi
 +
cd vcmi
 +
wget http://sourceforge.net/apps/trac/vcmi/export/2480/branches/programmingChallenge/vcmiinstall.sh
 +
chmod 700 vcmiinstall.sh
 +
./vcmiinstall.sh --install lean
 +
 +
Dalej postępować jak w instrukcji dla studenta. Konieczne są tylko dostosowania w pliku Makefile i run.sh (poprawić ścieżki).
 +
 +
Potrzebne pakiety:
 +
* libtool
 +
* automake
 +
* autoconf
 +
 +
Biblioteki:
 +
* libstdc++ devel
 +
* SDL and SDL-devel
 +
* SDL_mixer and SDL_mixer-devel
 +
* SDL_image and SDL_image-devel
 +
* SDL_ttf and SDL_ttf-devel
 +
* zlib and zlib-devel
 +
* the ffmpeg libraries (libavformat and libswscale). Their name could be libavformat-devel and libswscale-devel, or ffmpeg-libs-devel or similar names.
 +
* boost c++ libraries v1.36+ (1.35 will not work) (www.boost.org):
 +
** program-options
 +
** filesystem
 +
** iostreams
 +
** system
 +
** thread
 +
 +
= FAQ =
 +
* '''Co to są te fazy? I czy te strategie jakie zastosuję (którą) to wybieram ja w AI czy gra?'''
 +
Faza to tylko pomocniczy termin opisujący część procesu oceniania (testowania). AI będzie musiało stoczyć sześć lub siedem bitew. Są one kompletnie niezależne od siebie.
 +
 +
Pierwsze pięć bitew to będą dość proste układy, dla których istnieje wyraźna strategia wygrywająca (jedna lub więcej). Naiwna strategia prowadzi do porażki, sprytna do łatwego zwycięstwa. Strategie [[#Strategie_wymagane_w_pierwszej_fazie|są opisane na wiki]], AI musi „znać” je wszystkie i potrafić ocenić, którą należy stosować (przy czym generalnie one są w znacznej mierze niewykluczające wzajemnie). Za zwycięstwo w każdej z tych pięciu bitew AI otrzymuje po punkcie. Za porażkę — nic. Rozegranie tych pięciu bitew to pierwsza faza testów.
 +
 +
Druga faza zawiera tylko jedną, szóstą z kolei bitwę. Składa się na nią duży, skomplikowany układ. Dwie liczne armie stają naprzeciw siebie. Oceniany jest tutaj nie tylko sam fakt zwycięstwa, ale także poniesione straty. Jeśli AI rozgromi wroga, otrzyma trzy punkty. Jeśli wygra ledwo-ledwo — jeden punkt.
 +
 +
Trzecia faza to turniej między najlepszymi AI. Przebieg tych rozgrywek zostanie określony, gdy już będzie wiadomo, ile tych AI. Odbędzie się po ocenie pozostałych bitew, a możliwe, że nawet po drugim terminie wyzwania.
 +
 +
 +
* '''Czy działanie czaru jednostka odczuwa w trakcie jednej tury, czy może on trwać dłużej?'''
 +
Czary możemy generalnie podzielić na efekty natychmiastowe oraz długotrwałe uroki. Czar natychmiastowy, to np. błyskawica — we wroga uderza grom z jasnego nieba, niosący obrażenia. Nie ma tu mowy o trwaniu.
 +
Natomiast jeśli idzie o uroki (czary typu spowolnienie, błogosławieństwo, itp.) to trwają one tyle tur, ile wynosi siła czarów bohatera.
 +
Wartość siły czarów bohatera można sprawdzić instrukcją:
 +
if(const CGHeroInstance *h = cb->battleGetFightingHero(side)) //mamy bohatera
 +
    h->getPrimSkillLevel(PrimarySkill::SPELL_POWER) //pytamy o jego siłę czarów
 +
 +
 +
* '''Czy istnieje łatwa metoda na wizualizację bitwy na żywo, lub na podstawie logów?'''
 +
Tak, wizualizację bitwy można uruchomić dodając do parametrów odpalarki -v. Będzie działać pod warunkiem wgrania archiwów z grafikami. Rozegraną już bitwę także można zwizualizować, czemu służy plik duel_log.vdat. Umieśćcie go (pod jakąkolwiek nazwą) w folderze z odpalarką i wydajcie polecenie
 +
VCMI_client.exe -rbduel_log.vdat
 +
 +
 +
* '''Czy jednostki są rozstawiane na pozycjach początkowych automatycznie?'''
 +
Tak. Gdy AI otrzymuje wywołanie battleStart, każdy z oddziałów stoi już na swoim początkowym polu (p. CStack::position).
 +
 +
 +
* '''Jakie jednostki są dostępne dla każdego przypadku?'''
 +
Nie jest to określone. AI nie powinna się nastawiać na żadne konkretne jednostki, a raczej samodzielnie je klasyfikować.
 +
 +
 +
* '''Czy można korzystać ze specjalnych umiejętności jednostek?'''
 +
Tak. O ile działają. :P
 +
Inna sprawa, że akurat nieszczególnie warto sobie tym zaprzątać głowę, raczej za dużo tych umiejętności się nie pojawi. Wiele z nich zresztą funkcjonuje bez żadnej ingerencji ze strony AI (podwójny atak np. sam się wykonuje podwójnie, AI nie ma zlecać drugiej akcji).
 +
 +
 +
* '''Co w przypadku, gdy wszyscy zginą?'''
 +
Zupełnie wszyscy na polu bitwy? Wtedy jest remis, AI nie otrzymuje punktów (wymagane jest zwycięstwo). Zresztą, gdy wszyscy już zginą, to co taka sytuacja obchodzi AI? Po wszystkim przecież.
 +
Inna sprawa, że taka sytuacja raczej się nie zdarzy, może być jedynie skutkiem masowych czarów ofensywnych, które raczej nie pojawią się w zestawach testowych.
 +
 +
 +
* '''Czy są brane pod uwagę artefakty?'''
 +
Są, ale AI to w ogóle nie powinno się tym interesować. Wszystkie premie pochodzące z artefaktów są widoczne u bohatera i jego oddziałów.
 +
 +
 +
Przykładowo, jeśli bohater ma czarodziejski łuk, który sprawia że strzelcy nie ponoszą kary za odległość strzału, to AI nie musi sprawdzać listy artefaktów bohatera. Premia ta przechodzi na bohatera i wszystkie jego oddziały. Tak więc wywołanie CBattleInfoCallback::battleHasDistancePenalty da zawsze poprawną odpowiedź, uwzględniającą artefakty. Podobnie jest z innymi efektami artefaktów.
 +
 +
 +
* '''Czy walczyć będziemy z przeciwnikiem sterowanym StupidAI?'''
 +
Nie wykluczam jakichś drobnych poprawek, ale zasadniczo tak. Wróg będzie głupi, jego siła będzie tkwić... w sile.
 +
 +
 +
* '''Czy dostaniemy pliki *.json bitew, które są naszymi zadaniami?'''
 +
Dostępne są przykładowe bitwy, którymi warto się zainteresować: http://vcmi.eu/pc/battles/
 +
Nie są to jednak bitwy identyczne z właściwymi testami — może się pojawić więcej jednostek (np. dwaj wrodzy strzelcy, z czego jeden słabowity i dla zmyłki) lub nieco innych typów.
 +
 +
 +
* '''Czy jest gdzieś dostępny opis tworzenia pliku bitwy?'''
 +
[[#Plik_JSON_z_bitw.C4.85|Opis został dodany.]]
 +
 +
 +
* '''Jakie czary będą dostępne?'''
 +
Wystarczające do zdobycia punktów będzie wykorzystanie czarów wymienionych w opisie strategii. Nie znaczy to, że pojawią się wszystkie te czary, ani że pojawią się tylko te czary.
 +
 +
 +
* '''Czy można wybrać bohatera (specjalność, umiejętności) czy jest z góry określony?'''
 +
Nie można. Bohater jest określony w pliku z bitwą.
 +
 +
 +
* '''Czy bohater będzie posiadał machiny wojenne, a jeśli tak, to jakie?'''
 +
Nie będzie posiadał.
 +
 +
 +
* '''Walka samurajów (kto pierwszy zada cios): wysterczy przecież poczekać aż podejdzie rywal i zadać mu cios?'''
 +
Jednostka może odłożyć ruch na później (wait, raz w turze) lub zrezygnować z ruchu (defend). Strategia właśnie na tym polega — trzeba zaczekać aż wróg podejdzie i wtedy zadać cios.
 +
 +
 +
* '''Czy jest możliwość sprawdzenia czy wróg jest już pod wpływem zaklęcia (np. przed rzuceniem następnego) ?'''
 +
Tak.
 +
Generalnie interesują nas dwie klasy:
 +
CSpell opisuje zaklęcie z punktu widzenia mechaniki. Jest globalnie dostępny wektor VLC->spellh->spells indeksowany po ID zaklęcia (p. namespace Spells) i można z jego pomocą sprawdzać parametry czaru o danym ID.
 +
Bonus to klasa... bardzo uniwersalna. W szczególności realizuje „bycie pod wpływem czaru”.
 +
 +
Pobieranie czarów, pod których wpływem jest jednostka można zrobic na dwa sposoby.
 +
Prostszy — same ID:
 +
tlog0 << "Active spell IDs: ";
 +
BOOST_FOREACH(si32 spellID, firstEnemy->activeSpells())
 +
  tlog0 << spellID << " ";
 +
tlog0 << std::endl;
 +
 +
Sprytniejszy — ID oraz informacje dodatkowe:
 +
tlog0 << "Active spells:\n";
 +
BOOST_FOREACH(const Bonus *b, *firstEnemy->getSpellBonuses())
 +
{
 +
    const CSpell *spellInfo = VLC->spellh->spells[b->sid];
 +
    tlog0 << "Name=" << spellInfo->name << "\tID=" << b->sid <<"\tTurns remaining: " << b->turnsRemain << "\tPositive=" << (int)spellInfo->positiveness << std::endl;
 +
}
 +
tlog0 << std::endl;
 +
 +
 +
* '''battleGetAvailableHexes (const CStack *stack, bool addOccupiable, std::vector< THex > *attackable=NULL) - czym jest drugi i trzeci argument'''
 +
a) stack — oddział, dla którego dostępność pól jest liczona
 +
b) addOccupiable — czy dodać pola „zajmowalne”. Twój oddział może mieć dwa heksy szerokości. Jego pozycją jest pole „ku centrum” (prawe dla atakującego, lewe dla obrońcy). To drugie pole, na którym stoi tył jednostki, nazywane jest polem zajętym (zwraca je metoda occupiedHex). Jeżeli parametr addOccupiable jest ustawiony, to funkcja zwróci nie tylko pola, na których może stanąć przód jednostki, ale też pola, na których może się znaleźć jej tył. Jeżeli stack zajmuje jeden hex, wartość tego argumentu jest bez znaczenia. Aby sprawdzić, czy jednostka zajmuje dwa heksy można użyć wywołania stack->doubleWide() (zwraca prawdę dla dwuheksowców).
 +
c) wskaźnik na wektor pól. Jeżeli jest ustawiony (wołający musi taki wektor zapewnić), to funkcja wypełni go polami, które mogą być celami ataku. Są to pola, na których stoją wrogie jednostki (lub przynajmniej ich tyły), które przy tym znajdują się w zasięgu ruchu lub przylegają do niego (tak, że poprawna jest akcja WALK_AND_ATTACK). Jeżeli stack potrafi strzelać, to w tym wektorze umieszczone zostaną wszystkie pozycje wrogich jednostek (strzelać można na dowolną odległość).
 +
 +
 +
* '''battleGetDistances (const CStack *stack, THex hex=THex::INVALID, THex *predecessors=NULL) - co zwraca?'''
 +
Funkcja wykonuje BFS na polu bitwy — celem jest określenie odległości poszczególnych pól od naszego oddziału. Zwraca wektor liczący tyle elementów, ile jest pól. Tak więc battleGetDistances(stack)[70] to liczba pól, jakie oddział musi przejść, by dojść do heksa nr 70. Jeżeli odległość wynosi -1 to znaczy, że się obecnie nie da dojść na dane pole. Dwa opcjonalne parametry to heks, z którego nalezy rozpocząć poszukiwania (jesli inny od pozycji stacku) oraz wskaźnik na tablicę heksów (predecessors). Jeśli ten wskaźnik jest ustawiony, to musi pokazywć na tablicę obiektów THex liczącą przynajmniej BFIELD_SIZE elementów. Zostanie ona wypełniona poprzednikami, tj. predecessors[70] zawierać będzie nr poprzedniego heksa, który był odwiedzony przez BFS. W funkcji jest błąd — jej drugi parametr jest w istocie ignorowany! Jeśli chcecie liczyć odległości z heksa innego niż pozycja jednostki wykorzystajcie funkcję battleGetDistancesFromHex, która zachowuje się zgodnie z opisem wyżej (to takie battleGetDistances bez buga)!
 +
 +
 +
* '''Jakie współczynniki bierze pod uwagę battleEstimateDamage? Czy tylko obrażenia, czy również obronę, atak (w tym ich różnicę z odpowiednim do jej wartości współczynnikiem), coś jeszcze?'''
 +
Wszystkie wymienione współczynniki są uwzględnione. Funkcja w ogóle bierze pod uwagę wszystko za wyjątkiem efektów losowych lub zależnych od komendy. NIE uwzględnia:
 +
- premii za szarżę (5% za każde pole przejechane w drodze do ataku dla staków z hasBonusOfType(Bonus::JOUSTING) ),
 +
- szczęścia — i tak wyłączone w wyzwaniu,
 +
- losowo działających zdolności jednostek, jak np. death blow rycerza śmierci (szansa na podwójne obrażenia) — i tak wyłączone w wyzwaniu,
 +
- szansy na podwójne obrażenia balisty — nie dość, że wyłączone, to i tak nie dostaniecie balisty.
 +
 +
 +
* '''Czy jest jakaś funkcja, dzięki której automatycznie możemy się dowiedzieć, którą z kolei turę walki rozgrywamy?'''
 +
Nie, ale na początku każdej tury walki AI otrzymuje jej numer w argumencie funkcji battleNewRound.
 +
 +
 +
* '''W jaki sposob sprawdzic, czy CStack moze czekac, czy musi sie ruszyc?'''
 +
CStack może czekać tylko raz w turze. Aby sprawdzić, czy już czekał, można użyć konstrukcji:
 +
if(vstd::contains(stack->state, WAITING))
 +
    ...; //juz czekal, nie moze ponownie
 +
else
 +
    ...; //moze zaczekac jeszcze raz
 +
 +
 +
* '''Jak najlepiej wyciagnac liste dostepnych czarow? Iteracja battleCanCastThisSpell() po wszystkich czarach?'''
 +
Można tak, to jest najpewniejsza metoda — battleCanCastThisSpell sprawdza szereg warunków okolicznościowych, mogących zablokwoać rzucanie czaru (np. specjalny teren pola bitwy, artefakty blokujące magię lub jej część, itp.) lub umożliwiających rzucanie mimo braku znajomości czaru. Jeśli komuś bardzo zależy na szybkości, to można sprawdzać tylko czary o ID ze zbioru hero->spells (to „własna” księga czarów bohatera), choć w pewnych specyficznych przypadkach bohater może rzucać czary spoza tej listy (np. gdy ma artefakt typu Tome of Air).
 +
 +
 +
* '''Czy da sie estymowac obrazenia zadawane przez czary?'''
 +
Dodałem do callbacku metodę battleEstimateSpellDamage. Oprócz czarujacego bohatera i czaru, może przyjąć jako trzeci argument (opcjonalny) oddział, w który czarem chcemy rąbnąć (sprawdzi wtedy jego odporności). Można jej używać wg wzorca:
 +
int dmg = cb->battleEstimateSpellDamage(cb->battleGetFightingHero(side), VLC->spellh->spells[Spells::MAGIC_ARROW]);
 +
Metoda zwraca liczbę punktów obrażeń, jakie czar zapewne by wyrządził. Nie sprawdza jednak, czy bohater może rzucić ten czar.

Latest revision as of 14:45, 23 December 2011

Przebieg bitwy - jak to wygląda od strony AI

Lasciate ogni speranza, voi ch'entrate.

Informacje ogólne

Zadanie

Przedmiotem zadania jest napisanie programu, który będzie sterował graczem w czasie bitwy w otwartej reimplementacji Heroesa 3 — VCMI. Sprowadza się to do napisania w języku C++ dynamicznie ładowanej biblioteki (zależnie od platformy .dll bądź .so), zawierającej klasę implementującą interfejs dla AI.

Skrótowy opis bitwy

Uwaga: opis w tej sekcji jest mocno uproszczony. Ma za zadanie wprowadzić do zadania i wyrobić podstawowe intuicje, uszczegółowiony jest przez reguły zawarte dalej. W razie rozbieżności, to one są decydujące.

Podstawy

Bitwa toczona jest pomiędzy dwiema armiami, które zaczynają rozstawione po przeciwległych krańcach planszy. Armia może (choć nie musi) być dowodzona przez bohatera. Gracza „lewego” nazywamy „atakującym”, „prawego” zaś „broniącym się”. Armia składa się z oddziałów (jednostek) — każdy oddział jest charakteryzowany przez typ (np. pikinier albo czerwony smok) oraz liczebność. Dodatkowo każda jednostka posiada szereg zmiennych współczynników opisujących jej parametry bojowe, do najważniejszych zaliczają się:

  • Atak
  • Obrona
  • Zakres zadawanych obrażeń
  • Wytrzymałość (punkty życia — HP)
  • Szybkość

Oddział ginie, gdy jego liczebność spadnie do zera. Gracz, który straci wszystkie oddziały, przegrywa bitwę.

Ruch

Bitwa podzielona jest na tury. Każda jednostka rusza się raz na turę. Oddziały wykonują ruchy po kolei, w porządku malejącej szybkości. Za każdym razem, gdy wypada kolej ruchu jednostki sterowanej przez AI, następuje wywołanie metody activeStack. Zadaniem AI jest zwrócenie struktury opisującej, co dana jednostka ma uczynić. Podstawowe akcje to:

  • Atak — jednostka może zaatakować sąsiadującą jednostkę w zwarciu, bądź — jeśli umie strzelać — dowolną jednostkę na mapie.
  • Ruch — jednostka może przesunąć się o tyle pól na mapie, ile wynosi jej szybkość. Ruch może zostać zakończony atakiem na osiągnięty oddział wroga.
  • Czekanie — jednostka spróbuje się ruszyć później w tej turze (najwyżej raz na turę).
  • Obrona — jednostka rezygnuje z akcji, aby czasowo poprawić swój współczynnik obrony.

Pole bitwy

Pole bitwy składa się z heksagonalnych pól ułożonych w 11 linii po 17 pól, ponumerowanych jak pokazano na rysunku.

Bfield grid.gif

Pola w dwóch skrajnych kolumnach nie są dostępne dla zwykłych jednostek. Ponadto niektóre z heksów (tj. pól) mogą być zablokowane ze względu na umieszczone na nich przeszkody. Heksy na których stoi już inne jednostka także traktowane są jak zablokowane. Na takim polu żadna z jednostek nie może zakończyć ruchu, przekraczać zaś to pole mogą wyłącznie jednostki latające.

Pozycją jednostki jest numer heksa, na którym stoi. Każda jednostka zajmuje jednego lub dwa sąsiadujące w poziomie heksy. W przypadku jednostki dwuheksowej jej pozycją jest pozycja PRZODU jednostki (wojska atakującego są zawsze zwrócone w prawo, broniącego się zaś w lewo).

Bohater

Jak wskazano wcześniej armia może być dowodzona przez bohatera. Wiąże się to paroma korzyściami:

  • Bohater może posługiwać się magią (p. niżej)
  • Bohater może posiadać specjalne machiny wojenne
  • Jednostki otrzymują premie do atrybutów, zależne od parametrów bohatera

Czary

Bohater, jeżeli jest obecny na polu bitwy, może raz na turę, przed przesunięciem oddziału (w czasie, gdy ten jest aktywny) rzucić zaklęcie. Każdy bohater może posiadać księgę zaklęć, określającą, jakie czary są dostępne (bohater nie posiadający księgi nie może czarować). Rzucenie czaru wymaga poświęcenia pewnej liczby punktów many. Bohater, który wyczerpie swoją manę, traci możliwość rzucania czarów.

Jak AI komunikuje się z grą

Do komunikacji służą dwa interfejsy:

  • CBattleGameInterface — główna klasa AI musi dziedziczyć po tej klasie. Silnik gry wywołuje jej metody, by informować AI o wydarzeniach w grze bądź by zapytać, jaką akcję chce podjąć.
    • Poprzez interfejs IBattleEventsReceiver odziedziczono szereg metod pozwalających otrzymywać informacje o wydarzeniach w bitwie [1].
    • Zaimplementowanie wirtualnej i abstrakcyjnej metody activeStack (wołanej, ilekroć otrzymuje się kolejkę) jest kluczowym aspektem implementacji AI. P. dalej.
  • CBattleCallback — interfejs zaimplementowany w silniku, udostępniany AI. AI może wywoływac jego metody, by pobierać informacje o stanie bitwy oraz by podejmować niektóre specjalne akcje. Dziedziczy on po dwu interfejsach:
    • CBattleInfoCallback — zbiór metod pozwalających odpytywać silnik o stan bitwy. P. [2]
    • IBattleCallback — zbiór (a właściwie) para metod. Pierwsza, battleMakeAction jest stosowana do rzucania czarów przez bohatera. Druga, battleMakeTacticAction, w czasie fazy taktycznej. P. [3]

Każde AI jest kompilowane do dynamicznie ładowanej biblioteki, która musi eksportować następujące funkcje:

  • void GetAiName(char* name); — powinno wypełnić pamięć pod zadanym wskaźnikiem ciągiem znaków z nazwą AI.
  • CBattleGameInterface* GetNewBattleAI(); — powinno stworzyć nowy obiekt głównej klasy AI (dziedziczącej po CBattleGameInterface), które pokieruje nadchodzącą bitwą. Obiekt powinien być stworzony operatorem new, tak aby po zakończeniu bitwy poprawna na nim była operacja delete. Silnik na otrzymanym obiekcie będzie wywoływał stosowne metody. Stanowić one będą podstawę komunikacji silnik -> AI. Pierwszym wywołaniem będzie metoda init, poprzez którą AI otrzyma wskaźnik na implementację interfejsu CBattleCallback, poprzez który AI może „odpytywać silnik”.

Co AI *musi* robić?

Choć interfejs dla AI jest bogaty i zawiera wiele metod, tak naprawdę koniecznie wymagane jest zaimplementowanie tylko jednej. Jest to:

virtual BattleAction activeStack(const CStack * stack)=0;

Metoda ta jest wołana, ilekroć AI musi podjąć akcję dla jakiejś jednostki. Należy zwrócić poprawnie wypełnioną strukturę BattleAction, opisującą przedsiębraną akcję akcję.

Implementacja wszystkich innych metod jest opcjonalna — służą one informowaniu AI o wydarzeniach w bitwie, niemniej AI może te informacje samodzielnie (acz żmudnie) pozyskiwać odpytując interfejs ICallback. Do tego jednak konieczne jest zapamiętanie jego adresu — drugą funkcją, którą należy więc przeciążyć jest:

virtual void init(CBattleCallback * CB);

Nie jest to „konieczne” w sensie ścisłym, niemniej bez tego AI nie będzie w stanie sensownie działać.

Ważne klasy i ich ważne atrybuty

Węzeł systemu bonusów — CBonusSystemNode

Wszystkie opisane niżej klasy (i wiele innych) dziedziczą po CBonusSystemNode. Oznacza to, że są zarządzane przez system bonusów. Najkrócej ujmując, system bonusów pozwala określić dla każdego z węzłów wartość szeregu atrybutów (np. liczba punktów ataku), obecność flag i efektów.

Oddział — CStack

Podstawowa klasa opisująca oddział na polu bitwy. Żaden z oddziałów nie zostanie skasowany w trakcie bitwy — obiekty tej klasy trwać będą nawet po śmierci oddziału. Natomiast w czasie bitwy mogą pojawić się nowe oddziały, wtedy AI otrzyma wywołanie battleNewStackAppeared.


Ważne pola

  • TQuantity count — liczebność oddziału
  • THex position — numer heksa na którym stoi oddział (lub jego przód, jeśli zajmuje dwa heksy)
  • ui32 firstHPleft — ile punktów zdrowia potrzeba odebrać, by ubić pierwszego stwora w oddziale.

Ważne metody

  • ui32 Speed(int turn = 0) — oblicza szybkość stwora (opcjonalnie — za ileś tur)

Obiekt z armią — CArmedInstace

Obie armie uczestniczące w bitwie nie biorą się znikąd. Silnik gry musi je skojarzyć z jakimś uzbrojonym obiektem. CArmedInstance stanowi bazową klasę dla bohatera, miasta (opisanych niżej) oraz szeregu mniej znaczących klas reprezentujących rozmaite obiekty z armią.

Bohater — CGHeroInstance

CGHeroInstance to podstawowa klasa reprezentująca bohatera - obiekt posiadający w sensie systemu bonusów wszystkie jednostki AI. W trakcie rozgrywki każde AI ma dokładnie jednego bohatera, żyjącego przez cały czas bitwy. Jest to węzeł pośredniczący w przekazywaniu pewnych bonusów jednostkom, jednak sam również generuje pewne premie. Jego obecność umożliwia AI rzucanie czarów zapisanych w księdze zaklęć bohatera.

Ważne pola

  • si32 mana — liczba punktów many (za te punkty rzuca się czary).
  • std::set<ui32> spells — identyfikatory znanych czarów.

Ważne metody

  • int getPrimSkillLevel(int id) — pozwala sprawdzić wartość umiejętności drugorzędnej. Przykład: h->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
  • ui8 getSecSkillLevel(SecondarySkill skill) — pozwala sprawdzić wartość umiejętności drugorzędnej (wyniki: 0 — brak; ...; 3 — ekspert)

Miasto — CGTownInstance

Niektóre bitwy są oblężeniami miast. W takim przypadku broniące się AI (to po prawej stronie pola bitwy) dostaje dodatkowe premie z tego powodu. Przede wszystkim miasto może być wyposażone w fort/cytadelę/zamek, które powodują, że obrońca jest otoczony murem z opcjonalną fosą i wieżyczkami strażniczymi (zależne od poziomu ulepszenia). Ponadto niektóre miasta dodają inne premie broniącej się armii - patrz np. tutaj. AI atakujące miasto z murami otrzymuje katapultę mogącą niszczyć mury i wieżyczki strażnicze.

Informacje szczegółowe

Początek bitwy

Na początku bitwy AI otrzymuje od silnika wywołanie funkcji void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side); w której przekazywane są następujące informacje:

  • jednostki należące do atakującego (znajduje się po lewej stronie pola bitwy)
  • jednostki należące do broniącego się (znajduje się po prawej stronie pola bitwy)
  • lokalizację pola bitwy na mapie przygody (mogą od tego parametru zależeć premie lub kary)
  • informacje o bohaterze atakującym i broniącym się, takie jak czary, które może rzucać, bonusy dla jednostek itp.
  • (nadmiarową) informację, po której stronie AI ma walczyć.

Opcjonalnie, zależnie od artefaktów posiadanych przez bohatera, zaraz po tym wywołaniu możliwe są wywołania takie jak przy rzucaniu czaru.

Faza taktyczna

Jeżeli jeden z bohaterów ma lepiej rozwiniętą zdolność drugorzędna „taktyka” od rywala, ma możliwość ustawienia swoich oddziałów przed rozpoczęciem bitwy.

AI otrzymuje wywołanie metody yourTacticPhase. Wewnątrz tej funkcji może robić wywołania cb->battleMakeTacticAction. Dozwolone akcje to zakończenie taktyki oraz przesunięcie jednostki. Parametrem yourTacticPhase jest int distance — określa szerokość pasu dostępnych pól (liczone od skrajnej kolumny heksów po stronie gracza z taktyką).

Po wykonaniu wszystkich przesunięć jednostek, należy zgłosić zakończenie fazy taktycznej — wywołać battleMakeTacticAction z akcją typu END_TACTIC_PHASE (warto wykorzystać pomocniczą funkcjęBattleAction::makeEndOFTacticPhase).

Przebieg tury

Każda tura zaczyna się od dwu wywołań:

void battleNewRoundFirst(int round)
void battleNewRound(int round)

z których najpierw wykonywane jest pierwsze, a potem drugie (obydwa dostają jako parametr numer tury). Różnią się tym, że pierwsze jest robione przed, a drugie po naniesieniu na stan gry zmian wynikających z rozpoczęcia się nowej rudny (np. zakończenie działania pewnych efektów czarów).

Następnie po kolei dla każdej jednostki następuje jedna z poniższych sytuacji:

  • Jednostka traci turę z powodu złego morale - szansa zależna od wartości morale danej jednostki. AI dostaje wywołania o początku i końcu akcji BAD_MORALE.
  • Jednostka jest pod wpływem berserku i automatycznie atakuje najbliższą jednostkę pozostającą w zasięgu (AI otrzymuje informację o początku i końcu akcji ataku na najbliższą jednostkę - WALK_AND_ATTACK). W przypadku braku jednostek w zasięgu jednostka nic nie robi (AI dostaje informację o początku i końcu akcji DO_NOTHING).
  • Jeżeli jednostka jest balistą, a bohater AI nie posiada umiejętności "artyleria", to AI otrzymuje informację o początku i końcu akcji strzału balisty.
  • Jeśli jednostka jest namiotem medyka, a bohater AI nie posiada umiejętności "pierwsza pomoc", to AI otrzymuje informację o początku i końcu akcji STACK_HEAL (jeśli jakaś jednostka jest ranna) lub DO_NOTHING (jeśli żadna nie jest ranna).
  • Jeśli nie zaszła żadna z poprzednich możliwości, AI będące posiadaczem jednostki (lub wrogie, jeśli jednostka jest zahipnotyzowana) jest proszone o podanie akcji, którą oddział winien podjąć.
  • Po wykonaniu akcji, jeżeli jednostka jest żywa i ma dodatnie morale, istnieje szansa na uzyskanie przez nią powtórnego ruchu — AI wtedy ponownie jest pytane o akcję (jak wyżej).

Jeżeli po przejściu tej sekwencji bitwa ciągle nie jest skończona (obie strony posiadają żywe oddziały, następuje kolejna tura.

Akcje jednostek i ich wydawanie

Zapytanie o akcję realizowane jest za pomocą funkcji BattleAction activeStack(int stackID) gdzie jako parametr występuje unikalny identyfikator jednostki. Funkcja ma zwrócić obiekt opisujący ruch jednostki lub rzucany czar. Możliwe akcje są następujące:

  • rzucenie czaru przez bohatera (nie powoduje utraty tury przez jednostkę, można rzucać czar tylko raz na turę - o ile bohater AI ma taką możliwość w ogóle)
  • przejście jednostki na inne pole
  • polecenie przejścia jednostki do obrony - jednostka traci turę, ale zwiększa się jej współczynnik obrony
  • ucieczka AI z pola bitwy (może być niemożliwa, zależnie od posiadanych artefaktów
  • poddanie się AI
  • zaatakowanie pieszo (ang. melee) jednostki wroga znajdującej się w zasięgu, z wybranego pola sąsiadującego
  • strzał do dowolnej jednostki (może być niemożliwy, nie każda jednostka strzela, stojąca koło jednostki wroga jednostka z reguły blokuje możliwość strzelania; każdy strzał zmniejsza liczbę pocisków jednostki, chyba, że na polu bitwy jest wóz z amunicją (Ammo Cart). Jednostka z liczbą pocisków równą zero nie może strzelać)
  • czekanie (jednostka będzie się ruszała na końcu tury, po wszystkich jednostkach z niższą inicjatywą)
  • rzucenie czaru przez jednostkę (nieliczne jednostki to potrafią, jest to jeszcze nieobsługiwane, ale powinno w końcu się pojawić)
  • leczenie innej jednostki (dla namiotu medyka)

Informacja o początku / końcu akcji

Każde polecenie wysłane przez AI do silnika jest analizowane pod kątem możliwości jego wykonania. Jeśli uzna, że polecenie jest wykonalne (czyli np. nie jest poleceniem rzucenia nieposiadanego czaru lub próbą ataku własną jednostką na inną własną jednostkę), przystępuje do jego wykonania. Wykonanie akcji zawsze zaczyna się od poinformowania o jej początku przez wywołanie void actionStarted(const BattleAction *action){}, następnie, jeśli jest taka potrzeba, następują wywołania o efektach akcji (np. jednostka ruszyła się na inne pole / zaatakowała jakąś inną / czar został rzucony), całość zaś kończy wywołanie void actionFinished(const BattleAction *action){} oznaczające, że wszystkie efekty związane z daną akcją zostały obsłużone.

Wywołania AI -> silnik

Obecnie zaimplementowane są następujące wywołania w callbacku:

Plik nagłówkowy

Dokumentacja wygenerowana Doxygenem

Umożliwiają one dostęp do każdej mechanicznie istotnej informacji o stanie gry, do jakiej dostęp ma AI. Część informacji zdobywana jest jednak w sposób pośredni, przez wywoływanie odpowiednich metod na obiektach dostarczanych przez wymienione metody. Pewne niezmienne w czasie elementy mechaniki sa także dostępne przez specjalny obiekt klasy VLC (dostępne dla AI po zainclude'owaniu pliku /lib/VCMI_Lib.h).

Czary bohaterów

Bohaterowie mogą rzucać w trakcie bitwy czarować. Aby rzucić zaklęcie konieczne jest jednak spełnienie następujących warunków:

  • Bohater musi posiadać księgę zaklęć oraz nie rzucił jeszcze w tej turze czaru. Aby sprawdzić, czy nasz bohater w danym momencie bitwy jest zdolny do czarowania, można posłużyć się metody bool CCallback::battleCanCastSpell().
  • Zaklęcie jest dostępne dla bohatera: ma je zapisane w księdze zaklęć bądź uzyskał w drodze bonusu (np. dzięki artefaktowi). Aby sprawdzić, czy konkretny czar jest dostępny dla bohatera, należy na nim wywołać metodę bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const. Wektor wszystkich czarów w grze jest dostępny np. jako VLC->spellh->spells
  • Bohater ma nie mniej punktów many niż wynosi koszt zaklęcia. Aby sprawdzić koszt rzucenia czaru, należy użyć metody int CCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const, podając jako argumenty wybrany czar i naszego bohatera-dowódcę. Liczba punktów many bohatera jest publicznie dostępnym polem klasy CGHeroInstance o nazwie mana
  • Czar można rzucić tylko, gdy nasza jednostka oczekuje na wykonanie akcji: musi się to odbyć po wywołaniu metody activeStack, a przed jej zwróceniem. Rzucenie czaru nie zwalnia od obowiązku zwrócenia akcji dla obecnego oddziału. [TODO: opisać synchronizację oraz przypadek utraty akcji, jeśli oddział zginie w efekcie rzuconego czaru.

Aby rzucić czar, należy wywołać metodę int CCallback::battleMakeAction(BattleAction* action), gdzie struktura BattleAction opisuje rzucony czar: typ akcji wynosi BattleAction::HERO_SPELL, pole additionalInfo zawiera ID czaru, pole destinationTile docelowy heks na który ma być rzucony czar (jeśli dotyczy). Pamiętać również trzeba o polu side — musi być ustawione zgodnie z naszą stroną (0 — atakujący, 1 — obrońca).

W grze obecnie zaimplementowane jest 48 z 59 czarów bitewnych dostępnych w oryginalnej grze. Listę zaimplementowanych czarów można znaleźć tutaj, a opisy działania czarów np. tutaj (wyświetla tylko czary magii powietrza; aby obejrzeć inne, trzeba wybrać inną opcję z menu na górze!) Wśród nich są czary zadające jednostkom wroga bezpośrednie obrażenia, zwiększające parametry jednostek AI, obniżające parametry wrogich jednostek lub specyficzne czary pozwalające np. przejąć kontrolę nad wrogą jednostką na pewien czas.

Koniec bitwy

Jeżeli jedna ze stron się podda, ucieknie, lub wszystkie jej jednostki poza maszynami bojowymi zostaną zabite, bitwa się kończy. Oba walczące AI dostają wywołanie void battleEnd(const BattleResult *br); Zawierające informacje o typie zwycięstwa, wygranej stronie, ofiarach, doświadczeniu zdobytym przez bohatera oraz przejętych artefaktach.

Uruchamianie bitwy

Uczestniczące programy

Bitwa rozgrywa się między dwoma AI. AI jest jednak biblioteką dynamiczną, jej samej nie da się uruchomić. Konieczna jest do tego aplikacja, która ją załaduje. Do przeprowadzenia bitwy potrzebna jest współpraca kilku aplikacji:

  • VCMI_server — serwer rozgrywki sterujący jej przebiegiem i logiką. Łączy się z runnerami, odbiera od nich żądania akcji i informuje je o ich skutkach.
  • VCMI_runner — aplikacja pośrednicząca między serwerem oraz AI. Runner przechowuje stan bitwy i aktualizuje go w oparciu o informacje otrzymywane z serwera. Jeden runner ładuje jedną bibliotekę AI, którą obsługuje (wywołuje jej call-iny oraz dostarcza interfejsu zwrotnego).
  • odpalarka — pomocniczy program, który odpala wszystkie powyższe aplikacje w odpowiedniej liczbie i z odpowiednimi argumentami.

Odpalarka

Parametry odpalarki

Aby uruchomić bitwę trzeba skorzystać z odpalarki. Przyjmuje ona argumenty określające:

  • plik JSON z parametrami bitwy. Domyślnie wykorzystywany jest plik b1.json z folderu z VCMI.
  • nazwy AI (lub pełne ścieżki do nich) uczestniczących w bitwie. Domyślnie dostępne jest StupidAI będące równoważne udostępnionej przykładowej implementacji AI.
  • folder, w którym maja być umieszczone logi
  • plik, do którego ma być dopisany rezultat bitwy

Składnia argumentów:

-h [ --help ]                         Display help and exit
-l [ --aiLeft ] arg (=StupidAI)       Left AI path
-r [ --aiRight ] arg (=StupidAI)      Right AI path
-b [ --battle ] arg (=b1.json)        Duel file path
-o [ --resultsOut ] arg (=./results.txt)
                                       Output file when results will be 
                                       appended
-d [ --logsDir ] arg (=.)             Directory where log files will be 
                                       created
-v [ --visualization ]                Runs a client to display a 
                                       visualization of battle

Przykładowa komenda (wydana w folderze VCMI):

odpalarka.exe -l"C:\Documents and Settings\Administrator\Pulpit\Programming challenge\YourAI\YourAI_Standalone.dll" -d../logs -o../logs/results_list.txt

Uruchomi bitwę, gdzie lewe AI będzie wczytane z podanej ścieżki, zaś logi trafią do fodleru nadrzędnego „logs”.


Plik JSON z bitwą

Plik opisujący bitwę zapisany jest w tekstowym formacie JSON (strona oficjalna). Ogólnie format JSON obsługuje:

  • obiekty — są to pary "nazwa" : wartość wewnątrz klamer {}.
  • tablice (wektory) — są to ciągi wartości w klamrach []
  • literały — liczby i stringi w cudzysłowach ""

Plik zawiera jeden obiekt o następujących polach:

  • "terType" — liczba, identyfikator terenu, na którym odbywa się bitwa. Jednostki znajdujące się na swoim ojczystym terenie otrzymują niewielkie premie.
  • "bfieldType" — liczba, typ pola bitwy. Nie ma znaczenia mechanicznego, określa grafikę w tle.
  • "obstacles" — wektor zawierający liczby lub dwuelementowe wektory liczb. Opisuje rozmieszczenie przeszkód. Pojedyncza liczba oznacza umieszczenie na danym polu pniaka (będzie on zablokowany). Pary mają postać [id_przeszkody, nr_pola] — przeszkoda danego typu zostanie umieszczona na wskazanej pozycji. Spis identyfikatorów przeszkód jest dostępny np. tu.
  • "sides" — dwuelementowy wektor zawierający struktury z opisem stron uczestniczących w bitwie.
  • "creatures" — wektor struktur pozwalających zmienić parametry danej jednostki na czas bitwy.

Struktura opisująca stronę walki zawiera następujące pola:

  • "side" — liczba, określa, która to jest strona (0 — lewa, 1 — prawa)
  • "army" — wektor par liczb postaci [id_stwora, liczebność]. Każda para opisuje jeden oddział. Liczba oddziałów musi być w przedziale [1—7].
  • "heroid" — liczba, identyfikator bohatera. Jeśli brak tego parametru, strona nie będzie miała bohatera.
  • "heroPrimSkills" — czteroelementowy wektor liczb, określających wartości kolejnych umiejętności pierwszorzędnych boahtera (atak, obrona, siła czarów, wiedza).
  • "spells" — wektor zawierający identyfikatory czarów znanych przez bohatera.

Struktura opisująca zmianę parametrów jednostki:

  • "id" — liczba określająca identyfikator jednostki, która otrzyma nowe parametry
  • "attack" — liczba, wartość punktów ataku stwora. Analogicznie: "defense" (obrona), "HP" (punkty wytrzymałości), "dmg" (zadawane obrażenia), "shoots" (strzały), "speed" (szybkość).


Wynik bitwy i logi

Logi

W wyniku przeprowadznia bitwy w folderze z logami pojawią się następujące pliki:

  • VCMI_Server_log.txt — log serwera. Można w nim m.in. sprawdzić, jakie PID dostały runnery obsługujące poszczególne AI (ctrl+f za „PID=”).
  • VCMI_runner_log_N.txt — dwa lub trzy takie pliki powstają, gdzie za N podstawiony jest PID programu. Odpowiadają runnerom obsługującym AI. Jeśli AI spowoduje błąd, logu należy szukać właśnie w zapisie runnera, który je wczytał.
  • first_runner.txt, second_runner.txt, third_runner.txt — zapisy standardowego wyjścia runnerów. Zwykle zawierają podzbiór ich logów, ale przy niektórych błędach mogą zawierać wskazówkę.
  • result.vdrst — binarny plik (nie do odczytu ludzkiego) z informacjami o rezultacie bitwy. Zawiera zserializowany obiekt BattleResult.
  • duel_log.vdat — binarny plik z zapisem przebiegu bitwy. Klient VCMI może go wykorzystać do wizualizacji bitwy.

Wyniki

Podany w argumencie odpalarki plik z wynikami po przeprowadzeniu bitwy otrzyma dodatkową linię. Będzie wyglądała podobnie do poniższej:

b1.json	C:\Documents and Settings\Administrator\Pulpit\Programming challenge\YourAI\YourAI_Standalone.dll	StupidAI	0	13622	SIDE_DEFEATED	Sat Dec 10 00:06:16 2011

Kolejne pola wyniku (oddzielone tabulacją) to:

  • nazwa pliku z bitwą
  • nazwa lewego (atakującego) AI
  • nazwa prawego (broniącego się) AI
  • kto wygrał: 0 oznacza lewego, 1 prawego
  • z jakimi stratami wygrał (im większa liczba, tym gorsze zwycięstwo)
  • przyczyna zakończenia bitwy. SIDE_DEFEATED oznacza normalne zwycięstwo (możliwa jest również dyskwalifikacja którejś ze stron)
  • czas bitwy

Przebieg rozgrywek i punktacja

Punkty będą przyznawane w trzech fazach. Max. do zdobycia: 8 pkt, przy czym:

  1. 0—5 pkt — za implementację konkretnych (wskazanych) strategii. AI będzie musiało stoczyć ciąg bitew, za każde zwycięstwo otrzymuje punkt.
  2. 0—3 pkt — za ogólną skuteczność AI. Moduł będzie musiał pokierować znaczną armią w bitwie, otrzyma punkty zależne od strat poniesionych przy zwycięstwie.
  3. 0—? pkt — po zakończeniu i ocenie zasadniczej części „Wyzwania” przeprowadzony zostanie turniej między najlepszymi AI. Te, które się wyróżnią (zakładając satysfakcjonujący ogólny poziom projektów) otrzymają dodatkowe punkty.

Spełnienie wymogów formalnych (#Wymogi_techniczne_i_formalne) jest niezbędne, aby program był wzięty pod uwagę w ocenianiu.


Strategie wymagane w pierwszej fazie

Zaatakuj pierwszy ("Pojedynek samurajów")

Często w bitwie przewagę zyskuje ten, kto zada pierwszy cios. Osłabiony atakiem przeciwnik kontratakuje mniej groźnie. AI powinno więc tak manewrować swoimi oddziałami, by te nie wchodziły w zasięg wrogich oddziałów i były zdolne do zadania uderzenia jako pierwsze. Pomocne może być użycie akcji WAIT, by wróg zdradził się ze swoimi zamiarami.

Zwinny strzelec ("Elf - kill the Dwarf!")

Nawet pojedynczy strzelec może pokonać wielekroć silniejszy oddział, jeżeli ma odpowiednią przewagę szybkości. Wystarczy biegać naokoło i strzelać, samemu pozostając w bezpiecznej odległości. Uwaga! Część pól może być zablokowana przez przeszkody, trzeba uważać, by się nie zapędzić w ślepy zaułek.

Przydatne czary:

  • Precision — zwiększa atak jednostki strzelającej w walce dystansowej.
  • Haste — zwiększa szybkość jednostki.
  • Slow — zmniejsza szybkość wroga.

Szarża na strzelców ("Kill bloody elves!")

Jeżeli wróg dysponuje grupą strzelców, należy działać roztropnie. Nie wolno przedwcześnie zanadto się zbliżyć, gdyż przy znacznej odległości strzały tracą połowę efektywności (p. funkcja callbacku battleHasDistancePenalty). Tu przydatny jest WAIT — niech wróg najpierw strzeli.

Gdy już się dotrze do wroga, należy możliwie efektywnie go „zablokować” (oddział strzelający traci tę zdolność, gdy stoi obok niego wróg, zaś walcząc wręcz zadaje połowę obrażeń i traci niektóre zdolności, takie jak wielokrotny atak). Jeśli jest kilka wrogich oddziałów, możliwe, że będziemy w stanie zablokować więcej niż jeden na raz. (Tylko czasem warto zablokować jeden silny zamiast dwu słabych...)

Pomocne też mogą być czary:

  • Air Shield — zmniejsza obrażenia otrzymywane w wyniku ostrzału
  • Forgetfulness — przeklęty oddział traci zdolność strzelania i musi walczyć wręcz (połowa efektywności)
  • Shield — zmniejsza obrażenia w walce wręcz. Do stosowania, gdy już się do wroga i zaangażuje w bezpośrednią walkę.

Czarodziej

Do przejścia tego testu wystarczy wspomaganie swoich oddziałów czarami i osłabianie nimi wrogów. Przydadzą się także zasady sterowania oddziałami z pozostałych scenariuszy taktycznych. Poza czarami wymienionymi w pozostałych taktykach, warto wesprzeć:

  • Bless/Curse — jednostka zadaje maksymalne/minimalne obrażenia. Efektywne względem jednostek o dużym „rozrzucie” zadawanych obrażeń.
  • czary ofensywne: magic arrow, ice bolt, lightening bolt, implosion — działają dość identycznie: zadają wrogowi obrażenia.

Mistrz taktyki

Najlepsi dowódcy potrafią tak ustawić swoje armie przed bitwą, aby wykorzystać nawet najsłabsze z nich. Zadaniem gracza jest chronienie mało wytrzymałego (ale silnego w ataku) strzelca przed atakiem w pierwszej turze. Strzelec zaś, gdy przyjdzie jego czas, powinien uderzyć najgroźniejszego wroga.

Wskazówki odnośnie drugiej fazy

Ogłoszone zostaną czary i umiejętności jednostek, którymi warto się zainteresować.

Trzecia faza — turniej

Po zakończeniu obu poprzednich faz, pojawią się tu szczegóły.

Wymogi techniczne i formalne

Celem zadania jest implementacja modułu bitewnego AI. Całość musi być napisana w języku C++. Wolno korzystać wyłącznie ze standardowej biblioteki języka, biblioteki silnika VCMI oraz dostępnego na serwerze student zbioru bibliotek Boost. Nie wolno wykorzystywać innych bibliotek, w tym systemowych.

Budowa

Folder ze źródłami powinien być zatytułowany NazwiskoImie. Do kodu źródłowego biblioteki musi być dołączony plik Makefile. Wydanie polecenia make w folderze z nazwiskiem ma stworzyć wewnątrz tego folderu plik NazwiskoImie.so. Budowanie musi odbywać się wewnątrz folderu (tzn. nie można tworzyć ani pisać do plików poza nim). Kod musi się kompilować bez żadnych dodatkowych zależności na serwerze student (GCC 4.3).

Ogólne wymogi

Modułowi AI nie wolno tworzyć procesów potomnych, tworzyć ani pisać do żadnych plików ani komunikować się z jakimikolwiek procesami. (Wyjąwszy, oczywiście, interfejsy udostępnianie przez silnik VCMI.) Modułowi AI nie wolno w żaden sposób zakłócać działania serwera rozgrywki ani procesu go hostującego. W szczególności nie wolno zakłócać pomiarów czasu i uzycia pamięci.

Limity czasowe nałożone na moduł:

  • 50 ms na stworzenie (konstruktor i metoda init ŁĄCZNIE)
  • 100 ms na inicjalizację (wykonanie metody battleStart)
  • 1000 ms (jedna sekunda) na przeprowadzenie fazy taktycznej.
  • 150 ms na podjęcie decyzji o ruchu (wykonanie metody activeStack)
  • 5 ms na odnotowanie wydarzenia w bitwie (wszystkie pozostałe metody interfejsu IBattleEventsReceiver)

Czas jest mierzony od wywołania procedury do jej „zwrócenia się”. Pomiar będzie dokonany na serwerze Student, tak więc trzeba się liczyć z jego szybkością. Na waszych komputerach AI może działać szybciej lub wolniej. Jednocześnie zwracam uwagę, że przesunięcie jednostki w fazie taktycznej na studencie trwa ok. 80 ms, zaś rzucenie czaru ok 40 ms. Pozostałe wywołania powinny być wyraźnie szybsze.

Limit dostępnej pamięci dla modułu wynosi 16 MB.

Postać wysyłanych projektów: Projekt musi być spakowany do postaci NazwiskoImie.tar.gz. W jego wnętrzu musi się znajdować jeden folder o nazwie NazwiskoImie zawierający źródła AI oraz plik Makefile. Aby program mógł zostać poprawnie oceniony, niezbędne jest spełnienie tych wymagań! Archiwum musi zostać wgrane przed upływem terminu na platformę Moodle.

Terminy

Czas na zadawanie pytań: 11 grudnia 2011. "Zerowy" termin: do końca 23 grudnia 2011. Pierwszy termin: do końca 30 grudnia 2011.

Planowany jest także drugi termin, w którym jednak liczba punktów do zdobycia będzie wyraźnie mniejsza. Ogłoszony zostanie po sprawdzeniu i ocenieniu programów z pierwszego terminu. Na drugi termin można ponownie przysłać poprawiony program z pierwszego, bez żadnego ryzyka utraty zdobytych punktów (będzie się liczył lepszy z wyników).

Materiały

Serwer Student

Po zalogowaniu się na serwer i przejściu do katalogu domowego, proszę wydać następujące polecenia:

wget http://vcmi.eu/pc/YourAI.zip
mkdir ~/challenge_out 
unzip YourAI.zip
cd YourAI/
make
chmod 700 run.sh
./run.sh
cat ~/challenge_out/results_1.txt | tail -1

Uwaga — można też całość postawić w podfolderze (nie w katalogu domowym), trzeba wtedy dostosować też skrypt run.sh.

Folder YourAI będzie zawierał źródła i plik Makefile rozwijanego AI. Komenda make buduje plik libYourAI.so. Aby go wypróbować, użyty jest skrypt run.sh, który uruchamia odpalarkę, podając w argumencie ścieżkę do pliku .so oraz do folderu z logami. Ostatnie poelcenie wypisuje ostatnią linię pliku z wynikami bitwy. Ma ona postać:

b1.json	StupidAI	/home/infsgrp/mwutow/YourAI/libYourAI.so	0	13622	SIDE_DEFEATED	Sun Dec  4 16:04:59 2011

por. opis pliku z wynikami.

Windows

Budowa

Aby zbudować AI na Windowsie z użyciem środowiska Visual studio należy:

https://vcmi.svn.sourceforge.net/svnroot/vcmi/branches/programmingChallenge/
  • otwieramy Visualem solucję VCMI_VS10.sln z podfolderu VCMI. Wybieramy konfigurację, jaką chcemy (Debug lub RD, czyli Release). Budujemy solucję. Czekamy chwilę — powinniśmy w końcu otrzymać informację o poprawnym zbudowaniu ośmiu projektów.
  • otwieramy Visualem solucję YourAI_Standalone.sln z podfolderu YourAI. Budujemy, powinno przejść poprawnie.
  • voila! Powinniśmy w podfolderze YourAI uzyskać plik YourAI_Standalone.dll. Jest to biblioteka z AI, którą można już uruchomić odpalarce.

Uruchamianie

Wykonanie komendy F5 (Start Debugging) uruchomi odpalarkę, a ta bitwę. We właściwościach projektu w karcie „Debugging” w polu „Command Arguments” znajdują się parametry, z jakimi zostanie odpalona. Domyślnie za lewe AI zostanie wzięty wasz projekt, zaś wyniki trafią do nadrzędnego folderu logs.

Więcej o parametrach odpalarki i odczytywaniu wyników bitwy w sekcji #Uruchamianie_bitwy.

UWAGA! Żeby AI poprawnie działało, musi być zbudowane w tej samej konfiguracji (Debug lub RD) co solucja VCMI_VS10! Ich pomieszanie może powodować dziwnie wyglądające błędy, prowadzące najczęściej do dyskwalifikacji AI.

„Własny” Linux

Nie polecam, bo na różnych dystrybucjach się może zachowywać nieprzewidywalnie.

mkdir vcmi
cd vcmi
wget http://sourceforge.net/apps/trac/vcmi/export/2480/branches/programmingChallenge/vcmiinstall.sh
chmod 700 vcmiinstall.sh
./vcmiinstall.sh --install lean

Dalej postępować jak w instrukcji dla studenta. Konieczne są tylko dostosowania w pliku Makefile i run.sh (poprawić ścieżki).

Potrzebne pakiety:

  • libtool
  • automake
  • autoconf

Biblioteki:

  • libstdc++ devel
  • SDL and SDL-devel
  • SDL_mixer and SDL_mixer-devel
  • SDL_image and SDL_image-devel
  • SDL_ttf and SDL_ttf-devel
  • zlib and zlib-devel
  • the ffmpeg libraries (libavformat and libswscale). Their name could be libavformat-devel and libswscale-devel, or ffmpeg-libs-devel or similar names.
  • boost c++ libraries v1.36+ (1.35 will not work) (www.boost.org):
    • program-options
    • filesystem
    • iostreams
    • system
    • thread

FAQ

  • Co to są te fazy? I czy te strategie jakie zastosuję (którą) to wybieram ja w AI czy gra?

Faza to tylko pomocniczy termin opisujący część procesu oceniania (testowania). AI będzie musiało stoczyć sześć lub siedem bitew. Są one kompletnie niezależne od siebie.

Pierwsze pięć bitew to będą dość proste układy, dla których istnieje wyraźna strategia wygrywająca (jedna lub więcej). Naiwna strategia prowadzi do porażki, sprytna do łatwego zwycięstwa. Strategie są opisane na wiki, AI musi „znać” je wszystkie i potrafić ocenić, którą należy stosować (przy czym generalnie one są w znacznej mierze niewykluczające wzajemnie). Za zwycięstwo w każdej z tych pięciu bitew AI otrzymuje po punkcie. Za porażkę — nic. Rozegranie tych pięciu bitew to pierwsza faza testów.

Druga faza zawiera tylko jedną, szóstą z kolei bitwę. Składa się na nią duży, skomplikowany układ. Dwie liczne armie stają naprzeciw siebie. Oceniany jest tutaj nie tylko sam fakt zwycięstwa, ale także poniesione straty. Jeśli AI rozgromi wroga, otrzyma trzy punkty. Jeśli wygra ledwo-ledwo — jeden punkt.

Trzecia faza to turniej między najlepszymi AI. Przebieg tych rozgrywek zostanie określony, gdy już będzie wiadomo, ile tych AI. Odbędzie się po ocenie pozostałych bitew, a możliwe, że nawet po drugim terminie wyzwania.


  • Czy działanie czaru jednostka odczuwa w trakcie jednej tury, czy może on trwać dłużej?

Czary możemy generalnie podzielić na efekty natychmiastowe oraz długotrwałe uroki. Czar natychmiastowy, to np. błyskawica — we wroga uderza grom z jasnego nieba, niosący obrażenia. Nie ma tu mowy o trwaniu. Natomiast jeśli idzie o uroki (czary typu spowolnienie, błogosławieństwo, itp.) to trwają one tyle tur, ile wynosi siła czarów bohatera. Wartość siły czarów bohatera można sprawdzić instrukcją:

if(const CGHeroInstance *h = cb->battleGetFightingHero(side)) //mamy bohatera
   h->getPrimSkillLevel(PrimarySkill::SPELL_POWER) //pytamy o jego siłę czarów


  • Czy istnieje łatwa metoda na wizualizację bitwy na żywo, lub na podstawie logów?

Tak, wizualizację bitwy można uruchomić dodając do parametrów odpalarki -v. Będzie działać pod warunkiem wgrania archiwów z grafikami. Rozegraną już bitwę także można zwizualizować, czemu służy plik duel_log.vdat. Umieśćcie go (pod jakąkolwiek nazwą) w folderze z odpalarką i wydajcie polecenie

VCMI_client.exe -rbduel_log.vdat


  • Czy jednostki są rozstawiane na pozycjach początkowych automatycznie?

Tak. Gdy AI otrzymuje wywołanie battleStart, każdy z oddziałów stoi już na swoim początkowym polu (p. CStack::position).


  • Jakie jednostki są dostępne dla każdego przypadku?

Nie jest to określone. AI nie powinna się nastawiać na żadne konkretne jednostki, a raczej samodzielnie je klasyfikować.


  • Czy można korzystać ze specjalnych umiejętności jednostek?

Tak. O ile działają. :P Inna sprawa, że akurat nieszczególnie warto sobie tym zaprzątać głowę, raczej za dużo tych umiejętności się nie pojawi. Wiele z nich zresztą funkcjonuje bez żadnej ingerencji ze strony AI (podwójny atak np. sam się wykonuje podwójnie, AI nie ma zlecać drugiej akcji).


  • Co w przypadku, gdy wszyscy zginą?

Zupełnie wszyscy na polu bitwy? Wtedy jest remis, AI nie otrzymuje punktów (wymagane jest zwycięstwo). Zresztą, gdy wszyscy już zginą, to co taka sytuacja obchodzi AI? Po wszystkim przecież. Inna sprawa, że taka sytuacja raczej się nie zdarzy, może być jedynie skutkiem masowych czarów ofensywnych, które raczej nie pojawią się w zestawach testowych.


  • Czy są brane pod uwagę artefakty?

Są, ale AI to w ogóle nie powinno się tym interesować. Wszystkie premie pochodzące z artefaktów są widoczne u bohatera i jego oddziałów.


Przykładowo, jeśli bohater ma czarodziejski łuk, który sprawia że strzelcy nie ponoszą kary za odległość strzału, to AI nie musi sprawdzać listy artefaktów bohatera. Premia ta przechodzi na bohatera i wszystkie jego oddziały. Tak więc wywołanie CBattleInfoCallback::battleHasDistancePenalty da zawsze poprawną odpowiedź, uwzględniającą artefakty. Podobnie jest z innymi efektami artefaktów.


  • Czy walczyć będziemy z przeciwnikiem sterowanym StupidAI?

Nie wykluczam jakichś drobnych poprawek, ale zasadniczo tak. Wróg będzie głupi, jego siła będzie tkwić... w sile.


  • Czy dostaniemy pliki *.json bitew, które są naszymi zadaniami?

Dostępne są przykładowe bitwy, którymi warto się zainteresować: http://vcmi.eu/pc/battles/ Nie są to jednak bitwy identyczne z właściwymi testami — może się pojawić więcej jednostek (np. dwaj wrodzy strzelcy, z czego jeden słabowity i dla zmyłki) lub nieco innych typów.


  • Czy jest gdzieś dostępny opis tworzenia pliku bitwy?

Opis został dodany.


  • Jakie czary będą dostępne?

Wystarczające do zdobycia punktów będzie wykorzystanie czarów wymienionych w opisie strategii. Nie znaczy to, że pojawią się wszystkie te czary, ani że pojawią się tylko te czary.


  • Czy można wybrać bohatera (specjalność, umiejętności) czy jest z góry określony?

Nie można. Bohater jest określony w pliku z bitwą.


  • Czy bohater będzie posiadał machiny wojenne, a jeśli tak, to jakie?

Nie będzie posiadał.


  • Walka samurajów (kto pierwszy zada cios): wysterczy przecież poczekać aż podejdzie rywal i zadać mu cios?

Jednostka może odłożyć ruch na później (wait, raz w turze) lub zrezygnować z ruchu (defend). Strategia właśnie na tym polega — trzeba zaczekać aż wróg podejdzie i wtedy zadać cios.


  • Czy jest możliwość sprawdzenia czy wróg jest już pod wpływem zaklęcia (np. przed rzuceniem następnego) ?

Tak. Generalnie interesują nas dwie klasy: CSpell opisuje zaklęcie z punktu widzenia mechaniki. Jest globalnie dostępny wektor VLC->spellh->spells indeksowany po ID zaklęcia (p. namespace Spells) i można z jego pomocą sprawdzać parametry czaru o danym ID. Bonus to klasa... bardzo uniwersalna. W szczególności realizuje „bycie pod wpływem czaru”.

Pobieranie czarów, pod których wpływem jest jednostka można zrobic na dwa sposoby. Prostszy — same ID:

tlog0 << "Active spell IDs: ";
BOOST_FOREACH(si32 spellID, firstEnemy->activeSpells())
  tlog0 << spellID << " ";
tlog0 << std::endl;

Sprytniejszy — ID oraz informacje dodatkowe:

tlog0 << "Active spells:\n";
BOOST_FOREACH(const Bonus *b, *firstEnemy->getSpellBonuses())
{
   const CSpell *spellInfo = VLC->spellh->spells[b->sid];
   tlog0 << "Name=" << spellInfo->name << "\tID=" << b->sid <<"\tTurns remaining: " << b->turnsRemain << "\tPositive=" << (int)spellInfo->positiveness << std::endl;
}
tlog0 << std::endl;


  • battleGetAvailableHexes (const CStack *stack, bool addOccupiable, std::vector< THex > *attackable=NULL) - czym jest drugi i trzeci argument

a) stack — oddział, dla którego dostępność pól jest liczona b) addOccupiable — czy dodać pola „zajmowalne”. Twój oddział może mieć dwa heksy szerokości. Jego pozycją jest pole „ku centrum” (prawe dla atakującego, lewe dla obrońcy). To drugie pole, na którym stoi tył jednostki, nazywane jest polem zajętym (zwraca je metoda occupiedHex). Jeżeli parametr addOccupiable jest ustawiony, to funkcja zwróci nie tylko pola, na których może stanąć przód jednostki, ale też pola, na których może się znaleźć jej tył. Jeżeli stack zajmuje jeden hex, wartość tego argumentu jest bez znaczenia. Aby sprawdzić, czy jednostka zajmuje dwa heksy można użyć wywołania stack->doubleWide() (zwraca prawdę dla dwuheksowców). c) wskaźnik na wektor pól. Jeżeli jest ustawiony (wołający musi taki wektor zapewnić), to funkcja wypełni go polami, które mogą być celami ataku. Są to pola, na których stoją wrogie jednostki (lub przynajmniej ich tyły), które przy tym znajdują się w zasięgu ruchu lub przylegają do niego (tak, że poprawna jest akcja WALK_AND_ATTACK). Jeżeli stack potrafi strzelać, to w tym wektorze umieszczone zostaną wszystkie pozycje wrogich jednostek (strzelać można na dowolną odległość).


  • battleGetDistances (const CStack *stack, THex hex=THex::INVALID, THex *predecessors=NULL) - co zwraca?

Funkcja wykonuje BFS na polu bitwy — celem jest określenie odległości poszczególnych pól od naszego oddziału. Zwraca wektor liczący tyle elementów, ile jest pól. Tak więc battleGetDistances(stack)[70] to liczba pól, jakie oddział musi przejść, by dojść do heksa nr 70. Jeżeli odległość wynosi -1 to znaczy, że się obecnie nie da dojść na dane pole. Dwa opcjonalne parametry to heks, z którego nalezy rozpocząć poszukiwania (jesli inny od pozycji stacku) oraz wskaźnik na tablicę heksów (predecessors). Jeśli ten wskaźnik jest ustawiony, to musi pokazywć na tablicę obiektów THex liczącą przynajmniej BFIELD_SIZE elementów. Zostanie ona wypełniona poprzednikami, tj. predecessors[70] zawierać będzie nr poprzedniego heksa, który był odwiedzony przez BFS. W funkcji jest błąd — jej drugi parametr jest w istocie ignorowany! Jeśli chcecie liczyć odległości z heksa innego niż pozycja jednostki wykorzystajcie funkcję battleGetDistancesFromHex, która zachowuje się zgodnie z opisem wyżej (to takie battleGetDistances bez buga)!


  • Jakie współczynniki bierze pod uwagę battleEstimateDamage? Czy tylko obrażenia, czy również obronę, atak (w tym ich różnicę z odpowiednim do jej wartości współczynnikiem), coś jeszcze?

Wszystkie wymienione współczynniki są uwzględnione. Funkcja w ogóle bierze pod uwagę wszystko za wyjątkiem efektów losowych lub zależnych od komendy. NIE uwzględnia: - premii za szarżę (5% za każde pole przejechane w drodze do ataku dla staków z hasBonusOfType(Bonus::JOUSTING) ), - szczęścia — i tak wyłączone w wyzwaniu, - losowo działających zdolności jednostek, jak np. death blow rycerza śmierci (szansa na podwójne obrażenia) — i tak wyłączone w wyzwaniu, - szansy na podwójne obrażenia balisty — nie dość, że wyłączone, to i tak nie dostaniecie balisty.


  • Czy jest jakaś funkcja, dzięki której automatycznie możemy się dowiedzieć, którą z kolei turę walki rozgrywamy?

Nie, ale na początku każdej tury walki AI otrzymuje jej numer w argumencie funkcji battleNewRound.


  • W jaki sposob sprawdzic, czy CStack moze czekac, czy musi sie ruszyc?

CStack może czekać tylko raz w turze. Aby sprawdzić, czy już czekał, można użyć konstrukcji:

if(vstd::contains(stack->state, WAITING))
   ...; //juz czekal, nie moze ponownie
else
   ...; //moze zaczekac jeszcze raz


  • Jak najlepiej wyciagnac liste dostepnych czarow? Iteracja battleCanCastThisSpell() po wszystkich czarach?

Można tak, to jest najpewniejsza metoda — battleCanCastThisSpell sprawdza szereg warunków okolicznościowych, mogących zablokwoać rzucanie czaru (np. specjalny teren pola bitwy, artefakty blokujące magię lub jej część, itp.) lub umożliwiających rzucanie mimo braku znajomości czaru. Jeśli komuś bardzo zależy na szybkości, to można sprawdzać tylko czary o ID ze zbioru hero->spells (to „własna” księga czarów bohatera), choć w pewnych specyficznych przypadkach bohater może rzucać czary spoza tej listy (np. gdy ma artefakt typu Tome of Air).


  • Czy da sie estymowac obrazenia zadawane przez czary?

Dodałem do callbacku metodę battleEstimateSpellDamage. Oprócz czarujacego bohatera i czaru, może przyjąć jako trzeci argument (opcjonalny) oddział, w który czarem chcemy rąbnąć (sprawdzi wtedy jego odporności). Można jej używać wg wzorca:

int dmg = cb->battleEstimateSpellDamage(cb->battleGetFightingHero(side), VLC->spellh->spells[Spells::MAGIC_ARROW]);

Metoda zwraca liczbę punktów obrażeń, jakie czar zapewne by wyrządził. Nie sprawdza jednak, czy bohater może rzucić ten czar.