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.
|