Dokumentace k zápočtovému programu

Ondřej Dušek, I - 46, 2004/2005


Obsah

  1. Funkce programu
    1. Stručný popis
    2. Hra - systém
    3. Možnosti nastavení
  2. Ovládání
    1. Ovládání hry
    2. Editor konfigurace
  3. Struktura kódu, rozdělení do unit
  4. Funkce programu, algoritmy
    1. Datové objekty
    2. Práce s grafikou
    3. Spolupráce s uživatelem
    4. Umělá "inteligence"
    5. Hlavní řídící objekt
  5. Závěr

1. Funkce programu

1.1 Stručný popis

Už podle názvu "Bitva u Stalingradu" by se dalo čekat, že jde o hru. V tomto případě tedy konkrétně o vcelku jednoduchou tahovou strategii na hexech, kdy úkolem hráče je prosté zničení všech jednotek nepřítele - počítače. Hra obsahuje i konfigurační editor, ve kterém lze nastavit vlastnosti obou armád, jednotlivých druhů jednotek i herního pole.

1.2 Hra - systém

Pravidla hry nejsou složitá. Vše se odehrává na poli 15 x 12 hexů, kde některá políčka mohou být pro jednotky neprůchozí. Hráč i počítač mají armády, sestávající z 1 -  5 oddílů. Každý obsahuje 1 - 255 jednotek jednoho druhu. Všechny druhy jednotek mají specifické vlastnosti (nastavitelné v editoru konfigurace), které ovlivňují jejich chování. Jsou to obrana, útok, útok na dálku a hit points, určující sílu v boji a dojezd, omezující možnost pohybu.

Pořadí tahů jednotlivých jednotek je určováno právě dojezdem, tedy "nejrychlejší" jednotky jsou na tahu nejdřív. Při útoku se jeho síla spočítá jako součin síly útoku daného druhu jednotky a počtu jednotek v oddílu. Po vydělení obranou cíle (a vynásobení dvěma, jinak by útoky neměly dostatečný efekt) dostaneme "poškození" cíle, které se odečte od jeho aktuálních hit pointů. Pokud je poškození větší než počet hit pointů cíle, je jedna (nebo i více) jednotek v oddílu zničeno.

Většina jednotek nemá vlastnost "útok na dálku", takže před útokem se musí k cíli dostat - alespoň jedno z políček sousedících s cílem jim musí být dostupné. Jednotkám, které možnost útoku na dálku mají (dělostřelectvo), nezáleží na pozici cíle - mohou útočit na všechny nepřátelské jednotky na herním poli, jejich vzdálenost sílu útoku nijak neovlivňuje.

1.3 Možnosti nastavení

V konfiguračním editoru je možné nastavit většinu vlastností hry. (Snad jediné, co nastavit nejde, jsou názvy většiny souborů s grafikou, kromě obrázků jednotek). Zvolit se dá rozložení armád - počty jednotek v oddílech, a to buď manuálně zadáním každého jednotlivého oddílu, nebo je možné nechat na počítači a pseudonáhodě, kdy jsou automaticky vybrány armády podle volby rovnocenné, přesila nebo oslabení. Vyváženost obou armád je ale jen přibližná.

Navolit jdou také vlastnosti jednotlivých druhů jednotek - útok a obrana v rozsahu 1 - 50, hit points 1 - 100 a dojezd 1 - 28 (Což je tak dvakrát více než je skutečně potřeba). Změnit jde také jejich názvy a umístění souborů s jejich obrázky).

Další možností volby je určení, která políčka na herním poli jsou neprůchozí (Nastavil jsem je tak, aby odpovídala grafice). Poslední volbou je prohození armád hráče a počítače - změní se i názvy a vlastnosti jednotek, i grafika i rozložení armád.


2. Ovládání

Po spuštění programu (main) se zobrazí hlavní menu. Uživatel má na výběr "Hra" - spuštění hry, "Volby" - editor konfigurace a "Konec" - ukončení programu. Výběr se provádí šipkami a klávesou Enter.

2.1 Ovládání hry

