1 Moduly a Programátorská rozhraní
1.1 Moduly
Projekt 8 Kingdoms je vzhledem k jeho složitosti a rozsáhlosti rozložen na několik relativně samostatných a nezávislých částí. Tyto části jsme nazvali moduly a bude na ně v dokumentaci tak odkazováno. Jejich výčet společně s popisem obsahuje tabulka 1.

RM | Resource Manager - Systém správy zdrojů se stará o nahrávání a uvolňování datových zdrojů z paměti.
GUI | Grafické uživatelské rozhraní má na starost vizualizaci dějů ve hře a vstup uživatele.
NET | Síťové rozhraní zprostředkovává komunikaci mezi programy běžícími na více počítačích propojených do sítě.
WORLD | World implementuje pravidla hry 8 Kingdoms a definuje rozhraní pro komunikaci s hrou.
AI | Modul umělé inteligence zajišťuje co nejlepší hru počítačového hráče v prostředí světa 8 Kingdoms.

Tabulka 1.1: Moduly projektu 8 Kingdoms.
Jejich implementace je zhruba rozdělena do stejnojmenných adresářů ve stromě zdrojových souborů projektu. Toto schéma zjednodušilo průběh vývoje, protože se jednotlivé moduly daly vyvíjet nezávisle na množství a funkčnosti implementovaného kódu ostatních modulů. Jediné společné systémy, které všechny moduly používají jsou: systém na výměnu zpráv, logování, funkce pro alokaci paměti a konstrukce řešící kompatibilitu s různými systémy. Tyto budou popsány v následujících kapitolách této části.
1.2 Adresářová struktura

/ai | Zdrojové soubory pro modul umělé inteligence
/bin | Přeložený projekt. Potřebné knihovny(Win32)
/common | Obecné programátorská rozhraní: logování, message systém, alokace paměti,...
/common/rm | Zdrojové soubory pro resource manager.
/common/tcl | Zdrojové soubory rozhraní pro TCL.
/common/xml | Zdrojové soubory rozhraní pro XML.
/doc | Dokumentace.
/editor | Zdrojové soubory, knihovny a data potřebná pro editor.
/editor/bin | Spustitelný soubor editoru a potřebné dll knihovny.
/external | Knihovny, dll a hlavičkové soubory. Hlavně pro Win32.
/gui | Zdrojové soubory uživatelského rozhraní.
/net | Zdrojové soubory pro síťové rozhraní.
/log | Adresář pro logovací soubory.
/projects | Adresář obsahující projekty pro různé překladače a prostředí.
/res | Adresář pro data.
/savegame | Adresář pro uložené hry.
/tests | Adresář pro testovací programy.
/world | Zdrojové soubory pro modul World.

Tabulka 1.2: Adresářová struktura.
1.3 Systém zpráv
Systém zpráv je integrální součástí projektu 8 Kingdoms. Je implementován v souborech Msg.cpp/h. Slouží pro komunikaci jednotlivých modulů mezi sebou. Na počátku, hned po spuštění, se inicializuje globální fronta příjemců zpráv typu TTransceiverQueue funkcí KInitGlobalTransceiverQueue. Poté si každý modul registruje pomocí funkce KRegisterGlobalTransceiver a struktury TMessageTransceiver handler typu RVAL(*HANDLER)(MESSAGE_ID,SENDER,PARAM), do kterého bude dostávat zprávy, které pošlou jiné moduly. Zprávy jsou pak posílány funkcí KSendMessage. Systém zpráv se nestará o zablokování ani o vlákna. Tvoří jen jakési rozhraní pro volání funkcí. Je to tak proto, že se předem nedá odhadnout, jak náročná reakce na zprávu bude, aby se tím případně příliš nezatěžoval systém při volání většího počtu zpráv. Diagram 1 názorně zachycuje fungování systému zpráv.
Diagram 1.1: Funkce systému zpráv.
1.4 Logování zpráv
Aby se usnadnil průběh vývoje a odhalování chyb při ostrém běhu projektu, byl navržen jednoduchý systém pro logování. Je implementován v Log.cpp/h. Zdrojový soubor, který chce použít logování musí na začátku uvést direktivu:

        #include "common/Log.h"

