HungryMen

sommario

Requisiti frecciaindice.png

Si realizzi un videogame multiplayer online, ispirato al celebre Pac-Man, nel quale diversi giocatori si affrontano in una mappa labirintica al fine di totalizzare, entro un tempo limite, il punteggio più alto. Lungo il percorso i players troveranno una serie di bonus che contribuiscono a incrementare il punteggio. Sono previsti, inoltre, alcune tipologie di PowerUp che daranno al player poteri speciali per un tempo limitato:
  • SpeedUp: aumenta la velocità del player;
  • Invincible: rende il player immune dagli attacchi degli altri players secondo le modalità descritte al punto successivo.
Si richiede di sviluppare il gioco secondo una modalità multi-environment; a tal fine la mappa del gioco deve poter essere espandibile in base al numero di giocatori.

Dinamiche del gioco frecciaindice.png

Lo scopo del gioco è riuscire a totalizzare il punteggio massimo entro lo scadere della partita. Oltre ai bonus extra, un giocatore può aumentare i propri punti ed ostacolare gli avversari "mangiandoli". Di default, un player può eliminare un nemico cogliendolo alle spalle o lateralmente; nel caso in cui, invece, si scontrino frontalmente, i due players verranno respinti nelle relative direzioni opposte senza alcun effetto negativo per entrambi. Qualora un player possieda il PowerUp Invincible avrà la capacità di eliminare senza restrizioni qualsiasi nemico che non abbia anch'esso lo stesso PowerUp; diversamente vengono applicate le normali regole di gioco sopra indicate. Come accennato prima, la partita ha una durata prestabilita. Nel momento in cui un player viene eliminato la sua partita si considera conclusa ma avrà la possibilità di monitorare la situazione dei punteggi e il tempo rimanente della partita a cui aveva preso parte. I giocatori non possono partecipare ad una partita già iniziata e perciò devono attenderne la fine; questo per garantire un'esperienza di gioco equa a tutti i partecipanti dato che, essendo una sfida a tempo con un punteggio finale, chi si aggiungesse in seguito all'inizio della partita risulterebbe da un lato svantaggiato poiché gli altri giocatori naturalmente avrebbero già maturato un punteggio, dall'altro potrebbe modificare le sorti della partita, rivelandosi un potenziale disturbo per gli altri partecipanti.

Glossario frecciaindice.png

Player: rappresentazione del giocatore all'interno del gioco. Giocatore: utente. Bonus: incrementatori di punteggio. Mappa: area giocabile.

Architettura frecciaindice.png

Componenti frecciaindice.png

  • Giocatore/Client
  • Sottomappa/Server
  • Dispatcher
  • Nodi

Strategia risolutiva frecciaindice.png

