Zadání

Základní zadání č. 17
Počítačové hry
  1. Vytvořte program realizující hru lodě, piškvorky, odebírání zápalek apod.
  2. Pro realizace využijte vhodného grafického rozhraní.
  3. Využijte služeb UNIXu a BSD socketů.
Hadi - síťová multiplayerová hra na způsob tronu

Uživatelská dokumentace

Server

Ke spuštění serveru slouží binarka s prozaickým názvem server. Má jeden nepovinný vstupní parametr - název souboru, do kterého se případně bude ukládát záznam komunikace serveru s klienty (bližší popis viz programátorská dokumentace).
Po spuštění server ještě potřebuje znát počet hráčů. Na tuto informaci se vyptá v konzoli. Akceptuje celé číslo v rozmezí 2 až 16. Poté čeká na připojení všech klientů. V průběhu hry je otevřeno okno pozorovatele, ve kterém je vidět průběh hry. Server nelze ukončit, dokud porbíhá hra. Po ukončení hry vyčkává na stisk klávesy.

Klient

Ke spuštení klienta slouží binárka hadice. Opět má jeden nepovinný vstupní parametr - název souboru, do kterého se bude případne bude ukládat záznam komunikace se serverem. Po spuštění se klient nejprve zeptá na jméno serveru (akceptuje jak doménové jméno tak i IP adresu). a na nick, pod kterým bude hráč ve hře vystupovat. Hada hráč ovládá kurzorvými klávesami vlevo a vpravo. Pokud zvítězí hráč, má právo "spanilé jizdy", kterou ukončí vlastní sebevraždou, či stiskem klavesy escape. V průběhu hry se může hráč odpojit. Učiní tak stiskem klávesy escape.


Programátorská dokumentace

Koncept komunikace

Hra je závislá na předchozím stavu. To znamená, že je potřeba koordinace všech hráčů. Proto server i klient běží v jediném procesu. Synchronizace je pak docíleno blokováním chodu programu nad čtením dat ze sítě.

Protokol

Komunikaci začíná klient, zasláním zprvá hello, která má následující tvar:
int nick_len // délka nicku char nick[nick_len] // pole znaků o délce nick_len, které nese informaci o nicku
Server vyčká, dokud se takto nepředstaví všichni klienti a pak každémů z nich odpoví následující zprávou:
int no_players // počet hráčů int x,y,fi // inicializační stav klientova hada
Dále následuje seznam nicků ostatních hráčů (je zřejmé, že tato zprává bude zopakována no_players-krát:
int nick_len // opět délka nicku char nick[nick_len] // nick
Touto zprávou říká server, že hra právě začala.
V každém kroku (tahu) hry posílá klient následující zprávu:
int x,y,fi // aktuální stav hráče bool end_request // je-li nastaveno na true, žádá tím klient o ukončení hry, na kterou server již neodpovídá, pouze ukončí spojení
Pokud položka end_request byla nastavena na false, server pokračuje v komunikaci až v okamžiku, kdy předchozí zprávu zašlou všichni klienti. Formát zprávy serveru je následující:
int game_state // stav hry: 0 - hra probíhá; 1 - hra je u konce int your_score // aktuální skóre hráče bool r_u_alive // true - pokud hráč je stále naživu (nekolidoval s jiným hráčem nebo se nepokusil opustit hrací plochu)
Bez ohledu na hodnotu položky game_state server pokračuje v komunikaci ješte posloupností zpráv, kterými informuje klienta o stavu ostatních hráčů. Pro každého hráče (vyjma právě obsluhovaného klienta) posílá server zprávu v tomto formátu:
int x,y // pozice soupeře (kam popolezl) int score // skóre soupeře
Teď již má klient veškeré informace, které v tomto tahu potřebuje (čímž je tento tah ukončen) a opět posílá zprávu o svém stavu...

Struktura systému

Pro síťovou komunikaci jsem vytvořil knihovnu netstream, která má následující objetky: Knihovna netstream má ještě dvě funkce
Ke svému chodu jak server tak i systém využívá další dva moduly:

zdrojový soubor main.cpp - klient

Hlavní modul pro klienta (soubor main.cpp) má tyto globální proměnné:
int x,y,fi,fiadd; // stav lokálního hráče bool alive; // je stále naživu? int score; // jeho skóre bool end_request; // požádal o ukonční sezení int game_state // stav hry (0 - běží, 1 - končí) typedef struct{ // struktura udržující stavy vzdálených hráčů int x, y, score; char *nick; } remote_player; int remote_players_count; // počet vzdálených hráčů remote_player remote_players[MAX_PLAYERS]; // jejich stavy
Dále se v tomto modulu nachází funkce, které globální proměnné posílají serveru nebo zprávy ze serveru ukládájí do globálních proměnných:
void send_hello(Socket s, char *nick) // pošle hello serveru void recv_hello(Socket s) // uloží hello do globálních proměnných void send_info(Socket s) // pošle stav lokálního klienta serveru void recv_info(Socket s) // získá stavy vzdálených hráčů ze serveru
A konečně se zde nachází funkce na zobrazování hráčů a obsluhu kláves
void handle_keys() // obsluha kláves int xa_calc(float x) // na základě úhlu fi (x) vypočte x-ový přírůstek int ya_calc(float x) // na základě úhlu fi (x) vypočte y-ový přírůstek void step() // na základě fi nastaví nový stav void draw() // vykreslý hady void init() // inicialzace globálních proměnných
A nesmí chbět funkce main:
int main(int argc, char** argv) // <--- to je ona

zdrojový soubor server_main.cpp - server

Takto jsou uloženy stavy všech hráčů:
typedef struct{ int x,y,fi; // state int score; // score of player bool alive; // is alive? bool connected; // is connected? AcceptedSocket s; // communication socket char* nick; // nick of player } remote_player; int remote_players_count; // count of players remote_player remote_players[MAX_PLAYERS];
I zde jsou funkce, které komunikují po síti (tentokrát s klienty) a to buď tak, že globální proměnné posílají klientům a nebo stavy klientů zaznamenávají do globálních proměnných:
void recv_hello(int no) // přijme hello zprávu od klienta číslo no void send_hello(int no) // pošle hello zprávu klientovy číslo no void recv_info(int no) // získá stav klienta void send_info(int no, int game_state)// pošle stav ostatních hráčů a stav hry
Dále tu máme funkce, které obstarávájí různé úkony nutné pro hru
void inc_score_of_alive_players() // zvýší skóre všem živým hráčům (voláno v okmažiku umrtí hráče) void init_players() // inicializuje stavy všech hráčů void done_players() // obešle všechny hráče s informací, že hra končí bool exists_alive_player() // zjistí, zda existuje ještě hráč, který je naživu bool exists_connected_player() // zjistí, zda existuje ještě hráč, který je připojen int xa_calc(float x) // vypočte x-ový přírůstek na základě úhlu fi (x) int ya_calc(float x) // vypočte y-ový přírůstek na základě úhlu fi (x) bool collide(int no) // funkce, která zjišťuje, zda nějaký z hráčů nehavaroval a podle toho nastaví příznak alive void step(int no) // nastavý nový stav hráče číslo no void draw() // vykreslí všechny hráče int main(int argc, char** argv) // <--- funkce main, co více říci