K logování se používá logovací třída TLog. Logovat je možné buď do souborů, nebo na standardní výstup. Typ výstupu se ovlivní parametry konstruktoru. Logovací soubory mohou mít buď speciální jména, nebo jména vygenerovaná systémem TLog. Adresář, kam se zapisují logovací soubory je v makru LOG_DIR. Maximální délka logovacích zpráv je dána makrem MAXLOGBUFFER_LEN.

        #include "common/Log.h"

        //loguj do souboru mujlog.log
        TLog mylog("mujlog.log");

        //loguj na standardni vystup
        TLog mylog(1);

        //loguj do vygenerovaneho souboru
        TLog mylog();

Samotné logování se provádí voláním metody LogMsg, která má stejné parametry jako funkce printf. Dále je možné logovat s prioritou metodou LogMsgId. Tímto způsobem je možné logování škálovat v rámci jednoho souboru. Logují se jen ty zprávy, které mají větší prioritu, než je priorita logu nastavitelná parametrem konstruktoru. Defaultní hodnota priority je definována makrem DEFAULT_PRIORITY_LOG.

        #include "common/Log.h"

        //loguj na standardni vystup s prioritou 5
        static TLog mylog(1,5);

        //doporuceny zpusob pouzivani vlastnicho logu
        #define MYLOG mylog.LogMsg

        void myfunc()
        {
          ...
          MYLOG("System spustil funkci myfunc");
          ...
          MYLOG("myfunc: Vysledek: %d, Chybovy kod: %d, Zprava: %s", v, c, s);
          ..
          mylog.LogMsgId(6,"tato zprava se zaloguje");
          mylog.LogMsgId(2,"tato zprava se nezaloguje");
        }

Logovací systém definuje jeden primární log, tzn globální instanci TLog, který je aktivní podle toho, jestli je definováno makro K8_LOG. Tento log loguje do souboru, nebo na obrazovku podle toho jestli je makro GLOBALLOG_STDOUT nastaveno 1, nebo 0. Samotné logování se musí provádět pomocí maker GLOBALLOG a GLOBALLOGID.

        #include "common/Log.h"

        void myfunc()
        {
          ...
          GLOBALLOG("System spustil funkci myfunc");
          ...
          GLOBALLOGID(4,"myfunc: Vysledek: %d, Chybovy kod: %d, Zprava: %s", v, c, s);
        }

1.5 Alokace paměti
Aby se snáze odstranily problémy s neodalokovanou pamětí, používají se v projektu 8 Kingdoms speciální techniky na její alokaci. Tyto techniky umožňují dohledat zdrojové soubory, ve kterých se neodalokovaná paměť alokovala. Pro používání tohoto systému musí být na začátku hlavičkového souboru Cčkového modulu makro.

        #include "common/mm.h"

Všechno další zajistí používání funkcí KMemAlloc a KMemFree pro alokaci struktur. Alokace tříd je složitější, musí se na ně volat new. Operátor new není předefinován globálně. Místo toho každá třída je potomkem DebugClassHelper, která předefinovává operátor new pro tuto třídu. Dědění se provádí makry CLSDBG1, CLSDBG2. Viz příklad.

        #include "common/mm.h"

        struct mystruct
        {
          ...
        };

        class myclass CLSDBG1
        {
          ...
        };

        void myfunc(){
          mystruct* ms;
          ms = (mystruct*)KMemAlloc(sizeof(mystruct));

          myclass* mc = new myclass();
        }