Volendo realizzare un sistema scalabile e multi-environment e dopo aver analizzato diverse soluzioni, siamo giunti alla conclusione che una strategia risolutiva efficace potrebbe essere quella di adottare un paradigma architetturale di tipo three-tier. L'intero sistema verrà suddiviso in tre moduli distinti e indipendenti, ognuno dei quali avrà un compito ben preciso: un modulo si occuperà di gestire l'interfaccia utente, permettendo al giocatore di interagire e monitorare lo stato del gioco; un altro modulo si dedicherà alla business logic, ovvero alla logica applicativa del videogame; l'ultimo modulo servirà a coprire logiche funzionali del gioco, alleggerendo la mole di lavoro del secondo modulo, oltre a gestire i dati persistenti dell'applicazione. Trattandosi di programmazione distribuita, chiaramente i diversi moduli sono distribuiti su diversi nodi della rete. Infine, i moduli cooperano tra loro secondo lo stile client-server. {style:type=div|align=center}{style:type=div}{image:schemalv.jpg}{style} {style:type=div|align=justify} Come da requisiti, per riprodurre un ambiente multi-environment, si è deciso di suddividere l''intera mappa in più sottomappe autonome, ognuna situata su una macchina diversa, che fungono da server e vengono allacciate dinamicamente al sistema in base al numero totale di giocatori di una partita. Ogni sottomappa mantiene traccia della posizione dei players e degli altri elementi di gioco e ne gestisce le interazioni tra questi ultimi, svolgendo una parte, quindi, di business logic, oltre alla gestione dei dati persistenti. Gli utenti sono client del sistema software distribuito e s'interfacciano con esso. Per garantire l'efficacia e l''estendibilità del prodotto, quindi pensando anche in ottica futura ad un elevato numero di giocatori, si è pensato di dotare il sistema di un componente intermedio, il nodo, che gestisca la comunicazione tra i client e sottomappe. Il nodo supporta un numero massimo predefinito di utenti e detiene i collegamenti ad un preciso numero di sottomappe; congiunto ad altri nodi, si stabiliscono delle interconnessioni e si forma una rete nodale a stella; ogni nodo possiede la lista dei client ad esso collegati e conosce i players che in quel preciso frangente stazionano nelle sue sottomappe. Questo permette ad ogni nodo di poter servire le richieste che arrivano dai client ad esso collegati, inoltrandole alle proprie sottomappe oppure ad un altro nodo, qualora il player richiedente il servizio si trovi in quell'istante sulla sottomappa appartenente ad un altro nodo. Per lo smistamento iniziale degli utenti è stato opportuno inserire una nuova figura all''interno del sistema chiamato Dispatcher. Questo componente resta in attesa di nuovi utenti e, se non ci sono partite in corso, li reindirizza verso i nodi man mano che si riempiono; se invece una partita è già in corso creerà una lista d'attesa degli utenti informandoli sullo stato della partita. Dal momento in cui il Dispatcher riceve il secondo client (in modo che ci siano almeno due giocatori) partirà un countdown la cui fine determinerà l'inizio della partita; in questo lasso di tempo si potranno aggiungere altri giocatori, il che provocherà un reset del countdown al valore di partenza, per esempio 15-20 secondi. Il Dispatcher, in base al numero di utenti in attesa di iniziare la partita, instaura nuove connessioni con i nodi secondo le necessità e interconnette quest'ultimi tra loro. Si occuperà di informarli dell'inizio e della fine della partita, considerato che egli stesso terrà traccia del tempo di gioco. Dopo aver assegnato un client a un nodo, il Dispatcher chiude la connessione con il client. I giocatori, finita la partita, avranno la possibilità di riconnettersi al Dispatcher per giocarne una nuova.
[{style:type=div|align=center}{style:type=div}{image:01.gif}{style}|document=Space.Page|file=01big.gif]
  1. Inizialmente non ci sono client che richiedono di connettersi; i nodi non sono collegati tra loro.
  2. I client inviano una richiesta di connessione al Dispatcher.
  3. Il Dispatcher entra in comunicazione con il primo nodo.
  4. Il Dispatcher reindirizza tanti client a un nodo quanti questo ne può accogliere; raggiunto il numero massimo di client per quel nodo, il Dispatcher attiverà nuovi nodi su cui migrare i client. Durante questa operazione, il Dispatcher, inoltre, interconnette i nodi tra di loro.

Vantaggi/ Svantaggi {image:frecciaindice.png}

{style:type=div|align=center}{style:type=div}{image:02.JPG}{style}Immaginando il sistema senza nodi che smistano le richieste e le risposte tra client e server, ci troveremmo di fronte a un collegamento diretto fra client e server; ciò comporterebbe un carico di lavoro maggiore dal lato server, ovvero la sottomappa che, oltre a dover gestire gli spostamenti all'interno di essa, dovrebbe anche occuparsi della comunicazione; ciò potrebbe provocare un ritardo nella risposta da parte del server alle richieste dei giocatori, se questi sono molti.{style:type=div|align=center}{style:type=div}{image:03big.JPG}{style}Gestire la comunicazione separatamente alleggerisce il lavoro dei server e diminuisce i tempi di comunicazione poiché, essendo tutti i nodi presenti collegati fra di loro, quando un giocatore si sposta in una sottomappa, anche distante da quella in cui ha iniziato la sua partita, la comunicazione potrà avvenire direttamente tra i nodi interessati.{style:type=div|align=center}{style:type=div}{image:04big2.jpg}{style} {style:type=div|align=justify}{style}L'aggiunta del Dispatcher comporta, a nostro avviso, un passo evolutivo nella strategia e nell'architettura; abbiamo ritenuto opportuno delegare alcune funzioni a questa nuova entità per evitare di appesantire il carico di lavoro di un singolo nodo che, oltre a gestire la comunicazione tra sottomappa e client, dovrebbe anche occuparsi di ricevere e smistare i client e di tenere traccia del tempo di una partita. L'idea di fondo era anche quella di mantenere equità tra i vari nodi, senza esplicitamente specializzarne uno in particolare. Così facendo ogni client che desidera prendere parte al gioco inoltra una richiesta di servizio al Dispatcher; sarà poi quest'ultimo a smistare i client, attivando dinamicamente nuove connessioni verso altri nodi distribuiti per la rete qualora sia necessario. Il Dispatcher può essere definito il fulcro del nostro sistema software, essendo l'entità scandisce il tempo in ogni partita. I nodi si sincronizzano con esso, ricevendo aggiornamenti sull'inizio e la fine della partita.

