Indice - Progetto di Reti di Calcolatori - Fabio Adani e Marco Chiesi
Progetto

Elementi del gioco

Visto che si intende fare uso della programmazione object-oriented il primo passo della fase di progettazione vera e propria consiste nel progettare una serie di classi che corrispondano agli elementi del gioco.

PLANISFERO, CONTINENTI, TERRITORI

Ogni territorio presente nel gioco è caratterizzato da informazioni statiche (nome del territorio, riferimenti ai territori confinanti, continente di appartenenza) e da informazioni dinamiche (giocatore che ne è in possesso, numero di armate presenti). Questa distinzione fa pensare al fatto che potrebbe essere utile creare due classi distinte per modellare ogni territorio, la prima che memorizza soltano informazioni statiche (Country), la seconda che memorizza soltanto informazioni dinamiche (CountryInfo). Questa scelta ha il vantaggio di ridurre la quantità di informazioni da trasferire durante le partite, in quanto le informazioni statiche sono già note a priori da tutti i giocatori e quindi non necessitano di essere trasmesse.
Per quanto riguarda i continenti, invece, tale problema non si pone neppure in quanto i essi contengono solamente informazioni statiche, come il nome del continente stesso, i territori che ne fanno parte ed il numero di armate a cui dà diritto ogni continente nel caso lo si possieda interamente. Pertanto viene creata un'unica classe Continent che memorizza tali informazioni.
Risulta inoltre utile introdurre una classe World, che rappresenta l'intero planisfero e la sua funzione sarà quella di fare da contenitore di oggetti di tipo Continent e Country visti prima.

CARTE E MISSIONI

Per rappresentare le carte da gioco, si introduce una classe Card che memorizza il tipo (fante, cavaliere, cannone, jolly) di ciascuna carta e il territorio raffigurato sulla carta stessa. Accanto a questa risulta utile avere un'ulteriore classe CardHeap in grado di modellare un insieme di più carte, quale può essere ad esempio il mazzo da cui si pescano le carte dopo la conquista di un territorio oppure l'insieme di carte possedute da ciascun giocatore.
Relativamente alle missioni, si utilizza una classe Mission che si occupea di memorizzare l'obettivo di ogni giocatore e di verificare se tale obiettivo è stato raggiunto in una particolare situazione di gioco.

GIOCATORI

Anche nel caso della modellazione dei giocatori risulta utile distinguere due classi in quanto non è necessario che ciascun giocatore conosca completamente lo stato di ogni altro. Si pensi ad esempio alle carte possedute: non c'è motivo di fare in modo che un giocatore sia a conoscenza delle carte possedute dagli avversari. Questo equivale a risparmiare sulle informazioni che si devono trasmettere. Ad esempio, nel caso delle carte, solo il giocatore che pesca una carta dal mazzo riceve le informazioni contenute in quella carta, mentre gli altri giocatori no. Detto questo, si può pensare di utilizzare una classe PlayerInfo che memorizza soltanto le informazioni di base (nome, colore, stato di vita) ed un'altra classe Player in cui invece si memorizzano informazioni più specifiche e che non devono essere note a tutti i giocatori (carte possedute, numero di armate da posizionare).

Architettura del sistema

La scelta dell'architettura del sistema è naturalmente una delle fase cruciali del progetto, in quanto da essa dipende tutto il funzionamento del sistema. Ci si trova di fronte a diverse possibilità:

  • Architettura client-server, in cui c'è un server unico che mantiene lo stato del gioco ed a cui si collegano i vari client.
  • Architettura peer-to-peer, in cui non c'è un server, o se c'è non ha conoscenza dello stato del gioco ma si limita a ritrasmettere a tutti i giocatori i messaggi che riceve.
  • Architettura ibrida, in cui si ha un server ma la maggior parte delle comunicazioni vengono fatte in modalità peer-to-peer.