Samotná hra se kompletně ovládá myší. Po načtení veškeré grafiky do paměti, což může několik vteřin trvat, se zobrazí herní pole. Jednotka, která je na tahu, má modře zvýrazněna všechna přístupná políčka. (Je to jediná jednotka, jejíž vlastní políčko je přístupné). Pokud je na tahu počítač, kurzor myši se změní na přesýpací hodiny, dokud počítač neprovede svůj tah.

Když přijde na řadu jednotka hráče, kurzor se změní na šipku a dál se mění podle toho, nad jakým políčkem se právě nachází a jakou akci je zde možné provést. Pokud se jednotka může na určité políčko přesunout, zobrazí se zaměřovací kříž. Jestliže se myš nachází nad nějakou jednotkou (i nepřátelskou), zobrazí se v dolní části obrazovky její vlastnosti - počet, aktuální hit points a podobně. Informace o prováděných tazích se vypisují do řádku ve spodním dílu obrazovky.

Pokud je možné útočit na nepřátelskou jednotku na poli pod pozicí myši, kurzor dostane tvar meče. Pro útok jednotkou, která nemá vlastnost "útok na dálku" je navíc nutné zvolit, ze které strany se bude vést útok - stačí najet myší ze směru, ze kterého chci vést útok (Kurzor - "meč" - mění svůj směr podle aktuálního možného směru útoku).

Jestiže se myš nachází nad místem, se kterým nejde nic provést, změní se kurzor zpět na šipku. Jedinou výjimkou z tohoto je malé "x" v rohu obrazovky - po kliknutí do tohoto místa se hra ukončí. Hru lze ukončit i z klávesnice - stisknutím "k". Ukončením hry (buď z vůle uživatele, nebo po vítězství jedné ze stran) se program vrátí do hlavního menu.

2.2 Editor konfigurace

Editor konfigurace je vlastně systém různých menu, ve kterých se nastavují jednotlivé položky. Všechna se ovládají klávesnicí - kurzorovými klávesami (pohyb při výběru z několika možností) a klávesou Enter (pro potvrzení výběru), některé parametry se zadávají přímo, do okna pro vstup textu (např. síla útoku a obrany jednotl. jednotek), a očekává se korektní vstup - po nesprávném zadání (např. text místo čísla) je daný parametr nastaven na nejnižší možnou hodnotu a zobrazí se hlášení o chybě, které je nutné potvrdit stisknutím "Enter".

Na výběr je v hlavním menu náhodné nastavení armád , které změní údaje o počtu oddílů obou stran a počtu jednotek v oddílech. Dále manuální nastavení armád hráče i počítače, pod položkami Vlastnosti jednotek hráč ,resp. počítač . Další možností je určení vlastností jednotlivých druhů jednotek - Vlastnosti druhů hráč, počítač . Po vybrání této volby se zobrazí další menu pro nastavení útoku, obrany, dálkového útoku, dojezdu a hit points zvoleného druhu jednotek.

Položka Neprůchozí políčka označuje menu pro nastavení vlastností herního pole - je zde možné nastavit až 24 neprůchozích políček. (Toto je ale spíše vázané na grafický soubor s jednotlivými políčky, takže nedoporučuji toto nastavení měnit, pokud zároveň neměníte grafiku. Je nastaven tak, že neprůchozí objekty - stromy, rozvaliny apod. jsou skutečně pro jednotky neprůchozí. Položka přesah určuje, o kolik jsou jednotlivá neprůchozí políčka "vyšší" než ostatní, takže např. strom může přesahovat do vyšší řady políček - a zakrývat jednotky v ní stojící.)

Další možností výběru je Soubory grafiky , kde lze specifikovat cestu k souborům s grafikou jednotek hráče a počítače. Ty ale musí být ve stejném formátu jako původní. Poslední volbou je Prohodit strany - položka mluvící sama za sebe. Prohodí všechny druhy jednotek, nastavení armád i grafických souborů. Konec ukončí editor, přičemž se zeptá na uložení zadaných voleb.


3. Struktura kódu, rozdělení do unit

