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

Architettura del Sistema

L'applicazione prevede la presenza di tre componenti interagenti.
Il Server ha il compito di fare da intermediario nella fase iniziale di instaurazione della connessione diretta tra i due client e di fare da ponte per permettere al gioco di essere inserito come modulo all'interno di NetGame.
I Client contengono il grosso della logica del gioco e si distinguono, nella fase iniziale, in master e slave. Nel seguito del capitolo si chiarirà il significato di tale distinzione.
La figura seguente mostra graficamente quanto detto.

Come si può vedere, e come si chiarirà più approfonditamente in seguito, la comunicazione tra i partecipanti al gioco (i clients) avviene con modalità peer-to-peer. Si è scelta questa soluzione perché è quella che permette un minore tempo di latenza nel tragitto che devono compiere i dati rispetto alle alternative (client-server e ibrida) e già si è detto dell'importanza, in una applicazione real-time, di limitare tale fattore.
Inoltre essendo i partecipanti solo due, non ci sono problemi di distribuzione di messaggi e lo stato della partita sarà mantenuto concordemente dai giocatori.

Descrizione dell'Interazione

Durante un match l'interazione tra clienti attraversa diverse fasi.

Fase iniziale : instaurazione della comunicazione tra i clients

Evidentemente la prima cosa da fare è stabilire un canale di comunicazione tra i due gicatori. Concretamente si tratterà di creare due socket in entrambi i contesti e di collegarle agli estremi di uno stesso canale.
In questa fase inizialmente solo il server, come dettato dalle specifiche dei server di giochi ospitabili in NetGame, ha conoscenza di tutti i partecipanti. Proprio esso ha il compito di instaurare tale connessione e lo fa come mostrato in figura.

In dettaglio le operazioni sono le seguenti :

  1. Il server invoca il metodo remoto prepare() sul master
  2. Il master crea una ServerSocket su una porta libera ed un InfoKey per l'accesso
  3. Il master comunica (in realtà valore di ritorno della prepare()) al server l'oggetto InfoKey
  4. Il server invoca sullo slave il metodo remoto start()
  5. Lo slave crea una socket e...
  6. ... la connette a quella del master

L'oggetto InfoKey contiene tutte le informazioni necessarie allo slave per individuare il master; in particolare :

  1. Indirizzo del nodo su cui si trova il master
  2. Porta della socket su cui il master attende connessioni
  3. Password