Prima di effettuare una scelta tra uno di questi modelli occorre fare alcune considerazioni. Inannzitutto si deve tenere in conto il numero di giocatori che possono partecipare ad ogni singola partita, in questo gioco il numero massimo di partecipanti è 6, il che comporterebbe nel caso di un'architettura peer-to-peer, un elevato numero di connessioni tra i vari giocatori, nonchè una più complicata gestione delle comunicazioni e delle disconnessioni. In secondo luogo, visto che si tratta di un gioco a turni e non real-time non sono richiesti particolari requisiti di velocità, e quindi anche un'architettura client-server potrà servire adeguatamente allo scopo. Inoltre, l'adozione di un'architettura client-server pura ha l'ulteriore vantaggio che lo stato del gioco viene totalmente mantenuto nel server, il che impedisce ad eventuali client "disonesti" di effettuare mosse in contrasto con le regole del gioco. Infatti, visto che ogni modifica di stato viene fatta sul server e solo in seguito comunicata ai client, il server è in grado di controllare le mosse fatte e segnalare errore nel caso tali mosse siano scorrette. Pertanto, sulla base delle considerazioni fatte ora, l'architettura adottata è quella client-server. Di seguito si riporta uno schema che raffigura i vari elementi del sistema e le interazioni fra di essi.

Server

Il server, che è unico nel sistema, deve essere in grado di gestire più partite contemporaneamente. Traducendo questo concetto nell'ambito della programmazione ad oggetti, si può dire che si utilizza una classe RisikoServer, la quale mantiene dei riferimenti a più classi RisikoGame. La classe RisikoServer costituisce un server vero e proprio, nel senso che accetta le connessioni da parte dei client e quindi permette agli utenti di accedere al gioco. Ogni volta che un utente si connette, il server crea un agente (PlayerAgent) che si occupa di gestire completamente la comunicazione con l'utente. Una partita viene avviata dal server quando viene raggiunto un sufficiente numero di giocatori. La classe RisikoGame è quella che effettivamente implementa l'astrazione di partita ed infatti sè il contenitore dello stato della partita, vale a dire tutte le informazioni che riguardano i giocatori, come ad esempio, i territori occupati da ogni partecipante ed il relativo numero di armate, le carte possedute, le missioni, i turni, ecc.

In base a quanto visto nella fase di analisi, relativamente alle varie mosse possibili attuabili dai giocatori, si può modellare ogni partita come un sistema a stati finiti, in cui ogni stato corrisponde ad uno stato del gioco. Gli stati sono i seguenti:

  • NO_MOVE: questo stato significa che nessuna mossa è possibile da parte di nessun giocatore. Questo avviene prima che la partita abbia inizio prima che si raggiunga un sifficiente numero di giocatori oppure alla fine della partita, quando è stato proclamato un vincitore.
  • PLACE_ARMIES: ci si trova in questo stato quando ci sono giocatori che hanno delle armate da posizionare. Questo si verifica all'inizio del gioco subito dopo la distribuzione delle armate ed anche all'inizio di ogni utrno di gioco.
  • MUST_TRADE: questo si verifica quando il giocatore corrente possiede un numero di carte maggiore di 5 nel qual caso è obbligato a scambiarle prima di poter eseguire qualsiasi altra mossa.
  • ATTACK: in questo stato il giocatore corrente può sferrare i suoi attacchi.
  • WAITING_FOR_MOVE: questo stato viene raggiunto immediatamente dopo un attacco andato a buon fine (conquista di un territorio) e consiste nell'attendere che l'attaccante segnali quante armate intende muovere nel territorio appena conquistato. Questo perchè non è permesso che un territorio rimanga scoperto.

Client

Il client del gioco, come si può vedere dal precedente schema, è strutturato in due parti. La prima (RisikoClient) si occupa della comunicazione con il server, mentre la seconda (RisikoUI) costituisce l'interfaccia grafica che si occupa di gestire l'interazione con l'utente. Nel client viene mantenuta una copia dello stato della partita che però può essere modificato solamente modo indiretto in seguito a messaggi ricevuti dal server.

