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

From VCMI Project Wiki
Jump to: navigation, search
(dodanie siatki pola bitwy, formatowanie, link do czarów)
(Wywołania AI -> silnik: napisanie wywołań)
Line 96: Line 96:
 
== 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:
  
TBD
+
int battleGetBattlefieldType(); //  1. sand/shore  2. sand/mesas  3. dirt/birches  4. dirt/hills  5. dirt/pines  6. grass/hills  7. grass/pines  8. lava  9. magic plains  10. snow/mountains  11. snow/trees  12. subterranean  13. swamp/trees  14. fiery fields  15. rock lands  16. magic clouds  17. lucid pools  18. holy ground  19. clover field  20. evil fog  21. "favourable winds" text on magic plains background  22. cursed ground  23. rough  24. ship to ship  25. ship
 +
int battleGetObstaclesAtTile(int tile); //returns bitfield
 +
std::vector<CObstacleInstance> battleGetAllObstacles(); //returns all obstacles on the battlefield
 +
const CStack * battleGetStackByID(int ID, bool onlyAlive = true); //returns stack info by given ID
 +
const CStack * battleGetStackByPos(int pos, bool onlyAlive = true); //returns stack info by given pos
 +
int battleGetPos(int stack); //returns position (tile ID) of stack
 +
int battleMakeAction(BattleAction* action);//for casting spells by hero - DO NOT use it for moving active stack
 +
std::map<int, CStack> battleGetStacks(); //returns stacks on battlefield
 +
void getStackQueue( std::vector<const CStack *> &out, int howMany ); //returns vector of stack in order of their move sequence
 +
std::vector<int> battleGetAvailableHexes(int ID, bool addOccupiable); //reutrns numbers of hexes reachable by creature with id ID
 +
bool battleCanShoot(int ID, int dest); //returns true if unit with id ID can shoot to dest
 +
bool battleCanCastSpell(); //returns true, if caller can cast a spell
 +
bool battleCanFlee(); //returns true if caller can flee from the battle
 +
const CGTownInstance * battleGetDefendedTown(); //returns defended town if current battle is a siege, NULL instead
 +
ui8 battleGetWallState(int partOfWall); //for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
 +
int battleGetWallUnderHex(int hex); //returns part of destructible wall / gate / keep under given hex or -1 if not found
 +
std::pair<ui32, ui32> battleEstimateDamage(int attackerID, int defenderID); //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
 +
ui8 battleGetSiegeLevel(); //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle
 +
const CGHeroInstance * battleGetFightingHero(ui8 side) const; //returns hero corresponding ot given side (0 - attacker, 1 - defender)
 +
si8 battleHasDistancePenalty(int stackID, int destHex); //checks if given stack has distance penalty
 +
si8 battleHasWallPenalty(int stackID, int destHex); //checks if given stack has wall penalty
 +
si8 battleCanTeleportTo(int stackID, int destHex, int telportLevel); //checks if teleportation of given stack to given position can take place
 +
 
 +
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 ==

Revision as of 19:59, 8 December 2010

Przebieg bitwy - jak to wygląda od strony AI

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:

  • CGlobalAI — główna klasa AI musi implementować ten interfejs. Silnik gry wywołuje jego metody, by informować AI o wydarzeniach w grze bądź by zapytać, jaką akcję chce podjąć.
  • ICallback — 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.

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

  • void GetAiName(char* name); — powinno wypisać nazwę AI
  • CGlobalAI* GetNewAI(); — powinno stworzyć nowy obiekt głównej klasy AI (dziedziczącej po CGlobalAI), które pokieruje nadchodzącą bitwą. 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 ICallback, poprzez który AI może „odpytywać silnik”.

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.

Przebieg tury

Każda tura zaczyna się od dwóch 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, dla każdej jednostki w kolejności wyliczonej inicjatywy, może nastąpić następująca sytuacja:

  • 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 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) 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)
  • 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

TBD

Wywołania AI -> silnik

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

int battleGetBattlefieldType(); //   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines   8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields   15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog   21. "favourable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
int battleGetObstaclesAtTile(int tile); //returns bitfield
std::vector<CObstacleInstance> battleGetAllObstacles(); //returns all obstacles on the battlefield
const CStack * battleGetStackByID(int ID, bool onlyAlive = true); //returns stack info by given ID
const CStack * battleGetStackByPos(int pos, bool onlyAlive = true); //returns stack info by given pos
int battleGetPos(int stack); //returns position (tile ID) of stack
int battleMakeAction(BattleAction* action);//for casting spells by hero - DO NOT use it for moving active stack
std::map<int, CStack> battleGetStacks(); //returns stacks on battlefield
void getStackQueue( std::vector<const CStack *> &out, int howMany ); //returns vector of stack in order of their move sequence
std::vector<int> battleGetAvailableHexes(int ID, bool addOccupiable); //reutrns numbers of hexes reachable by creature with id ID
bool battleCanShoot(int ID, int dest); //returns true if unit with id ID can shoot to dest
bool battleCanCastSpell(); //returns true, if caller can cast a spell
bool battleCanFlee(); //returns true if caller can flee from the battle
const CGTownInstance * battleGetDefendedTown(); //returns defended town if current battle is a siege, NULL instead
ui8 battleGetWallState(int partOfWall); //for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
int battleGetWallUnderHex(int hex); //returns part of destructible wall / gate / keep under given hex or -1 if not found
std::pair<ui32, ui32> battleEstimateDamage(int attackerID, int defenderID); //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
ui8 battleGetSiegeLevel(); //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle
const CGHeroInstance * battleGetFightingHero(ui8 side) const; //returns hero corresponding ot given side (0 - attacker, 1 - defender)
si8 battleHasDistancePenalty(int stackID, int destHex); //checks if given stack has distance penalty
si8 battleHasWallPenalty(int stackID, int destHex); //checks if given stack has wall penalty
si8 battleCanTeleportTo(int stackID, int destHex, int telportLevel); //checks if teleportation of given stack to given position can take place

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

W grze na obecną chwilę 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.