Extra kód zajišťující udržování záznamů o alokovaných blocích paměti se zkompiluje pokud je definováno makro K8_MM_DEBUG. Tyto záznamy se zapíší do speciálního logu mimo Logovací systém ve chvíli kdy se zavolá DumpAllocMem(). Nejčastěji se tak děje před ukončením programu.
Dobrým programátorským zvykem je kontrolovat, zda byla paměť alokována. MM obsahuje pro tento případ náhražky za funkce malloc, realloc a operátor new, které tuto kontrolu provádějí v ostré verzi centrálně. Tyto metody se použijí při překladu ostré verze(release) s definovaným makrem K8_MM_MALLOCCHECK. Standardně není tato kontrola zapnuta, protože se má zato, že jde o zbytečný overhead. Pokud dojde i virtuální paměť, program umírá a znamená to, že problém je někde jinde, a operační systém umírá vzápětí také. V našem případě není okamžité ukončení programu kritické a ztráta dat důležitá.
MM ještě obsahuje funkce pro realokaci polí, KMemRealloc a trošku chytřejší realokaci polí, která ukusuje pamět po celých mocninách dvojky KExtArrayRealloc. Mezi užitečné funkce patří také KInitString a KReleaseString pro alokaci a nastavení řetězců.
V poslední fázi vývoje se začaly využívat inteligentní pointery. Některé objekty jsou vlastněny více rodičovskými objekty a není u nich zajištěno pořadí, ve kterém se dealokují a neví se kdo má vlastně podřízený objekt jako poslední a může jej bezpečně odstranit z paměti. Tyto objekty dědí třídu TRefCounter. Při jejich alokaci je počet referencí nastaven na jedna. Voláním metod getRef a ungetRef se počet referencí zvyšuje a snižuje podle toho, na kolika místech je uložen ukazatel na objekt. Jakmile klesne počet referencí na 0, objekt se sám odstraní z paměti.
1.6 Kompatibilita
Jelikož portabilita je jedním z cílu projektu 8 Kingdoms, je nutné si poradit s některými odlišnostmi různých OS, přinejmenším do doby než se najde lepší a přenositelnější řešení. Proto ty účely existuje modul compatibility.cpp/h ve kterém se řeší některé systémově specifické věci. Většinou jde o nestandardní názvy běžných funkcí na různých platformách, které neměly to štěstí, aby se dostaly do specifikace ANSI C. (Čímž se staly prostředkem pro výrobce překladačů, aby do věci zavedli ještě větší zmatek):

- různé verze funkce printf(vsnprinf, vsnwprinf)
- různé verze názvy funkce unlink v knihovnách překladače od Borlandu, MS, a gcc
- různé způsoby pro práci s adresáři na Unixu a MS Windows
- stricmp a strcasecmp
- socket API je řešeno přímo v modulu NET, ale patří do této kategorie

1.7 Výjimky
Některá, hlavně vysokoúrovňová, rozhraní používají pro pro hlášení výjimečných stavů a chyb kromě chybových kódů a návratových hodnot C++ výjimky. Pro tyto výjimky platí, že každá je odvozena od E_8K nebo od některého z jejich potomků. Tato třída obsahuje základní datové položky pro přenos informace o chybě, čísla řádku a jména souboru. Všechny výjimky jsou definovány v souboru exc.cpp/h. Pro usnadnění vytváření výjimek je možné použít makro COMMON_EXCEPTION.

        /// definice vyjimky
        class E_8K_MYEXCEPTION : public E_8K {
        public:
          E_8K_GUI(const char* d):E_8K(d){};
          E_8K_GUI(const char* d,const char* f,int l):E_8K(d,f,l){};
        };

        /// alternativne
        COMMON_EXCEPTION(E_8K, E_8K_MYEXCEPTION);

Pro volání výjimek se pak používá speciální makro THROW, které dosadí do konstruktorů výjimek číslo řádku a jméno souboru, takže lze vznik výjimky snadno dohledat. Uživatelským parametrem výjimky je textový řetězec popisující vzniklou chybu.

        void func() {
          ...
          if(a<b)
            THROW(E_8K_MYEXCEPTION,"impossible: a less then b\n");
          ...
        }

        ...
        try {
          func();
        }
        catch(E_8K_MYEXCEPTION e) {
          //s tim se neda nic delat
          exit(-1);
        }