Analogamente a quanto visto per il server, anche nel client si possono individuare diverse situazioni di gioco:

  • WATCHING: in questo stato l'utente può solo guardare e si verifica prima dell'inizio o dopo la fine della partita, oppure quando il turno è di un altro giocatore.
  • MUST_TRADE: il giocatore è obbligato a scambiare le carte.
  • PLACE_ARMIES: l'utente deve posizionare le armate che ha a disposizione sui suoi territori.
  • ATTACK: il giocatore può sferrare degli attacchi
  • MOVE_ARMIES: il giocatore sta muovendo le sue armate da un territorio ad un altro. questa possibilità si verifica sia appena dopo una conquista, sia quando il giocatore decide di fare degli spostamenti (è consentito soltanto uno spostamento per ogni turno).

Protocollo di comunicazione

Il server ed i client ovviamente comunicano in modo bidirezionale, ed inoltre ogni messaggio transita sempre attraverso il server. Ad esempio, quando un utente attua una mossa agendo sulla propria interfaccia grafica, quest'ultima lo comunica al client che a sua volta invia le informazioni relative all'azione al server. Dal lato server queste informazioni vengono ricevute dal relativo agente che tenta di modificare lo stato della partita in base al comando ricevuto dall'utente. Se tale comando è lecito, lo stato viene modificato e di conseguenza tutti i client che partecipano alla partita ricevono una notifica di questo aggiornamento, sempre tramite l'agente. Se invece la mossa non è lecita, viene inviato un messaggio di errore al giocatore che l'ha effettuata. Chiaramente nella maggior parte dei casi è opportuno evitare di inviare mosse illegali al server, per cui si prevede un controllo preventivo da parte del client.

La comunicazione avviene in modo asincrono tramite scambio di messaggi. Naturalmente ci sono diversi tipi di messaggio, e di seguito se ne riporta l'elenco con il relativo significato. Ogni messaggio, è caratterizzato, oltre che dal tipo, anche da altre informazioni specifiche di quel tipo di messaggio. Ad esempio, un messaggio di attacco specifica qual è il territorio da cui parte l'attacco, qual è quello attaccato ed il numero di dadi usati, e così via.

MESSAGGI DAL CLIENT AL SERVER

Messaggio Significato
JOIN_GAME ingresso in partita di un giocatore
LEAVE_GAME abbandono (volontario) di un giocatore
ATTACK attacco di un territorio, vengono specificati i territori di arrivo e di partenza ed il numero di dadi usati
M_ATTACK attacco multiplo, analogo al precedente ma l'attacco procede fino a quando è possibile
NUM_ARMIES comunicazione del numero di armate da spostare in un territorio appena conquistato
PLACE_ARMIES posizionamento di una o più armate in un determinato territorio
MOVE_ARMIES spostamento di una o più armate tra due territori adiacenti appartenenti allo stesso giocatore
TRADE_CARDS scambio di una combinazione di carte
END_TURN passaggio del turno al giocatore successivo

MESSAGGI DAL SERVER AL CLIENT