Celý kód programu sestává celkem ze 7 hlavních souborů, plus třech dalších unit, zajišťujících podpůrné funkce. Z většiny (krom podpůrných souborů) jsem se snažil napsat ho objektově. Hlavní program se nachází v souboru main.pas - ten obsahuje jen inicializaci grafiky a zobrazení hlavního menu, které zavolá hru, editor konfigurace, nebo program ukončí.

Veškeré podpůrné funkce jsou v těchto souborech : menu.pas - Stará se o zobrazení všech menu, rámečků, nápisů a podobně. Tuto knihovnu jsem napsal již před pěti lety a pro program jí jen znovu použil (přidal jsem jen procedury zobrazení prázdného rámečku a textu bez rámečku a opravil jednu menší chybu) - podle toho taky vypadá její kód - implementation neobsahuje komentáře a celý kód je dost nepřehledný, názvy proměnných volené nahodile, nesmyslný boolean parametr procedur atd. atd. Jedinou předností této knihovny je, že přes to všechno doopravdy funguje. Soubor bitmaps.pas obsahuje procedury pro práci s bitmapami - načtení bitmapy ze souboru, zobrazení přímo ze souboru na obrazovku, nebo z paměti, zobrazení s transparentní barvou, popř. zobrazení jen části obrázku. mouse.pas zařizuje veškerou spolupráci s myší - zobrazení, zjišťování pozice a změny kurzorů.

Základní objekty, které jsou ostatními využívány, jsou zahrnuty v souboru data.pas. Jedná se hlavně o objekty zajišťující práci s daty - vlastnostmi armád, herním polem a podobně. Soubor také obsahuje všechny konstanty, v programu používané (vyjma konstant, užívaných konfiguračním editorem pro umístění menu na obrazovce a nejčastější druhy menu). Krom toho v něm jsou objekty pro práci s frontou, řízení bitvy - výpočet výsledků útoku a určení dalšího tahu, a jednoduchý generátor náhodných druhů jednotek, používaný konfiguračním editorem pro náhodné nastavení armád.

Veškerá spoupráce (hry, ne editoru konfigurace) s obrazovkou se děje přes objekt T_Screen, obsažený v souboru display.pas. To zahrnuje funkce, zajišťující zobrazení záhlaví, herního pole, jednotlivých políček, přístupných políček, "animace" pohybu a útoku jednotek (pro 3 obrázky/jednotka je to možná až moc odvážné označení), dále informací o útocích a pohybu jednotek a zobrazení informací o jednotce. Využívá služeb knihoven data, bitmaps a menu.

Unita hrac_inp.pas se stará o spolupráci s uživatelem - tedy zjištění, kam si hráč přeje provést tah, popř. ukončení hry. K tomu volá z unity display zobrazení informací o jednotce, nad níž se právě nachází kurzor myši. Používá unity data, display, mouse a bitmaps, protože pro zjištění pozice myši na hexech a směru aktuálního útoku používá do paměti ukládané bitmapy, které obsahují masku hexu a směrů útoku v různých barvách - dotazem na konkrétní byte obrázku zjistí vše potřebné. Možná to není nejrychlejší způsob, ale jakékoliv početní vyjadřování tvaru hexu - který pixel do něj ještě patří a který ne - mi přišlo příliš složité.

Knihovna pc_ai.pas představuje umělou inteligenci počítače - zajišťuje rozhodnutí o příštím tahu. Používá data.

Celou hru zastřešuje objekt T_Hra, obsažený v souboru game.pas. Ten provádí inicializaci celé hry a vydává pokyny ostatním objektům - řídí běh celé hry - sled jednotlivých tahů a provádění útoků, kdy přímo ukládá výsledky do dat jednotek. Vydává pokyny ke zjištění příštího tahu hráče i počítače a vyvolává také procedury zobrazení přístupných políček, aktuálních informací a animace. Navíc zjišťuje aktuální počet (dosud živých) jednotek obou stran a rozhoduje tak o vítězství jedné z nich. Využívá jednotky data, hrac_input, pc_ai a display

Posledním souborem kódu je config.pas, obsahující celý konfigurační editor, tedy i přístup k datům, i zobrazení a systém menu. Pro svou funkci potřebuje unitu data, ze které používá datový formát načtení položek ze souborů a generátor náhodných druhů jednotek. K zobrazení mu slouží knihovna menu.