1.8 Konfigurace před překladem
V kořenovém adresáři projektu se nalézá soubor config.h, který obsahuje nejvyšší úroveň parametrů pro správné fungování projektu. Tento soubor je na platformách Unix výstupním souborem nástrojů pro konfiguraci a kompilaci projektů(autoconf, automake, autoheader). Při překladu na platformách Windows mohou být parametry nastaveny ručně. Soubor config.h pak includuje každý zdrojový soubor, který chce tyto parametry použít. Tabulka 3 shrnuje tyto parametry.

Makro | Popis
RESOURCES_DIR | Adresář s daty projektu. Na OS Windows vetšinou relativní cesta vzhledem k programu. Na OS Unix cesta někam do /usr/local/share/...
K8_LOG | Zdali má být zapnuto logování.
LOG_DIR | Adresář pro logovací soubory. Na OS Windows cesta relativně ke spustitelnému souboru. Na OS Unix cesta do nějakého /tmp/...
VERSION | Řetězcové konstanta označující verzi projektu.

Tabulka 1.3: Makra pro konfiguraci projektu před překladem.
Některé moduly mohou mít skryté, neoficiální vlastnosti, které se aktivují jedině makrem definovaným při překladu. Tabulka 4 uvádí takové:

Modul | Makro | Popis
Obecné | K8_MM_DEBUG | Ladění paměti.
K8_MM_NEWDEBUG | Ladění new/delete, náročné.
K8_MM_DADEBUG | Ladění DA, ještě náročnější.
MEMDBG_FILE | Soubor s výpisem informací o nedealokované paměti.
K8_MM_MALLOCCHECK | Kontrola alokace paměti.
GUI | STEERING | Experimentální pohyb jednotek.
SHADOWMAPPING | Vrhání stínů shadowmappingem.
SIMPLE_SHADOWS | Jednoduché stíny.
FULLTROTTLE | Vyřadí pasivní čekání ve vykreslovací smyčce.
SHOW_DBG_STATES | Aktivuje vypisování informací o stavu hry.
DEBUG_STEERING | Aktivuje kreslení překážek ve steeringu.
DEBUG_DRAW_AABB | Aktivuje vykreslování AABB u TSceneModelObjectů.
DEBUG_DRAW_PATH | Aktivuje vykreslování cesty.
DEBUG_SCENE | Vykreslování obsahu scény z pohledu "druhé" kamery.
DEBUG_DRAW_GATES | Aktivuje vykreslování trasy kolem budov.
DEBUG_DRAW_ATTACKPOSITIONS | Aktivuje vykreslování útočných pozic.
DRAW_DEBUG | Aktivuje vykreslování směrových gizm.
Síť | COMPRESS_ACTIVE | Aktivuje kompresi XML dat posílaných přes síť. Pozn.:na klientech i serveru musí být stejná hodnota.
WORLD | CLIENT_AUTOPLAY | Režim pro automatickou hru.
SERVER_AUTOSAVE | Automatické ukládání na konci kola.
AI | AI_STRATEGIZER_ENABLED | Makro určující, zdali se má volat tah umělé inteligence - má čistě ladící význam.
AI_LOGGING | Makro určující, zdali má AI logovat své akce.

Tabulka 1.4: Makra pro konfiguraci překladu skrytých vlastností.
1.9 Použité knihovny
K překladu 8 Kingdoms jsou potřeba externí knihovny shrnuté v tabulce 4. Přičemž platí, že projekt je tak přenositelný, jak jsou přenositelné tyto knihovny.

Knihovna | Verze | Zdroj
OpenGL | 1.1(1.2) | Součást OS. http://www.mesa3d.org/ http://www.opengl.org/
SDL | 1.29 | http://www.libsdl.org/
SDL_mixer | 1.2 | http://www.libsdl.org/projects/SDL_mixer/
Expat | 1.95.9 | http://expat.sourceforge.net/
Tcl | 8.4.12 | http://www.tcl.tk/
zlib | 1.2.3 | http://www.zlib.net/

Tabulka 1.5: Tabulka použitých knihoven.