Messaggio Significato
JOINED_GAME ingresso in partita di un giocatore, conseguente ad un messaggio JOIN_GAME inviato da un utente
END_GAME fine della partita, con indicazione di chi è il vincitore
LEAVED_GAME abbandono da parte di un giocatore, può essere dovuto ad un abbandono volontario o a una disconnessione
SEND_INFO inizializzazione di una partita, consiste nell'invio al client dello stato iniziale del gioco
TURN comunicazione del turno corrente
ADDED_ARMIES assegnazione di armate ad un certo giocatore, si verifica all'inizio di ogni turno o quando un giocatore scambia le carte
PLACED_ARMIES posizionamento di armate in un territorio, conseguente ad un corrispondente messaggio PLACE_ARMIES inviato da un client
MOVED_ARMIES movimento di armate, conseguente ad un corrispondente messaggio MOVE_ARMIES inviato da un client
ATTACKED attacco, conseguente ad un corrispondente messaggio ATTACK (o M_ATTACK) inviato da un client
ATTACK_RESULT risultato di un attacco, contiene le informazioni che indicano l'esito di un attacco, cioè il risultato dei dadi e le armate perse da attaccante e difensore nello scontro
SEND_CARD invio di una carta ad un giocatore, in seguito alla conquista di un territorio
SEND_CARDS invio di più carte ad un giocatore, in seguito al passaggio delle carte di un giocatore eliminato a quello che lo ha distrutto
REMOVE_CARDS ritiro delle carte ad un giocatore, conseguente ad uno scambio di carte o all'eliminazione del giocatore
TRADED_CARDS esito di uno scambio di carte, indica il giocatore che ha effettuato lo scambio e quante armate ha ricevuto in cambio, conseguente ad un messaggio TRADE_CARDS inviato da un client
MISSION invio della missione, usato nella preparazione della partita

Ai precedenti messaggi vanno aggiunti messaggi di errore, che vengono inviati ad esempio, in seguito al tentativo di eseguire una mossa illegale.

Il protocollo funziona nel seguente modo:

  • Appena un client si connette invia un messaggio JOIN_GAME ed attende l'arrivo di tutti i partecipanti
  • Quando tutti i giocatori sono arrivati, il server inizializza la partita stabilendo la successione dei turni, l'assegnazione dei territori, delle armate e delle missioni, etc., e spedisce queste informazioni a tutti i client, tramite i messaggi SEND_INFO e MISSION
  • A questo punto ogni giocatore comincia ad effettuare il piazzamento delle armate che ha inizialmente a disposizione e lo notifica al server con il comando PLACE_ARMIES
  • Quando tutte le armate sono state disposte sul planisfero, la partita ha inizio ed il server lo segnala mandando un messaggio TURN indicando qual è il giocatore a cui spetta muovere.
  • Eventualmente prima di segnalare il turno, viene mandato un messaggio ADDED_ARMIES che assegna al giocatore le armate supllementari a cui ha diritto.
  • Ad ogni turno, il giocatore corrente può fare le sue giocate, ovvero posiziona eventuali armate (PLACE_ARMIES), scambia le carte (TRADE_CARDS), attacca (ATTACK, M_ATTACK, NUM_ARMIES) e muove le armate (MOVE_ARMIES), fino alla fine del suo turno (END_TURN).
  • Chiaramente per ognuno dei precedenti messaggi, il server controlla che lo stato del gioco sia compatibile con l'esecuzione dell'azione, e in tal caso attua i dovuti cambiamenti e notifica tutti i client (compreso quello che li ha generati) tramite i messaggi visti in precedenza.
  • Il turno viene quindi via via passato in successione ai vari partecipanti fino a quando uno non è riuscito a completare la propria missione, evento che segna la fine della partita (END_GAME).

Dalla precedente descrizione si possono distinguere due fasi in ogni partita. Nella prima di fase, in cui i giocatori effettuano la disposizione iniziale delle armate, tutti i giocatori possono comunicare contemporaneamente sul server. Nella seconda fase, invece, relativa al gioco vero e proprio c'è un solo giocatore (quello corrente) per volta che può comunicare col server. Questo significa che se uno degli altri partecipanti invia dei messaggi al server, riceverà certamente un messaggio di errore. Un caso a parte è il messaggio LEAVE_GAME che può essere ricevuto in un qualsiasi momento e che va attuato in modo immediato. In particolare se il giocatore che ha lasciato la partita era proprio quello corrente si deve provvedere a passare il turno a quello successivo, altrimenti sarà sufficiente disattivarlo.

Indietro Inizio pagina Avanti
Indice   Fabio Adani e Marco Chiesi