4. Funkce programu, algoritmy

4.1 Datové objekty

V unitě data se nachází veškeré datové typy i objekty pro práci s nimi.

Pro uložení vlastností armád se používá datový typ T_vlastnosti_army, který obsahuje potřebné informace o jednom oddílu. Typ T_vlastnosti_unit slouží k uložení informací o atributech jednoho druhu jednotek. Data tohoto typu v defaultních proměnných Army_hrac a Army_pc obsahuje objekt T_Army_Data, kde je uchováván stav armád po celý běh hry. Přístup k položkám je řešen přímo - jsou viditelné i pro ostatní objekty. Jedinou metodou tohoto objektu je Read_Config, tedy načtení všech údajů z disku (ta je totiž používaná jak hrou, tak konfigurátorem; zápis do souboru provádí jen konfigurátor). Následníkem tohoto objektu je T_Army_opts, který se používá jen v editoru konfigurace. Obsahuje navíc tři metody pro náhodné generování armád a dva objekty typu T_unit_randomizer, které k tomu využívá.

Řízení bitvy obstarává objekt T_Battle pomocí tří metod - Prvni_Tah, Next_Tah, kdy je spočítáno, která jednotka je na tahu na základě čísla jednotky předchozí, a Utok, kde se vypočítají výsledky útoku - jsou jen vráceny v parametrech, v (odkazem) předávaných proměnných se nic nemění. Metody pro řízení posloupností tahů pracují s parametry ve formátu P_vlastnosti_army, tedy odkazu na pole vlastností oddílů - to proto, aby je mohla využívat jak umělá inteligence (která uchovává informace v jednom poli o deseti položkách), tak hlavní objekt řízení hry (Přistupující přímo k T_Army_Data, kde jsou dvě pole o pěti položkách). Metoda pro útok nemění parametry jednotek, aby byla použitelná i pro umělou inteligenci - zkoušení různých možností.

Poslední hlavní strukturou unity data je herní pole. Pro potřebu konfigurátoru obsahuje objekt T_pole_opts - jen pro uložení pozic neprůchozích políček a jejich přesahů. Typ T_Pole_Rec obsahuje vlastní data herního pole - pozice jednotek, jejich natočení a také neprůchozí políčka (pro snadnější přístup během vykreslování). Proměnná Rec tohoto typu je obsažena v objektu T_pole_data, který obstarává základní práci s herním polem: Init - zapsání neprůchozích políček, nulování ostatního, Je_nepruchozi - vrací boolean o průchodnosti políčka (uvažuje i zda se na políčku nachází jednotka, nebo přetečení mezí herního pole). Set_Pos a Del nastaví, resp. vymaže jednotku na nějaké pozici. Vlna - implementuje algoritmus vlny - důležitý pro hledání cesty a určení vzdálenosti dvou políček. (Metoda Vlna používá dat. typ T_Pole_Vlna, do kterého ukládá výsledky). Objekt T_Pole_Data je využíván i procedurami umělé inteligence. Jeho následník se nazývá T_Pole. Obsahuje navíc dvě metody - Move pro přesunutí jednotky a Kill, která jednotku na urč. pozici nejen vymaže, ale zásahem do vlastností armád nastaví počet jednotek v daném oddílu na 0 a sníží počet oddílů v konkrétní armádě.

Krom toho jsou v unitě data dva další objekty - už zmíněný T_Unit_Randomizer a T_Fronta - objekt zajišťující práci s frontou, používaný pro algoritmus vlny (i v objektu T_Pole_Data, i umělou inteligencí v dalších metodách). Nejdůležitější objekty, používané ve hře, mají v unitě data i svoje globální instance - Army, Bitva a Pole, ke kterým přistupují ostatní objekty - řízení bitvy, umělá inteligence, spolupráce s obrazovkou a vstup od uživatele.

4.2 Práce s grafikou

