Síťový mariáš - Programátorská dokumentace
Specifikace
Aplikace umožňující společnou hru mariáše třem hráčům. Hraje se podle pravidel voleného mariáše
Aplikace se skládá ze dvou hlavních částí - klienta a serveru
MessageDispatcher - třída zapouzdřující síťovou komunikaci
Session - třída udržující informaci o aktuálním "sezení"
Game - abstraktní třída určující průběh hry a uchovávající informace o hře
GameBasic - potomek Game, určuje průběh obyčejné hry
GameBetl - potomek Game, určuje průběh betla
GameBasic - potomek Game, určuje průběh durcha
Trick - abstraktní třída uchovávající informace o štychu, přidávání do štychu apod.
BasicTrick - přidávání do štychu se specifiky obyčejné hry (jiné pořadí karet, trumfy...)
BetlTrick - přidávání do štychu pro betl a durch
Card - struktura uchovávající jednu kartu - její barvu a výšku, navíc má metody pro převod na řetězec a zpět
CardPack - datová struktura zapouzdřující typ std::vector<Card>
main.cc – modul spouštějící vlastní server + navíc obsahuje pomocné funkce pro zpracování textu
Card.cc – modul implementující třídy Card a CardPack
Game.cc – modul implementující třídy Game, GameBasic, GameBetl a GameDurch
MessageDispatcher.cc – modul implementující síťovou komunikaci
Session.cc – modul implementující třídu Session
Trick.cc – modul implementující potomky třídy Trick – BasicTrick a BetlTrick
Síťová komunikace je zapouzdřena třídou MessageDispatcher. Tato třída zpřístupňuje tradiční C-API BSD socketů prostřednictvím rozhraní objektů. V konstruktoru je vytvořen socket (pomocí volání socket()), socket je pojmenován (bind()) a poté je vytvořena fronta požadavků pomocí listen(). Výchozím portem, na kterém server vyřizuje požadavky, je port 2902, toto lze změnit volbou -p <číslo portu> při spuštění serveru. Třída MessageDispatcher pracuje se sockety v blokovacím režimu. Odpovědi od klientů jsou ukládány do fronty; pokaždé při požadavku na odpověď od klienta (metoda readCmd()) jsou, pokud je fronta prázdná, přečtena všechna data od klientů (pomocí select() vybere sockety, na kterých jsou data k přijetí). Pokud se objeví požadavek na nové spojení s klientem, a dosud nejsou připojeni více než 2 klienti, je požadavek přijat, a nový socket (vytvořený pomocí connect()) je zařazen do pole aktivních (hráčských) socketů.
Při odesílání zprávy klientovi (metoda sendCmd()) je klient identifikován svým pořadím v poli socketů (čísla 0..2).
Informace o aktuálním sezení jsou udržovány pomocí instance třídy Session. Ta se stará zároveň řízení spouštění hry, a přechod mezi jednotlivými faázemi hry. Třída Session v prvé řadě uchovává informace o připojených hráčích a o jejich skóre v kontejneru players, který obsahuje strukturu Player. Dále obsluhuje některé požadavky od klientů, především informační a servisní příkazy (pro seznam příkazů viz dodatek A). Metoda sendCmd (v několika přetížených variantách) složí k odeslání zprávy klientovi. Klienti jsou identifikováni svým pořadím v seznamu hráčů (toto pořadí je stejné, jako pořadí aktivních socketů v MessageDispatcher).
Průběh sezení je následující:
klienti se připojují; jakmile je připojeno dost klientů (3), začíná hra - je naalokován nový objekt třídy GameBasic
po skončení hry je aktualizováno skóre, a klienti jsou informování o novém skóre
pokud se některý z klientů odpojí, je sezení ukončeno, jsou odpojeni i ostatní klienti a je inicializováno nové sezení
Sezení zpracovává zprávy od klientů pomocí několika (privátních) metod - handleServCmd, handleInfoCmd. Tyto metody slouží k provedení patřičné akce při přijetí zprávy příslušného typu (serv, respektive info). Ke zpracování zpráv dochází na základě volání z jiných metod, především z potomků třídy Game při vlastní hře. Při hře je typicky očekávána zpráva typu game, a to od určitého klienta (hráče); proto zpracování příkazu (readCmd()) čeká, dokud nedostane zprávu typu game od zadaného klienta - neherní zprávy jsou zpracovány, herní zprávy od ostatních klientů jsou zahazovány.
Sezení rovněž obsahuje objekt třídy MessageDispatcher (viz popis výše), pomocí kterého je realizována vlastní síťová komunikace.
Vlastní hra je implementována hierarchií tříd, s abstaktní třídou Game na jejím vrcholu. Od této třídy jsou pak odvozeny třídy pro jednotlivé typy mariášové hry (normální hra, betl a durch).
Třída Game poskytuje kromě abstraktního rozhraní, jehož implementace pak závisí na hrané hře, také metody používané v hrách všech typů. Jsou to metody pro rozdávání karet (distCards, sendCards), a metoda pro sehrání štychu. Abstraktní rozhraní třídy Game pak obsahuje metody pro řízení průběhu jednotlivých částí hry, tj. výběr hry (contract()), flekování (flek()), sehrávku (play()) a výpočet skóre hry (scoring()). Tyto metody jsou pak volány z metody runGame() třídy Session.
Při výběru hry se nejprve začíná s normální hrou (GameBasic). Pokud některý z hráčů požádá o vyšší hru, vrací metoda contract() číslo tohoto hráče (jinak vrací -1), a poté je v metodě Session::runGame() naalokován nový objekt třídy GameBetl, do kterého se okopírují důležité informace z původní hry (karty, předák...); původní hra je pak destruována. Podobně následuje výběr hry pro betla a durcha.
Vlastní hra funguje na podobném principu u všech typů her. Využívá se abstraktní třídy Trick (= štych), která obsahuje virtuální metodu pro přidávání karet do štychu. Proto je samotná fáze sehrání štychu implementována už ve třídě Game (metodou playTrick()), ostatní odvozené metody play() pouze obalují tuto metodu o rozhodování. zda hrát ještě dál.
Výpočet skóre je specifický především u normální hry. Pro základní hry se totiž může hrát ještě stovka (co se hraje určuje proměnná gType_), a některý z hráčů může nahlásit sedmu (proměnná hracSedmy). Všechny tyto možnosti jsou při výpočtu zohledněny. Pro betla a durcha se pouze základní hodnota hry (určená konstantami GAME_BETL_VALUE a GAME_DURCH_VALUE) násobí stupněm flekování (proměnná flekStep). U normální hry je cena hry určena konstantami GAME_BASE_VALUE, GAME_BASE_7_VALUE a GAME_BASE_100_VALUE.
Interakce s klienty v průběhu hry probíhá takto: Pokaždé, když server očekává nějakou odpověď od hráče, který je zrovna na tahu, zavolá metodu Session::readCmd(unsigned int) prostřednictvím ukazatele na "rodičovský" objekt třídy Session. Tato metoda (viz popis výše) zpracovává příkazy od klientů, a dál "propustí" pouze zprávy typu game od zadaného hráče.
Zpracování štychu je starostí potomků třídy Trick, tříd BasicTrick a BetlTrick (pro normální hru, respektive betla/durcha). Tyto třídy implementují každá po svém přidávání karet do štychu, určování vítěze štychu a testování, zda lze vůbec (vzhledem k atuálnímu stavu hráčova balíčku) kartu do štychu odhodit. Pro rozhodování jsou uplatňována klasická mariášová kritéria - hráč musí hrát výš, ctít barvu, trumfy,...
Pro uchování informací o kartách se používají dvě základní třídy: Card a CardPack. Třída Card je vlastně obyčejnou strukturou obsahující hodnoty výčtových typů Card::colorType a Card::sizeType pro uložení informace o kartě. Navíc obsahuje konverzní konstruktor z typu std::string a několik statických metod pro převod řetězce na barvu karty a její výšku, a naopak.
Třída CardPack pak "zapouzdřuje" objekt třídy std::vector<Card>, přičemž chování metody addCard(Card) jej spíše přibližuje množině karet.
Jednoduchý klient obsahuje pouze grafickou příkazovou řádku pro komunikaci se serverem. Využívá grafickou knihovnu GTK+ prostřednictvím jejího C++ rozraní, knihovny GTK--.
MessageQueue - třída zapouzdřující síťovou komunikaci
mainWindow - třída hlavního okna aplikace; obsahuje navíc některé funkce pro obsluhu komunikace se serverem
další třídy jednotlivých oken uživatelského rozhraní - aboutWindow, connectionWindow, infoAlert, serverPropertiesDialog