Progettazione {image:frecciaindice.png}

Modelli del sistema {image:frecciaindice.png}

Durante la progettazione si sono delineate le funzionalità delle entità sopra discusse; per facilitare l'esposizione dei concetti si è deciso di organizzarli graficamente tramite l'impiego di UML.
{image:Modello1ComunicazioneBase.jpg}
Questo primo modello mette in luce la dinamica di comunicazione tra Player, Node e Submap quando è in corso una partita. Analizziamo ora nello specifico i vari componenti.
  • Submap rappresenta una porzione di area di gioco. L'entità principale, nonché unica, di Submap è il MoveUpdater; questa si occupa di gestire l'azione dei giocatori all'interno della mappa e di notificarne gli effetti.
  • Node rappresenta, come già detto in precedenza, l'unità centrale della comunicazione. Si è deciso, pertanto, di suddividere il nodo in due entità per rispecchiare i comportamenti di Submap e Player e per avere, quindi, un componente ibrido. L'utilità di questa scelta consiste nel nascondere questo componente intermedio, simulando una comunicazione diretta tra Player e Submap. Le entità interne al nodo, quindi, implementaranno da un lato l'interfaccia IMoveUpdater di Submap, e dall'altro IUpdateManager di Player.
  • Player rappresenta il player lato client. Questo componente consta di due entità per lo svolgimento di diversi compiti: la notifica delle azioni dell'utente e la gestione degli eventi generati dalla Submap.
{image:Modello2SetupBase.jpg}
Il secondo modello descrive le relazioni significati che sussistono tra gli agenti del sistema durante lo startup della partita. Un elemento precedentemente introdotto è il Dispatcher; questo accoglie le richieste dei nuovi giocatori, adatta la rete nodale in relazione ad esse e scandisce il tempo di gioco. Un Player che desidera prendere parte al gioco, invia una richiesta al Dispatcher. Il Dispatcher reindirizza il client verso il primo Node della rete disponibile; in assenza di un Node disponibile, il Dispatcher allaccia un nuovo Node alla rete nodale, su cui smistarà le richieste future. Il Dispatcher aggiorna esclusivamente il Node riguardo le fasi del gioco; in modo analogo, sarà il Node che inoltrerà queste indicazioni alle Submap ed ai Player. La gestione dello startup nei componenti Submap, Node e Player è affidata ai relativi Manager. Questi presentano comportamenti affini: sostanzialmente, hanno il compito di stabilire collegamenti tra Submap e Node (MapManager e NodeManager), tra i nodi che costituiscono la rete nodale (NodeManager) e tra Node e Player (NodeManager e PlayerManager).
{image:Modello3Completo.jpg}
Il terzo modello è l'unione del primo e del secondo e mette in evidenza, dunque, i componenti nella loro totalità (nell'immagine distinti da colori diversi). In questo ulteriore schema, inoltre, emerge una nuova entità presente nel componente Node: il TableManager. Il TableManager si propone di risolvere un problema nato in una successiva analisi: ogni nodo, infatti, è collegato con un certo numero di mappe, un adeguato numero di giocatori e con gli altri nodi connessi alla rete. Si presenta, pertanto, la difficoltà di dover gestire correttamente la comunicazione tra ogni giocatore e la relativa mappa su cui esso sta giocando; il singolo giocatore può voler comunicare, infatti, con una mappa appartenente al nodo su cui esso stesso risiede ma analogamente può interagire con le mappe di competenza di altri nodi. Il TableManager si occupa, quindi, di tenere traccia delle associazioni Player-Submap per consentire una corretta comunicazione e smistamento di messaggi tra i vari componenti del sistema; prima di una qualsiasi interazione esterna, infatti, gli elementi del nodo richiedono al TableManager informazioni relative al destinatario della comunicazione (in alternativa una Submap, un Node o un Player).

