Program je rozdělen do několika částí (modulů), každá zabírá jeden adresář:
Přitom návaznost jednotlivých modulů je taková:
Client: XVision, PaintInterface, Xlib, Game, Network, Common
Game: Network, PaintInterface, Common
Network: Common
PaintInterface: Common
Runner: -
Server: Network, Game, Common
XVision: PaintInterface, Common
Xlib: PaintInterface, Common
Soubor Makefile je docela kompilovaný, make si automaticky vyhledá všechny soubory s připonou .cc ve zadaných adresářích a přeloží je do adresáře $(ARCH). Přitom soubory závislé na barevné hlobce se překládají několikrát, a ukládají do $(ARCH) ještě navíc s udáním hloubky (např. FontCreator8.o).
Důležitá nastavení:
Kompilace a instalace se provádí posloupností příkazů make depend ; make ; make install.
Rand - vrátí náhodné číslo od 0 do zadaného maxima.
Die - vypíše chybu přes Error a ukončí program.
Error - vypíše chybu na stderr.
Print - vypíše text.
StdError - vypíše chybu pomocí funkce perror(3).
class MyErrStream - náhrada za cerr, na Solarisu to při linkování s g++ hlásilo chyby, tak jsem si napsal vlastní.
class Point - reprezentace bodu, používá např. Event pro určení souřadnic kurzoru myši.
class Rectangle - reprezentace obdélníku, používá např. PaintInterface, dokáže určit (Rectangle::Intersect), zda se 2 obdélníky protínají atd.
funkce Min a Max
Funkce pro kompatibilitu mezi různými typy systému (byteorder, chybějící funkce a konstanty).
Hlavní definice pro zapnutí debug informací.
Automaticky generováno ze souboru Makefile. Nastavení cest k obrázkům apod.
Skládá se ze 2 částí: rohraní pro grafiku a rozhraní pro klávesnici, myš atd.
Definice vlastností obrazovky (velikost, barevná hloubka), shrnutá do třídy Pixel_t.
enum TransparencyType - definu typy průhlednosti rovin:
class FrameBuffer - reprezentace jedné roviny grafiky. Několik rovin je poskládáno za sebe (s různou průhledností) v objektu FrontBuffer.
Při zápisu do roviny je uložena pozice do objektu DirtyWindow, a při obnovování obrazovky na konci snímku se obnovují jen změněné části obrazovky.
Obsahuje metody:
Modifikace předchozích, které se snaží najít seznamu DirtyWindow stejné nebo větší okno (používá např. XVision, kde např. nejdřív vykresí pozadí a na něj text, ale do DirtyWindow by se to zapsalo dvakrát):
A metody jen ve verzi _sd:
class FrontBuffer - třída, která obsahuje několik objektů FrontBuffer, zobrazující se za sebou se zadanou průhledností. Pro konkrétní implementaci (třeba jako XlibFrontBuffer) je třeba přepsat metodu UpdateScreen, aby opravdu něco zobrazovala.
Další metody slouží pro přidávání a odebírání objektů FrameBuffer: AddFrameBuffer dá rovinu uplně nahoru, InsertFrameBuffer vloží před zadaný jiný FrameBuffer, RemoveFrameBuffer rovinu odstraní (jenom ze seznamu, ne z paměti!).
class Picture - reprezentace obrázku (bitmapy). Metody Width, Height a Data dělají vrací velikost a ukazatel na bitmapu.
class TargaPicture - potomek Picture, dokáže načíst obrázek formátu TARGA (24 nebo 32 bitový, volitelná RLE komprese).
class Event - reprezentace zpráv. Každá zpráva má typ, ukazatel na odesílatele a volitelně také na adresáta, podle typu jsou pak obsazeny další položky.
Typy zpráv:
konstanty ktXXXXX definují hodnoty jednotlivých kláves v poli Event.key.
class EventManager - rozhraní pro hlavní rozesílač zpráv. Je nutné přepsat metodu New (např. v XlibEventManager) tak, aby skutečně generovala zprávy.
class FontCreator - tvůrce obrazu písma. Při vytváření obrazu metodou CreateFontImage se zadává kromě textu a řezu (normální, šikmý, malý, malý šikmý) i směr písma, ta zavolá metodu CreateNorthFontImage, aby vytvořila obraz normálního písma (zprava doleva), a pak jej podle zadaného směru otočí.
class FixedFontCreator - příklad implementace třídy FontCreator, vrací font 9x16 s pevnou velikostí (fixed).
class Filler - jednoduchý "plnič", který se využívá pro vykreslování těl červů. Pomocí metod CircleFirstOption, CircleSecondOptiond je možné vybírat mezi jednotlivými variantami výplně (tyto metody jsou přepsáný v potomcích), např. různé barvy.
Port PaintInterface na knihovnu Xlib.
Definice jmen fontů, použitých v XlibFontCreatoru.
class XlibFontCreator - potomek třídy FontCreator, pro vytvoření obrazu písma používá standartní X písma.
Objektový obal nad standartními X primitivy Window, Pixmap a Image.
class XDrawable - předek pro XPixmap a XlibMainWindow, sdružující jejich společné vlastnosti tak, aby zápis na obrazovku byl shodný s zápisem do bufferu v paměti.
class XlibMainWindow - třída pro manipulaci s oknem na obrazovce. Metoda CreateXImage je přepsána, aby vracela podle možností buď normální Image, nebo SHMImage (viz dále).
class XPixmap - třída podobná XlibMainWindow, ovšem jako Drawable zde vystupuje Pixmap, uložená v X serveru.
class XBuffer - buffer v lokální (!!!) paměti, spojený s objektem typu XDrawable, do kterého se při zavolání metody UpdateWindow překopírují data. Kopírovat data z XDrawable jde metodou GetWindow.
Metody UpdateWindow a GetWindow by měly být virtuální a přepsáný v potomcích, ale kvůli větší rychlosti jsou statické a zjištění, o kterého potomka jde, se dělá přes proměnnou Shared.
class XSHMBuffer - potomek XDrawable, který buffer sdílí přimo s X serverem pomocí MIT SHM. Proto je jej možné použít jen když program běží na stejném počítači jako X server. Autodetekce mezi XSHMBuffer a XImageBuffer je v XlibMainWindow::CreateXImage.
class XImageBuffer - potomek XDrawable, image buffer není sdílen s X serverem, proto je možné jej použít i na vzdálený X server. Je pomalejší než XSHMBuffer. Pokud běží na stejném počítači jako X server, tak ale neznatelně, protože XPutImage se stejně dělá přes mapování souboru do paměti (aspoň na Linuxu a XFree86).
class XlibFrontBuffer - potomek třídy FrontBuffer, metoda UpdateScreen je upravena tak, aby volala XDrawable::UpdateWindow.
class XlibEventManager - potomek třídy EventManager, události od X serveru jsou namapováný na objekty Event.
Jednoduché GUI, ideově podobné knihovně TurboVision od firmy Borland.
Definice cest k obrázkům checkboxu a ukazatele na načtený obrázek. Používá třída CheckBox.
class View - základní stavební prvek GUI. View má definovanou velikost, pozici na obrazovce, barvy, zda je vybrán, vybratelný, viditelný, centrovaný, ukazatel na rodiče atd. Má metody pro kreslení (Draw), které by se měly v potomcích přepsat, Select a Deselect pro změnu fokusu, Choose pro vybrání (přepsáno v SelectableView). Pro zpracování událostí je zde metoda HandleEvent.
Při vytváření objektu se nezadávají souřadnice počátku, jen velikost. Umístění se opraví automaticky při vkládání do objektu Group (viz dále).
View jsou za sebou navěšeny v jednosměrném seznamu.
class SelectableView - potomek View, metoda HandleEvent dokáže obsloužit události z klávesnice a myši (Select, Choose).
class Group - potomek View, do kterého lze vkládat další objekty typu View (děti). Při vytváření je třeba nastavit zarovnávání dětí (vertikálně nebo horizontálně, při složitějším umístění je třeba vytvořit několik skupin a naskládat je do další, vetší skupiny).
Skupina také zajišťuje distribuci událostí, viditelnosti, výběru atd.
Při vkládání objektu do skupiny metodou Insert je objekt umístěn a volá se metoda ReArrange.
Metoda ReArrange - podle zarovnávání přemístí podle zadaného zarovnání všechny objekty buď pod sebe, nebo vedle sebe a podle potřeby změní velikost a volá ReArrange rodiče.
Pro snažší vkládání tříd do skupin existují funkce CreateGroup a CreateItem, jejich použití je nejlépe vidět v Client/WormApp.cc.
class StaticText - zděděn z SelectableView, ale má vypnutou vlastnost Selectable. Zobrazuje text se zadaným směrem, řezem a barvou. Metoda SetText je pro nastavení textu, GetText text vrátí. Používá FontCreator pro vytvoření obrazu textu.
class Label - potomek StaticText, navíc jen nastavuje Selectable na TRUE a při výběru posílá událost evCommand se zadaným příkazem.
class Title - potomek StaticText, má jen změněnou barvu textu na žlutou.
class PictureView - potomek SelectableView, zobrazuje zadaný obrázek a při výběru (Choose) posílá událost evCommand.
class CheckBox - potomek Group, obsahuje PictureView a StaticText, navíc má metody IsChecked a SetChecked. Při změně posílá Event s evCommand a cmDataChanged.
class Selector - potomek Label, jako parametr je ukazatel na pole s jednotlivými texty, které se mění při Choose.
class InputLine - potomek Label, při stisku vypsatelné klávesy změní text (přidá písmeno) přes SetText, při stisku Delete nebo BackSpace jedno písmeno odebere. Zadaný text jde zjistit přes GetText. Při změně posílá Event s evCommand a cmDataChanged.
class InputNumber - potomek Label, umožňuje zadávat čísla v zadaném rozsahu. Má navíc metody SetNumber a GetNumber. Při změně posílá Event s evCommand a cmDataChanged.
class PictureSelector - potomek PictureView, podobný prvku Selector - jde vybírat mezi několik obrázky, při změně se posílá událost evCommand s cmDataChanged. Navíc má metody SetItem a GetItem.
class Menu - jednoduché hierarchické menu, složené z jakýchkoliv jiných prvků XVision (vetšinou ale MenuItem a SubMenu). Při stisku Esc (resp. při přijetí události cmCancel) se zobrazí znovu rodičovské menu.
class MenuItem - potomek třídy Label, navíc jen definuje jiný standartní řez písma (šikmé).
class Submenu - potomek třídy Label, slouží jako vstup do další urovně menu. Při výběru schová rodiče (a tím i sebe) a zobrazí podmenu.
class Application - potomek Group, hlavní objekt pro rozesílání událostí do potomků (je napojen na EventManager). Metoda Initialize se musí volat po vytvoření objektu, metoda Run periodicky pro vlastní běh celé Application.
app_global - ukazuje na instanci Application.
Herní logika - pohyby červů, detekce srážek, začátky kola, hry a konce kola a hry.
enum PlayerType_t - definice druhů červů (ptNone, ptAI, ptHuman, ptNetwork)
class Worm - hlavní předek všech typů červů. V potomcích je třeba přepsat kontruktor a metodu Control, aby červa řídila (měnila směr) pomocí TurnLeft a TurnRight. Při behu hry se periodicky volá metoda Step, která opravuje pozici červa, volá Control, zobrazuje červa a pod.
struct CrashBufferItem_t - pro detekci srážek červů se používá pole CrashBufferItem_t, ve kterém je vždy zaznamenáno kdo pole obsadil (PlayerNumber) a taky v kterém kroku pole obsadil (Counter). To je proto, že červ se může pohybovat jen po bodě, zatímco velikost jeho těla je vetší (třeba pět bodů) a za srážku se pokládá jen stav, kdy je buď PlayerNumber jiné, nebo Counter je od aktuálního Counter (uložené v objektu Worm) odlišné aspoň o 10 (resp. jiná hodnota). Zeď je v CrashBufferItem_t reprezentována jako červ s číslem WallBorderCrashB_playernum (255).
class Record - (nepoužívá se) červ, který při vytvoření dostane posloupnost přikazů typu "10 kroků zatáčej doleva, pak 20 doprava, pak 30 rovně" a podle ní se řídí. Šlo by použít např. při startu programu.
class Human - pomocí SetKeys se nastaví klávesy, kterými se červ má zatáčet doprava a doleva, metoda Control si pak přes EventManager zjistí, jestli jsou stisklé a podle toho zatáčí.
class AI - červ řízený počítačem. Každý 3-6 krok se znovu podle jednoduchého algoritmu rozhoduje, kterým směrem se bude červ točit. Volání algoritmu je nepravidelné, aby se snížily nárazy, které by vznikly, kdyby se rozhodování volalo pro všechny červy ve stejný krok.
Rozhodovací algoritmus je následující: Směr červa se zařadí do středu jedné z osmi úsečí kruhu o poloměru asi 200 bodů, a spočítá se počet zabraných bodů ve dvou sousedících úsečích (vždy jen asi každý 3. bod). Protože hustota bodů se snižuje se vzdáloností od středu kruhu, mají objekty blíže k hlavě červa větší váhu než objekty vzdálené.
class Remote - červ řízený ze vzdáleného počítače. Metodou Tune se nastavuje nový směr (točit doprava, točit doleva, rovně). Po síti se přenášejí jen změny stavu (menší zátěž).
class Players - třída pro manipulaci s polem objektů typu Worm (přidávání, odebírání atd.). Navíc zajišťuje přenos informaci o pohybu červů po síti (zavolá se StepLocal, posunou se lokální červi, pak se data o pohybu a případných smrtích pošlou přes SendLocal, po při přijetí dat se volá UpdateRemote a nakonec StepRemote). Navíc zajišťuje rozdělování a vykreslování bodů, řízení začátku a konce kol a hry.
class Connection - třída pro práci s síťovým spojením (socket). Obsahuje metody Connect, Disconnect, Listen, Send, Receive a pod. Všechny jsou abstraktní (pure virtual) a nutné je přepsat v potomcích (UDPConnection).
class Multiplexor - třída, která zajišťuje pomocí select(2) jednoduché čekání na příchod spojení na několika portech (objektech třídy Connection). Používá např. GameServer. Před smyčkou se nejdříve zavolá Reset a pak se volá metoda WaitConnection, která vrátí přímo Connection nebo 0 při timeoutu. Vrácená Connection se automaticky vyhodí ze seznamu čekajících (až do Reset). Pokud byl na Connection požadavek neplatný, je možné Connection vrátit do fronty (pole) pomocí ReWaitConnection.
class UDPConnection - potomek Connection, zajišťuje komunikaci přes protokol UDP/IP.
Konstanty, udávající standartní hodnoty pro timeout u tříd v modulu Network.
Síťová zpráva je tvořena vždy blokem PacketHeader a za ním několika dalšími bloky NetMesssage.
class NetMessage a potomci - definice jednotlivých zpráv, posílané po síti mezi klientem a serverem. Pole Size udává skutečnou velikost objektu.
class PacketHeader - hlavička zprávy.
Používá NetConnector pro uložení přijatých zpráv - roztřídí si je podle typu a pro manipulaci s nimi je použit právě objekt NetCTree (uvnitř jsou zprávy jednoho typu uloženy za sebou v spojovém seznamu).
class NetConnector - třída, zajišťující posílání a přijímání zpráv od vzdáleného NetConnectoru (vlastně proxy objekt).
Vždy jeden NetConnector beží jako klient a druhý jako server, rozdíl je v zvyšování čísla zprávy (klient zvyšuje číslo zprávky po přijetí, server po odeslání).
Metoda Connect se používá jen na straně klienta a slouží k připojení k portu, Disconnect k odpojení od serveru.
Zprávy se uloží k odeslání pomocí metody SendMsg, jednou za snímek se pošlou SendMessages, a po přijetí přes ReceiveMessages se vybírají metodou GetMsg.
Síťové spojení používá Connection a vypadá asi takto:
Tako komunikace je docela spolehlivá a hlavně rychlá, neboť se neposílají a ani se nečeká na potvrzovací pakety.
Logika klienta a klientské rozhraní programu.
class FlatFiller, CircleFiller, CheckerFiller, PictureFiller - potomci Filler, různé varianty výplně pro těla červů (Flat - výplň barvou, Circle - kruhy, Checker - kostičky, Picture - obrázky).
class PlayerTypeView - potomek PictureSelector, pro vyběr typu hráče při startu hry. Používá PlayerSelectorItem.
class PlayerControlView - potomek Label, pro vyběr ovládacích kláves při nastavování hry. Používá PlayerSelectorItem.
class FillerSelector - potomek SelectableView, pro vyběr výplně červa při nastavování hry. Používá PlayerSelectorItem.
class ScoreTable - potomek Group, jednoduchý panel s pořadím hráčů na konci hry.
Definice obrázků, použitých v modulu Client.
class PlayerSelectorItem - potomek Group, sdružuje PlayerTypeView, PlayerControlView, Label na jméno hráče a FillerSelector.
class PlayerSelector - potomek Group, panel sdružující 10 slotů pro PlayerSelectorItem a další prvky, sloužící k nastavení hráčů při začátku nebo přerušení hry.
class TaskSwitch - třída, která v metodě Run zajišťuje přepínání mezi jednotlivými úlohami (přijímání dat, posílání dat, pohyb hráčů, běh aplikace) a upravování FPS (počtu snímků za sekundu). FPS se mění v závislosti na aktuální zátěži a při běhu na síti je jeho hodnota stejná na všech počítačích.
class WormApp - potomek Application.
Při startu vytvoří v metodě Initialize menu a pak se chová podle událostí z HandleEvent (výběry z menu) a z NetConnector::GetMsg (zahájení hry, konec hry atd.). Při běhu na síti je vetšina zpráv řízení hry převedena na požadavky, které jsou zaslány na server, místo toho, aby se rovnou vykonaly. Tím je zajištěna synchronizace začátků kola a pod.
main - vytvoří všechny potřebné objekty a zavolá TaskSwitch::Run.
Jednoduchá spouštěcí utilitka pro zjištění barevné hloubky X serveru, která zavolá správný typ klienta (předkopilovaného pro určenou barevnou hloubku).
class GameServer - logika síťového serveru. Při startu vytvoří jeden objekt třídy NetConnector s přistupem pro kohokoliv a čeká na příchozí spojení. Pro každého klienta vytvoří nový NetConnector a pravidelně od nich přijímá zprávy (CollectMessages). Pak je roztřídí, opraví stav hry a pod. a rozešle zpět všem klientům.
main - zjistí volitené argumenty (vypíše help) a vytvoří objekt GameServer.