Přístup k obrazovce a zobrazování veškerých informací (hry, konfigurátor je spíš program sám pro sebe) se děje přes objekt typu T_Screen, z unity display. Mezi jeho datovými položkami jsou také všechny bitmapy, které se na obrazovku během hry vykreslují (ve skutečnosti vlastně jenom pointery na ně).

Formát souboru obrázků, s nímž hra pracuje, (základní operace se soubory i obrázky v paměti zajišťuje unita bitmaps) vypadá následovně - v souboru je na prvním řádku uložena výška, na druhém pak šířka (číslované od 0 - tedy obrázky o výšce 40 pixelů mají na prvním řádku 39) - obě v textovém formátu, oddělovač řádků je #13#10 (formát souborů jsem vymyslel kdysi dávno, když jsem s jinými než textovými soubory neuměl pořádně pracovat). Na třetím řádku jsou odleva odshora postupně jednotlivé pixely - jeden bod = jeden byte, číslo v ascii znamená číslo barvy. Do jednoho souboru je možné za sebou umístit až 256 obrázků. V paměti obrázek, představovaný typem P_Image (pointer na pole bytů) zabírá 4 byte + počet pixelů. Paměť je dynamicky alokovaná procedurou GetMem.

Objekt T_Screen obsahuje metody pro vykreslení všech potřebných věcí - záhlaví, celého herního pole nebo jen přístupných políček, informací o jednotkách, jejich pohybech a výsledcích útoku, dále pro animaci pohybu a útoku (metody Anim_Move a Anim_Attack), k čemuž využívá údaje algoritmu vlny (předem spočítané hlavním objektem hry) a navíc upravuje informace o natočení jednotek v datech objektu Pole. V unitě display se nachází také přímo instance tohoto objektu - Screen, která je volána ostatními.

4.3 Spolupráce s uživatelem

Vyhodnocování pokynů od hráče přísluší objektu Hrac třídy T_Hrac z unity hrac_input. Jedinou jeho starostí je sledovat pohyb myši (pokud je hráč zrovna na tahu) a zjistit hráčem vybrané místo přesunu jednotky, resp. útoku. To zprostředkovává metoda Get_Tah - ta neustále zjišťuje aktuální pozici kurzoru myši a předává údaje vnořené proceduře Get_Pos, která absolutní parametry na obrazovce přepočítává na herní políčka, stará se o změny vzhledu kurzoru a volá metodu objektu Screen, zajišťující zobrazení informací o jednotce, nad níž se nachází kurzor. Procedura Get_Tah také obstarává uživatelem vynucené ukončení hry - předá hlavnímu objektu hry jako cíl tahu pole [-2,-2].

4.4 Umělá inteligence

Nachází se v souboru pc_ai a objektu Pc třídy T_AI. Pro ostatní objekty je kromě inicializace přístupná jediná metoda - Get_Tah - podobně jako u hráče zjistí, kam se má aktuální jednotka přesunout a na co zaútočit. Get_Tah nejprve zkopíruje aktuální stav jednotek do lokální proměnné U (pole o deseti položkách typu T_vlastnosti_army) a potom ho předá odkazem (private) proceduře Think, která zajišťuje vlastní algoritmus umělé inteligence.

Používá k tomu jednoduchý rekurzivní algoritmus "minimax" - propočítává všechny možnosti na 4 tahy dopředu (dá se upravit nastavením konstanty ai_hloubka v unitě data, takto na Athlonu 1.8Ghz trvá tah asi 5 sekund, nevím jestli větší hloubka než 5 by vůbec byla únosná) a potom na nalezený stav spustí ohodnocovací funkci Value (která prostě spočítá zbylé jednotky hráče a počítače a odečte je od sebe - myslím, že ve hře, kde některé jednotky jedním tahem přejdou polovinu hrací plochy není dost dobře možné uvažovat "strategické" pozice atp.). Algoritmus počítá s tím, že hráč vždy vybere pro počítač nejhorší možnost. Pokud je ohodnocení možností rovnocené, rozhodne náhodně.