Modelli dei tipi di dati {image:frecciaindice.png}

All'interno del nostro gioco l'utente interagirà con diversi elementi presenti sulla mappa. Questi elementi sono rappresentati all'interno del sistema molteplici specializzazioni dell'entità generale IElementStatus:
  • IPlayerStatus, che rappresenta il giocatore con tutte le informazioni a lui allegate;
  • IPowerupStatus, da cui si sviluppa un'ulteriore gerarchia per l'identificazione dei differenti tipi di PowerUp;
  • IStarStatus, per delineare un elemento Bonus.
{image:rappresentazioneelementstatus.jpg}
Al fine di rendere più semplice la comunicazione degli eventi di gioco, è stato deciso di utilizzare una rappresentazione semplificata di IElementStatus: IElement.
{image:rappresentazioneelement.jpg}
Per quanto riguarda la notifica dei vari aggiornamenti, generati dalla Submap in seguito ad una o più interazioni degli utenti, è stato scelto di utilizzare una struttura dati apposita: IUpdate. Esistono diverse specializzazioni di IUpdate per descrivere tutti i tipi di evento ma ciascuna di esse racchiudono le informazioni sotto forma di IElement.
{image:update.jpg}

Diagrammi delle interazioni {image:frecciaindice.png}

I diagrammi delle interazioni tentano di esporre in modo più chiaro e specifico alcuni aspetti del funzionamento del sistema che potrebbero risultare complessi.
{image:ArrivoGiocatore.jpg}
Il primo diagramma delle interazioni si concentra sul setup della partita dovuto all'arrivo dei giocatori. Il player (PlayerManager) che intende partecipare al gioco richiederà al Dispatcher di essere redirezionato su uno dei nodi della rete; il Dispatcher inizialmente imposta l'ID del player e successivamente verifica la disponibilità, dei diversi nodi, ad accettare un giocatore. Qualora i nodi attualmente connessi alla rete fossero già al completo, il Dispatcher verificherebbe la disponibilità di ulteriori nodi da connettere al sistema e, nel peggiore dei casi, il player verrebbe inserito in una lista d'attesa. Nel caso contrario, il Dispatcher comunicherà al PlayerManager il nodo disponibile, e sarà il player stesso a contattarlo. Il NodeManager si occuperà di inserire il giocatore in arrivo in una delle sue Submap, aggiornerà le informazioni contenute all'interno del TableManager ed avvierà un processo di notifica a catena su tutti i nodi della rete e quindi sui relativi giocatori. Il PlayerManager, a questo punto, renderà noto all'ActionNotifier il Node (CommunicationToMap) su cui è stato smistato e infine informerà il Dispatcher del successo dell'operazione.
{image:StartMatch.jpg}
Raggiunto il numero massimo di player o scaduto il tempo d'attesa, il Dispatcher informa i NodeManager dell'imminente inizio della partita. Quest'ultimo si occuperà di propagare l'informazione alle sue Submap ed ai suoi Player.
{image:Partita.jpg}
Un aspetto interessante da analizzare durante lo svolgimento della partita è il meccanismo della "migrazione" di un Player da una Submap all'altra. Un Player che intende effettuare uno spostamento lo comunica al CommunicationToMap del nodo a cui è collegato; quest'ultimo si preoccuperà di interrogare il TableManager per risalire all' IMoveUpdater destinatario per propagare l'azione. L'IMoveUpdater destinatario può essere una Submap (MoveUpdater) vera e propria oppure il Node (CommunicationToMap) su cui quel Player si trova a giocare in quel momento. Il primo caso è riferito allo scenario in cui il Player sta giocando in una delle Submap del Node a cui appartiene; l'azione viene propagata direttamente alla Submap, che provvederà allo spostamento del Player. Il secondo caso è riferito, invece, allo scenario in cui il Player sta giocando in una delle Submap di un altro Node; l'azione, dunque, viene inoltrata a quest'ultimo Node che fa pervenire l'istruzione alla Submap opportuna. Come detto in precedenza, ogni Player è collegato unicamente a un Node. Durante la fase di gioco, può accadere che il Player, a fronte di taluni spostamenti, si trovi a giocare su Submap appartenenti ad altri Node. Di conseguenza, le interazioni del Player devono essere trasmesse dal Node a cui il Player appartiene al Node che ospita il Player in quel momento. Qualora il Player, in seguito ad un successivo spostamento, finisse su una Submap appartenente ad un ulteriore Node, la nostra implementazione previene la formazione di catene di comunicazione, al fine di evitare ritardi durante la partita, e obbliga il Node a cui il Player appartiene ad intraprendere una comunicazione col nuovo Node, su cui si è spostato il gioco. Una volta che l'azione del Player si propaga attraverso i nodi fino a raggiungere la Submap inerente, quest'ultima si occupa di gestire lo spostamento del Player e di notificare questo e gli effetti che ne derivano a tutti i giocatori presenti sulla Submap stessa. Le notifiche vengono, quindi, segnalate al Node (CommunicationToPlayer) che le inoltrerà, consultando il TableManager, ai relativi Player (UpdateManager). Se uno o più Player in questione non fosse di sua diretta appartenenza, allora il TableManager restituirà, come destinatario della comunicazione, un CommunicationToPlayer relativo al Node di appartenenza del Player in questione: sarà questo Node a preoccuparsi di informare il suo Player.