In realtà la password non è altro che un intero. Esso è generato in maniera random sul master in seguito alla chiamata da parte del server della funzione prepare() ed è utilizzato per evitare che un altro processo (ad esempio un client di un'altra partita di PingGame) possa intromettersi nella partita in questione.
Anche la porta viene scelta dinamicamente, facendo una serie di tentativi finchè la creazione non ha successo.

Fase di settaggio dei parametri

In questa fase, che ha lo scopo di definire tutti i parametri che caratterizzeranno la partita, i giocatori si scambiano messaggi della classe SetParMessage. (In realtà si scambieranno sottoclassi di tale classe, specifici per il tipo di parametro che andranno a modificare).
Anche se la tassonomia di classi sarà affrontata con maggior dettaglio nell'implementazione, qui se ne da comunque un accenno per poter chiarire meglio il funzionamento.

Tutti i messaggi usati in PingGame sono oggetti di sottoclassi della classe PingGameMessage.
Essa presenta una variabile intero di nome mode. Tale variabile ha il compito di distinguere un tipo di messaggio dall'altro. e potrà così essere utilizzata dal ricevente per ricostruire adeguatamente l'informazione che gli è stata trasmessa.
Quindi la forma generale di ogni messaggio è :

MODE
...

In questa fase i messaggi hanno la struttura :

MODE
PARAMETER
VALUE

Nel campo mode tutti i messaggi di classe SetParMessage presentano il valore SETPARMODE (0 in questa implementazione).
Il campo parameter serve ad identificare il tipo di parametro a cui ci si riferisce. I parametri che possono essere modificati sono stati presentati in fase di analisi.
Il campo value contiene il valore di tale parametro. Può avere natura diversa a seconda del parametro; in particolare può essere un intero, un reale o una stringa.

Come già si è detto in questa fase vi è ancora distinzione forte tra master e slave. In particolare il master ha il diritto di modificare tutti i parametri tranne quelli personali dello slave, mentre lo slave può modificare esclusivamente quelli.

La partita

A questo punto, quando il master decide che i parametri sono appropriati, ha inizio la partita.

Possiamo schematizzare il comportamento dei clients durante la partita con un diagramma degli stati. Un giocatore può trovarsi in uno dei seguenti stati :

  1. MYCTRLSTATUS
  2. TRASMITCTRLSTATUS
  3. YOURCTRLSTATUS

Il giocatore ha sempre il controllo della sua racchetta. A seconda dello stato in cui si trova può avere anche il controllo della pallina ed il compito di segnalare eventuali goals.
Lo stato della partita, rappresentato oltre che dallo score, dalle posizioni e velocità delle due racchette e della pallina, è quindi mantenuto in modo distribuito da entrambi i giocatori.
In particolare lo stato della pallina viene alternativamente gestito dall'uno o dall'altro. Si è fatta al riguardo la scelta di assegnare il calcolo della sua posizione e della sua velocità al giocatore maggiormente interessato in quel momento, ovvero a quello che presidia la zona in cui la pallina si trova. Questo perchè tale giocatore è quello che presumibilmente si appresta a colpire la pallina, e che quindi deve sapere con esattezza i suoi dati, prevenendo i già citati problemi connessi alla latenza di comunicazione.

In particolare nel primo stato (MYCTRLSTATUS) il giocatore ha il controllo della pallina e ne trasmette la posizione anche all'avversario, che si troverà necessariamente nel terzo stato (YOURCTRLSTATUS).
Nel secondo stato (TRASMITCTRLSTATUS) il giocatore calcola la posizione della pallina, ma solo per uso locale, cioè non la trasmette all'avversario.

Gli eventi che possono generare in modo asincrono una transizione di stato sono di due tipi :

  • Ricezione di un messaggio dall'avversario
  • Movimento della pallina in determinate zone del campo

La seguente figura mostra schematicamente la transizione tra gli stati, specificando gli eventi che li causano.

Il master comincia la partita nello stato MYCTRLSTATUS, in quanto la pallina è posta inizialmente ferma nella sua zona. Lo slave invece è inizialmente in YOURCTRLSTATUS.
Il passaggio dal primo al secondo stato è effettuato quando viene verificata localmente la seguente condizione (PallaOver):

la pallina è uscita dall'area del giocatore. Ovvero, per la prima volta, le coordinate della pallina sono esterne alla sua area e la sua velocità ha una componente orizzontale verso l'area dell'avversario.

Nel secondo stato si ha già comunicato all'altro la necessità che egli assuma il controllo della pallina, però non si è ancora ricevuta la sua conferma, quindi si continua a gestire lo stato della pallina per poterla comunque disegnare in questa fase di "transizione".

Per poter comprendere meglio il comportamento in questa fase vengono ora elencati in dettaglio i formati dei messaggi trasmessi durante la partita. Si tralascerà il campo mode che, come detto, serve solo ad identificare il tipo di messaggio, ed è comunque da intendersi come primo campo di ogni messaggio.

MyControlInfoMessage
CURRENTFRAME
X
Y
BALLX
BALLY

CURRENTFRAME è un intero che indica il numero di frame a cui si riferiscono le informazioni. Viene usato per mantenere il più possibile sincronizzate le visione del gioco dei due clients.
(X,Y) sono le coordinate della racchetta del mittente.
(BALLX, BALLY) sono le coordinate della pallina.

YourControlInfoMessage
CURRENTFRAME
X
Y

CURRENTFRAME è un intero che indica il numero di frame a cui si riferiscono le informazioni.
(X,Y) sono le coordinate della racchetta del mittente.

ChangeControlMessage
CURRENTFRAME
X
Y
BALLX
BALLY
BALLVX
BALLVY

CURRENTFRAME è un intero che indica il numero di frame a cui si riferiscono le informazioni.
(X,Y) sono le coordinate della racchetta del mittente.
(BALLX, BALLY) sono le coordinate della pallina.
(BALLVX, BALLVY) sono le componenti della velocità della pallina.
Questo messaggio viene inviato quando viene passato il controllo della pallina da un giocatore ad un altro. Le informazioni che vengono quindi inserite devono avere maggiore precisione rispetto ai casi precedenti, in cui l'uso che ne faceva il ricevente era semplicemente in veste grafica. In particolare in questo caso sia la posizione che la velocità della pallina vengono rappresentati con valori reali e non interi.

FinishMatchMessage, GoalScoredMessage, FinishMatchMessage

Questi messaggi non hanno bisogno di ulteriori campi oltre al mode in quanto il loro significato è unico ed inequivocabile.

Vediamo ora più in dettaglio i comportamenti dei giocatori in ognuno dei tre stati.
Si è già detto come esistano due tipi di eventi asincroni che causino l'evoluzione dello stato :
al gioco è stata imposta una determinata frequenza di aggiornamento dell'immagine (20 Hz). Ad ogni frame facciamo corrispondere un evento, nel senso che la disposizione della pallina può portare ad una transizione di stato o comunque all'invio di opportuni messaggi all'avversario (si pensi alla realizzazione di 1 goal). Questo tipo di evento possiamo quindi considerarlo sincrono, nel senso che si verifica pressoché costantemente ogni 50ms (vedremo poi in seguito come cercare di ottenere una frequenza unica anche su macchine diverse).
Questo non si può dire per il secondo tipo di evento, che corrisponde ad un messaggio ricevuto. Evidentemente questo aspetto è molto connesso all'affidabilità della rete, all'eventuale presenza di buffer, ecc. Potrebbe per esempio succedere che la macchina degli stati riceva consecutivamente tre eventi interni e poi tre eventi da messaggio, mentre l'ideale sarebbe un'alternanza tra i due tipi (almeno per la fluidità dell'immagine, anche se poi si vedrà un possibile metodo per cautelarsi sotto questo aspetto).

Nella trattazione seguente non sono considerati solo gli eventi significativi e che effettivamente si possono verificare con un giocatore in un determinato stato. (Quindi, per es, non è trattato il caso in cui un giocatore nello stato MYCTRLSTATUS riceva una segnalazione di goal da parte dell'avversario. Questo non è possibile, perchè se il giocatore è in tale stato significa che la pallina è nella sua area, e quindi se c'è un goal evidentemente dovrà essere verificato da lui ed ai suoi danni. Se si presentano condizioni incompatibili di questo tipo, significa che si è verificato qualche errore e lo stato della partita non è consistente alle due parti. La partita viene perciò forzatamente terminata).
Nelle tabelle seguenti il primo campo individua il tipo di evento. Per MSG si intende la ricezione di un messaggio, mentre con AGG la verifica di un nuovo frame.


Operazioni in MYCTRLSTATUS

TIPO EVENTO
DESCRIZIONE EVENTO
NUOVO STATO
MSG. INVIATO
MSG
YourControlInfoMessage MYCTRLSTATUS
-
AGG
Palla transita fuori dalla mia zona TRASMITCTRLSTATUS ChangeControlMessage
AGG
Goal subito e partita finita
FINE!
GoalScoredMessage;
FinishMatchMessage
AGG
Goal subito e partita non finita MYCTRLSTATUS GoalScoredMessage
AGG
Nessuno dei precedenti MYCTRLSTATUS MyControlInfoMessage


Operazioni in TRASMITCTRLSTATUS

TIPO EVENTO
DESCRIZIONE EVENTO
NUOVO STATO
MSG. INVIATO
MSG
YourControlInfoMessage TRASMITCTRLSTATUS
-
MSG
MyControlInfoMessage YOURCTRLSTATUS
-
AGG
-
TRASMITCTRLSTATUS YourControlInfoMessage


Operazioni in YOURCTRLSTATUS

TIPO EVENTO
DESCRIZIONE EVENTO
NUOVO STATO
MSG. INVIATO
MSG
MyControlInfoMessage YOURCTRLSTATUS
-
MSG
ChangeControlMessage MYCTRLSTATUS
-
MSG
GoalScoredMessage YOURCTRLSTATUS
-
MSG
FinishMatchMessage
FINE!
-
AGG
-
YOURCTRLSTATUS
YourControlInfoMessage

Essendovi una zona neutra al centro del campo, in cui nessuno dei due giocatori può agire, si è introdotta la possibilità di recuperare la pallina da quella zona (mediante pressione del tasto Esc), sempre che la sua velocità sia inferiore ad un determinato valore (valBMin). In quel caso la pallina viene posta nel zona del giocatore nel primo stato in quel momento e la partita procede regolarmente.

Terminazione della partita

La partita può terminare per diverse ragioni. Il modo più naturale è il raggiungimento da parte di uno dei due giocatori della quantità di goals prestabilita per vincere. Essendo però un gioco di rete si possono verificare anche problemi di comunicazione, o la caduta di uno dei nodi che ospitano i clients. In questi casi la partita viene forzatamente terminata.
In ogni caso la routine di chiusura si dovrà preoccupare di fare pulizia riguardo alle risorse allocate (socket, thread,...) e si dovrà provvedere ad avvertire il server del gioco (e di conseguenza anche tutto l'environment NetGame) della terminazione.

Miglioramenti possibili

Già a livello di progetto un miglioramento possibile dell'applicazione può essere ottenuto disegnando, in corrispondenza del frame locale n, non l'ultimo frame remoto ricevuto, ma una sua proiezione all'istante n. Perchè questo sia possibile occorrerebbe però, anche nel primo e nel terzo stato, comunicare all'avversario non solo la posizione della mia racchetta ma anche la sua velocità ed eventualmente la sua accelerazione. Solo così infatti si può predire con una certa accuratezza la posizione in un istante successivo.
Di conseguenza la comunicazione si appesantirebbe.
(E' stata comunque implementata nella fase iniziale anche questa soluzione, non ottenendo però buoni riscontri dal punto di vista della fluidità dei movimenti).

Struttura interna del client

I client durante una partita si trovano a dover compiere più azioni contemporaneamente :

  • Acquisire l'input dell'utente da tastiera
  • Ricevere i messaggi dall'avversario
  • Aggiornare lo schermo ad una refresh-rate costante.

Queste operazioni possono essere agevolmente assegnate a tre Thread separati :

  • InputThread. Questo thread controlla ad intervalli regolari quali tasti della tastiera (tra quelli significativi per l'applicazione, ovvero i 4 tasti direzionali e l'ESC) sono premuti. Aggiorna di conseguenza degli opportuni contatori che saranno poi utilizzati da UpdateThread per valutare la forza da applicare alla racchetta in quell'intervallo di tempo.
  • SocketThread. Questo thread è continuamente in attesa sulla socket di comunicazione con l'avversario per ricevere i messaggi da esso inviati. Si dovrà occupare (eventualmente affidandosi ad altre classi) della decodifica dei messaggi e delle contromosse essendo, come detto, la ricezione di un messaggio un evento di transizione.
  • UpdateThread. Questo thread ad intervalli regolari aggiorna la finestra della partita. Affidandosi alle formule già presentate nell'analisi (o meglio ad una discretizzazione di queste) calcolerà la posizione della sua racchetta ed eventualmente, a seconda dello stato in cui si trova, anche quello della pallina. Controllerà se si sono verificati eventi significativi e nel caso farà le opportune transizioni di stato oltre a comunicare all'avversario la sua nuova visione della partita.

Questi thread dovranno operare su aree di memoria comuni per scambiarsi i risultati delle loro elaborazioni. Saranno quindi necessarie delle tecniche di sincronizzazione. Java ci viene in aiuto anche in questo caso mettendo a disposizione, già nella classe Object, metodi di sincronizzazione (richiesta di lock, attesa (wait) e notificazione (signal)) su oggetti.
In particolare, InputThread e UpdateThread dovranno acquisire il lock dell'oggetto semaphore prima di accedere, in scrittura (InputThread) o in lettura (UpdateThread), alle variabili che rappresentano l'input dell'utente.
Inoltre SocketThread e UpdateThread dovranno acquisire il lock dell'oggetto statusSem prima di effettuare le operazioni (transizioni di stato, modifica dello stato inteso come coordinate e velocità, invio di messaggi) conseguenti alla valutazione di un evento.

Discretizzazione dei calcoli

Le formule differenziali presentate nell'analisi per calcolare traiettorie ed urti di pallina e racchette vengono in realtà discretizzate considerando solo alcuni istanti di tempo, in particolare quelli in cui viene aggiornata la finestra.
Se due istanti di tempo successivi : t-1 e t, posizione, velocità ed accelerazione all'istante t sono calcolate in funzione di quelle all'istante t-1, come espresso dalle seguenti equazioni :
Esse assumono quindi la seguente forma :

dove in spinta si tiene conto dei comandi immessi dall'utente nell'intervallo di tempo tra i due istanti suddetti.

Integrazione in NetGame e modalità Stand-Alone

PingGame può essere inserito sia come modulo all'interno di NetGame che giocato in modalità stand-alone.

Per essere riconosciuto da NetGame, le classi che lo implementano dovranno strutturarsi tassonomicamente secondo le specifiche proprie dell'ambiente NetGame. Questo è realizzato, come si può vedere nell'implementazione.

Si è visto come avviene la connessione iniziale tra i giocatori nel caso che il gioco sia integrato in NetGame.

Nel caso che vi si voglia giocare anche come applicazione a se stante è possibile farlo. In questo caso, però, mancando l'interazione da parte del server (almeno in questa implementazione) lo slave dovrà saper in altro modo il nodo, la porta e la password per connettersi al master. I valori di default per porta e password sono rispettivamente 5000 e 0.

Indietro Inizio pagina Avanti
Indice   Fabio Adani e Marco Chiesi