V každém kroku je uvažováno pro jednu jednotku max. 6 možných tahů - útěk a útok na max. 5 nepřátelských jednotek. O nejvhodnějším políčku pro útěk rozhoduje procedura Escape_Move - uvažuje, na které z přístupných políček může zaútočit nejméně nepřátelských jednotek. Jsou-li dvě políčka z tohoto pohledu rovnocenná, rozhoduje náhodně. Má-li aktuální jednotka možnost útoku na dálku, jednoduše se vyzkouší provést útok na všechny nepřátele (pro zjištění výsledků je volána metoda Bitva.Utok). V opačném případě se zavolá procedura Pozemni_Utok, která vyzkouší dostupnost jednotlivých nepřátel a útok na ně; pokud je konkrétní nepřátelská jednotka mimo dosah, metodou Attack_Move se zjistí nejlepší políčko pro přesun co nejblíže k nepříteli (Může se stát, že nepřátelská jednotka je od útočníka odříznutá úplně - ze všech šesti směrů s ní sousedí neprůchozí políčko nebo jiná jednotka. V takovém případě vrátí Attack_move false a aktuálně zkoumaná větev se odřízne.

Procedury Escape_move a Attack_move používají pro zjišťování pozic algoritmus vlny. Pracují se stejným objektem typu T_Fronta, jako metoda T_Pole_Data.Vlna. I datový typ pro ukládání výsledků je shodný (T_Pole_Vlna), pouze místo položek pro ukládání předchůdců políčka používají stejný paměťový prostor pro uložení vzdálenosti od nepřítele, popř. počtu nepřátel s přístupem na urč. políčko. Objekt typu T_AI obsahuje i datovou položku typu T_Pole_Vlna - všechny operace algoritmu vlny volané ze všech hloubek rekurze se provádějí na stejném datovém prostoru, a T_Pole_Data, kam se ukládají vždy aktuální pozice jednotek. Slouží také algoritmům vlny (pro zjištění průchodnosti políček). Informace o stavu armád si procedura Think do vyšších hloubek rekurze předává odkazem, přičemž si do svých lokálních proměnných ukládá předchozí stav aktuální jednotky pro zkoušení dalších možností.

4.5 Hlavní řídící objekt

Objekt Hra typu T_Hra se stará o řízení hry - vydává ostatním objektům pokyny k činnosti. Jediná metoda, viditelná hlavním programem je Hraj. Na začátku jejího provádění se zavolá (private) metoda Init_All, která zinicializuje data všech ostatních objektů a poprvé zobrazí celé herní pole. Potom se vybere jednotka, která je na tahu jako první. Pak se až do konce hry opakuje určení aktuální jednotky, zjištění jejího místa přesunu a animace tahu, popř. útoku. Při něm se navíc pomocí private metod Utok_Hrac a Utok_Pc spočítají škody a uloží do aktuálního stavu jednotek (metody přitom volají proceduru Bitva.Utok). Na konci tahu ještě objekt zkontroluje, jestli nemá dojít k ukončení hry.


5. Závěr

Na závěr bych rád uvedl seznam toho, co se dá udělat lépe a já jsem to nestihl, nebo se tím nezabýval proto, že by si to vyžádalo přepsání poloviny kódu. První na řadě by asi měla být umělá inteligence - nad počítačem je možná až moc jednoduché vyhrát. To by ale asi vyžadovalo úplně předělat celý systém, nebo ho nějakým (mně neznámým) trikem zrychlit, aby mohl počítat více do hloubky. Další je - z pohledu zpětně - dost nešikovný formát dat, kdy informace o jednotkách hráče a počítače jsou ve dvou oddělených polích - pro psaní AI, která je uchovává na jednom místě, a její spolupráci s objektem Bitva jsem musel použít i trik s operátorem @, o kterém nevím, zda do standartního pascalu vůbec patří. No a jako poslední bych zmínil pomalé načítání ze souborů (po jednom bajtu), které jsem výrazně neměnil někdy od doby před pěti nebo šesti lety, kdy jsem ho napsal poprvé. Určitě by se toho našlo ještě víc, tohle je asi to nejnápadnější.

Tolik tedy o mém zápočtovém programu, doufám, že jsem na nic důležitého nezapomněl. Přeji hezký den

Ondřej Dušek