Migrazione (Migration) {image:frecciaindice.png}

Un caso particolare si ha quando un giocatore si sposta da una Submap all'altra: questo fenomeno è denominato migrazione. La migrazione può avvenire tra:
  1. due Submap dello stesso Node
  2. due Submap di Node differenti
Il caso 1. risulta essere il più semplice; nel momento in cui il Player effettua un movimento ai confini della Submap, quest'ultima si occuperà di comunicare tempestivamente l'azione al CommunicationToPlayer del Node di riferimento che a sua volta, mediante un meccanismo a catena, informerà il CommunicationToMap e in successione il TableManager. Il TableManager restituirà, dunque, al CommunicationToMap la Submap di destinazione (calcolata in base alla direzione del Player), e aggiornerà le sue informazioni; a questo punto il CommunicationToMap segnalerà la migrazione alla Submap di arrivo che genererà una serie di aggiornamenti destinati ai Player presenti su di essa. Il caso 2. prevede, a suo volta, due possibili scenari:
  • il Player migra da una Submap appartenente al Node a cui è esso stesso collegato ad una Submap di un altro Node
  • sia la Submap di partenza che la Submap di arrivo appartengono a due Node differenti ma entrambi diversi da quello su cui risiede il Player
Per quanto riguarda il primo punto, una volta che la notifica della migrazione arriva al TableManager questo, dopo i suoi dovuti calcoli, non restituirà un riferimento ad una Submap come nel caso 1. ma un CommunicationToMap del Node su cui risiede la Submap di destinazione. Di conseguenza la migrazione viene propagata a questo Node che, dunque, in base alle informazioni estrapolate dal proprio TableManager, informerà la sua Submap adeguata. Il secondo punto è il più complesso. Il processo risulta similare al punto visto in precedenza con la differenza che il TableManager del Node da cui si migra, dato che il Player non gli appartiene, restituirà il CommunicationToMap del Node di appartenenza del Player; questo si occuperà di richiedere, secondo i risultati del proprio TableManager, la migrazione al Node su cui è situata la Submap di destinazione, che a sua volta innescherà il solito meccanismo.