Chess Game Service
Contenuti
Descrizione
Il Chess Game Service è un servizio che gestisce la configurazione, l’esecuzione e la terminazione di partite di scacchi, oltre che la connessione dei giocatori a tali partite.
Implementazione
L’implementazione del Chess Game Service è descritta dal seguente diagramma delle classi UML.
Come si può vedere dal diagramma, l’implementazione del servizio dipende dal framework HexArc e da un Legacy Chess Engine (i cui componenti nel diagramma sono identificabili attraverso il prefisso Legacy-
).
In particolare, il servizio definisce due componenti principali:
ChessGamePort
: definisce le funzionalità del servizio. Tali funzionalità si distinguono in due categorie:- Chess Game Management: gestiscono la creazione e ricerca delle partite di scacchi nel servizio, oltre che la connessione dei giocatori a tali partite.
- Chess Game Execution: gestiscono lo svolgimento di una partita di scacchi.
ChessGameHttpAdapter
: espone alcune delle funzionalità dellaChessGamePort
. In particolare, espone le funzionalità relative al Chess Game Management attraverso un contratto di tipo REST, disponibile al seguente link, mentre espone le funzionalità relative alla Chess Game Execution attraverso un contratto di tipo WebSocket, disponibile al seguente link.
Chess Game Execution
In questa sezione, si descriverà come sono state implementate ed esposte le funzionalità relative all’esecuzione di una partita di scacchi all’interno di questo servizio.
Modellazione del dominio
L’esecuzione di una partita di scacchi è gestita da un ChessGameServer
, ovvero un server di gioco all’interno del servizio. Il ChessGameServer
è descritto da uno stato, modellato dalla classe ServerState
.
ServerState
Il ServerState
di un ChessGameServer
contiene le seguenti informazioni sul server:
serverSituation
: la situazione attuale in cui si trova ilChessGameServer
, modellata dall’enumerazioneServerSituation
. UnaServerSituation
può essere uno dei seguenti stati:NotConfigured
: indica che ilChessGameServer
è in attesa di essere configurato prima di poter cominciare la partita.WaitingForPlayers
: indica che ilChessGameServer
è in attesa della partecipazione alla partita di un numero sufficiente di giocatori.Ready
: indica che ilChessGameServer
è pronto a cominciare la partita ed è in attesa che qualcuno la cominci.Running
: indica che ilChessGameServer
sta eseguendo la partita ed è reattivo ai comandi dei giocatori.Terminated
: indica che la partita gestita dalChessGameServer
è terminata, per suo naturale decorso oppure a causa di un errore nel server.
serverError
: l’errore più recente generato nelChessGameServer
, se presente. I possibili errori nelChessGameServer
sono modellati dalla classeChessGameException
e possono essere:GameConfiguredException
: generato se si prova a configurare ilChessGameServer
quando è già stato configurato.GameNotReadyException
: generato se si prova a cominciare la partita delChessGameServer
quando non è pronto a cominciarla.GameNotRunningException
: generato se si prova a inviare un comando alChessGameServer
quando non sta eseguendo la partita.GameNotWaitingForPlayersException
: generato se si prova a partecipare alla partita delChessGameServer
quando non è in attesa di giocatori.GameNotWaitingForPromotionException
: generato se si prova a promuovere un pedone nella partita delChessGameServer
quando non è in attesa della promozione di un pedone.GameTerminatedException
: generato se si prova ad eseguire un’azione sulChessGameServer
quando è terminato e l’azione richiede che non lo sia.GameWaitingForPromotionException
: generato se si prova ad eseguire un’azione sulChessGameServer
diversa dalla promozione quando è in attesa che un pedone sia promosso.InternalServerException
: generato a causa di un errore imprevisto nelChessGameServer
. Indica un probabile errore d’implementazione all’interno del servizio.PlayerAlreadyExistingException
: generato se si prova a partecipare alChessGameServer
come un giocatore di una specifica squadra quando un giocatore di quella squadra è già presente.
subscriptions
: l’insieme delle sottoscrizioni agli eventi generati dalChessGameServer
. Tale insieme è modellato come una mappa da degli identificatori alle corrispondenti sottoscrizioni, modellate dalla classeMessageConsumer
di Vertx.gameState
: lo stato della partita ospitata dalChessGameServer
, modellato dalla classeGameState
.
GameState
All’interno del ServerState
di un ChessGameServer
, il GameState
racchiude le seguenti informazioni:
legacy
: una conversione delGameState
in un formato comprensibile per il Legacy Chess Engine.-
chessboard
: lo stato della scacchiera nella partita, modellato dalla classe legacyChessboard
. UnaChessboard
è un insieme di pezzi, modellati dalla classe legacyPiece
, assegnati a una certa posizione sulla scacchiera, modellata dalla classe legacyPosition
.Un pezzo sulla scacchiera può essere un re, una regina, una torre, un alfiere, un cavallo o un pedone, modellati rispettivamente dalle classi legacy
King
,Queen
,Rook
,Bishop
,Knight
ePawn
. A ogni pezzo sono associate delle regole di movimento gestite dal Legacy Chess Engine. currentTurn
: il turno corrente nella partita. Il turno indica il colore del giocatore a cui è permesso di effettuare una mossa. Tale colore è modellato dall’enumerazione legacyTeam
, che consiste dei valoriWHITE
eBLACK
.-
moveHistory
: lo storico delle mosse effettuate durante la partita, modellato dalla classeMoveHistory
. LaMoveHistory
permette di registrare e di ottenere le mosse compiute dai diversi pezzi sulla scacchiera, modellate dalla classe legacyMove
.Una mossa può essere una cattura, la doppia mossa del pedone, l’arrocco o la presa al varco, modellate rispettivamente dalle classi legacy
CaptureMove
,DoubleMove
,CastlingMove
eEnPassantMove
. gameSituation
: la situazione in cui si trova la partita, modellata dalla classeGameSituation
. Essa può essere:None
: indica che non è presente nessuna situazione particolare nella partita;Check
: indica che il re del giocatore di turno potrebbe essere catturato dall’avversario nel turno successivo;Stale
: indica che il giocatore di turno non ha mosse disponibili;Checkmate
: indica che si è nelle situazioni diCheck
e diStale
;Promotion
: indica che un pedone del giocatore di turno ha raggiunto l’estremità opposta della scacchiera, quindi è in attesa di essere promosso a un altro pezzo.
-
gameOver
: indica il risultato della partita, se è terminata. Tale risultato è modellato dalla classeGameOver
, che include il giocatore che ha vinto la partita (se presente), modellato dalla classe legacyPlayer
, e la causa della terminazione della partita, modellata dalla classe legacyGameOverCause
.Le cause della terminazione di una partita includono lo scacco matto, lo stallo, lo scadere del tempo di uno dei due giocatori e la resa, modellati rispettivamente dai valori
Checkmate
,Stalemate
,Timeout
eSurrender
. timers
: indica il tempo rimasto a ciascun giocatore per effettuare le proprie mosse.gameConfiguration
: la configurazione della partita, modellata dalla classeGameConfiguration
.
GameConfiguration
All’interno del GameState
di un ChessGameServer
, la GameConfiguration
racchiude le seguenti informazioni:
legacy
: una conversione dellaGameConfiguration
in un formato comprensibile per il Legacy Chess Engine.gameMode
: la modalità di gioco della partita, modellata dall’enumerazione legacyGameMode
. UnaGameMode
può assumere i seguenti valori:PVP
: indica che entrambi i giocatori nella partita sono umani. Nel caso di questo servizio, ciò è sempre vero.PVE
: indica che un giocatore della partita è umano mentre l’altro è un computer.
timeConstraint
: indica i vincoli di tempo imposti ai giocatori per eseguire le loro mosse, modellati dall’enumerazioneTimeConstraint
. UnTimeConstraint
può assumere i seguenti valori:NoLimit
: nessun vincolo di tempo è applicato alle mosse dei giocatori.MoveLimit
: ogni giocatore ha un vincolo di tempo per effettuare le proprie mosse e tale vincolo si resetta ad ogni mossa;PlayerLimit
: ogni giocatore ha un vincolo di tempo per effettuare le proprie mosse e tale vincolo non si resetta ad ogni mossa.
blackPlayer
: indica il giocatore appartenente alla squadra nera, modellato dalla classe legacyBlackPlayer
.whitePlayer
: indica il giocatore appartenente alla squadra bianca, modellato dalla classe legacyWhitePlayer
.isPrivate
: indica se la partita è pubblica, ovvero accessibile a chiunque, o privata, ovvero accessibile solo ai giocatori che ne conoscono l’identificatore.gameId
: indica l’identificatore della partita. Tale identificatore può essere associato alla partita anche dopo la sua creazione.
Esecuzione di una partita
Il ChessGameServer
espone le seguenti funzionalità per controllare l’esecuzione di una partita di scacchi:
configure
: applica una configurazione specifica alla partita ospitata dalChessGameServer
. Al termine dell’operazione, in base al contenuto della configurazione specificata, ilChessGameServer
può entrare nello statoReady
, se sono già stati specificati entrambi i giocatori della partita, oppureWaitingForPlayers
, altrimenti.join
: fa partecipare un giocatore specificato alla partita ospitata dalChessGameServer
. Al termine dell’operazione, se sono già stati specificati entrambi i giocatori della partita, ilChessGameServer
entra nello statoReady
.-
start
: fa cominciare la partita ospitata dalChessGameServer
, eventualmente inizializzando i timer relativi ai giocatori e specificando una callback da eseguire ad ogni tick dei timer e una da eseguire allo scadere di un timer.Al termine dell’operazione, il
ChessGameServer
entra nello statoRunning
. Mentre, allo scadere di uno dei timer, se presenti, ilChessGameServer
entra nello statoTerminated
. stop
: fa terminare la partita ospitata dalChessGameServer
, fermando i timer relativi ai giocatori e rimuovendo tutte le sottoscrizioni agli eventi delChessGameServer
. Al termine dell’operazione, ilChessGameServer
entra nello statoTerminated
.findMoves
: restituisce tutte le possibili mosse di un pezzo su una specifica posizione nella scacchiera.-
applyMove
: applica una mossa specifica ad un pezzo sulla scacchiera di uno dei due giocatori, aggiornando la scacchiera e lo storico delle mosse, quindi verificando la situazione della partita dopo la sua applicazione e passando al turno del giocatore avversario.Al termine dell’operazione, se la situazione nella partita è
Stale
oCheckmate
, ilChessGameServer
entra nello statoTerminated
, altrimenti rimane nello statoRunning
. -
promote
: promuove il pedone del giocatore di turno al pezzo specificato se in attesa di essere promosso, verificando la situazione della partita dopo la sua promozione e passando al turno del giocatore avversario. I pezzi ai quali è possibile promuovere un pedone sono modellati dalla classePromotionChoice
e sono la regina, la torre, l’alfiere e il cavallo (rispettivamente i valoriQueen
,Rook
,Bishop
eKnight
).Al termine dell’operazione, se la situazione nella partita è
Stale
oCheckmate
, ilChessGameServer
entra nello statoTerminated
, altrimenti ritorna nello statoRunning
. -
subscribe
: registra una callback da eseguire ogni volta che viene generato uno specifico tipo di evento dalChessGameServer
. Gli eventi generati dalChessGameServer
sono modellati dalla classeEvent
, se non contengono dei dati in aggiunta al tipo di evento, oppure dalla classeEvent.Payload
, se contengono tali dati.In particolare, gli eventi generati da un
ChessGameServer
sono organizzati in una gerarchia, la cui radice è l’eventoChessGameServiceEvent
. L’organizzazione gerarchica permette ai giocatori di sottoscriversi a un sotto-albero della gerarchia, senza dover specificare tutti gli eventi che esso include. Di seguito, viene mostrata tale gerarchia:ChessGameServiceEvent
: un evento generato all’interno di questo servizio. Esso può essere:LoggingEvent
: un evento generato quando unChessGameServer
aggiunge un nuovo messaggio al suo registro dei messaggi;ServerStateUpdateEvent
: un evento generato quando lo stato di unChessGameServer
viene modificato. Esso può essere:ServerSituationUpdateEvent
: un evento generato quando la situazione di unChessGameServer
viene modificata.SubscriptionUpdateEvent
: un evento generato quando le sottoscrizioni a unChessGameServer
vengono modificate.ServerErrorUpdateEvent
: un evento generato quando l’ultimo errore occorso all’interno di unChessGameServer
viene modificato.GameStateUpdateEvent
: un evento generato quando lo stato della partita ospitata da unChessGameServer
viene modificato. Esso può essere:ChessboardUpdateEvent
: un evento generato quando la scacchiera nella partita ospitata da unChessGameServer
viene modificata.GameOverUpdateEvent
: un evento generato quando il risultato della partita ospitata da unChessGameServer
viene modificato, ovvero quando la partita termina.GameSituationUpdateEvent
: un evento generato quando la situazione nella partita ospitata da unChessGameServer
viene modificata.MoveHistoryUpdateEvent
: un evento generato quando lo storico delle mosse nella partita ospitata da unChessGameServer
viene modificato.TurnUpdateEvent
: un evento generato quando il turno corrente nella partita ospitata da unChessGameServer
viene modificato.PlayerUpdateEvent
: un evento generato quando un giocatore nella partita ospitata da unChessGameServer
viene modificato. Esso può essere:BlackPlayerUpdateEvent
: un evento generato quando il giocatore della squadra nera nella partita ospitata da unChessGameServer
viene modificato.WhitePlayerUpdateEvent
: un evento generato quando il giocatore della squadra bianca nella partita ospitata da unChessGameServer
viene modificato.
TimerUpdateEvent
: un evento generato quando il tempo rimasto a uno dei due giocatori nella partita ospitata da unChessGameServer
viene modificato.BlackTimerUpdateEvent
: un evento generato quando il tempo rimasto al giocatore della squadra nera nella partita ospitata da unChessGameServer
viene modificato.WhiteTimerUpdateEvent
: un evento generato quando il tempo rimasto al giocatore della squadra bianca nella partita ospitata da unChessGameServer
viene modificato.
unsubscribe
: rimuove alcune sottoscrizioni tra quelle registrate sulChessGameServer
, dato il loro identificatore.
Il ciclo di vita di un ChessGameServer
può quindi essere riassunto nel seguente diagramma a stati.
Entrando nei dettagli dell’implementazione del ChessGameServer
, modellata dalla classe BasicChessGameServer
, si è deciso di estrarre alcune delle sue funzionalità delegandole a delle classi separate, in modo da evitare la definizione di una god class. In particolare, il BasicChessGameServer
fa affidamento a due mixin:
BasicChessGameServerExecutionManager
: fornisce dei metodi di supporto per modificare l’esecuzione di alcune porzioni di codice all’interno delBasicChessGameServer
. IlBasicChessGameServerExecutionManager
viene principalmente utilizzato per garantire che le funzionalità delBasicChessGameServer
siano eseguite nello stato adeguato, come descritto dal diagramma degli stati. In caso contrario, ilBasicChessGameServerExecutionManager
genera un’opportunaChessGameServiceException
.-
BasicChessGameServerEventManager
: gestisce la sottoscrizione agli eventi delBasicChessGameServer
e la loro pubblicazione. Inoltre, fornisce un insieme diReceiver
corrispondenti a diverse porzioni dello stato delBasicChessGameServer
.Un
Receiver
è una funzione che osserva dei valori. In particolare, ilBasicChessGameServerEventManager
utilizza deiReceiver
per associare la modifica di una certa porzione di stato alla pubblicazione dell’evento corrispondente. In questo modo, finché lo stato viene modificato attraverso taliReceiver
, si è sicuri che i giocatori sottoscritti alBasicChessGameServer
siano notificati di tali cambiamenti di stato.
Connessione di un giocatore
L’accesso ai ChessGameServer
è limitato e mediato dalla ChessGamePort
, che funge da reverse proxy per i ChessGameServer
, gestendo le interazioni tra i giocatori e le partite a cui stanno partecipando.
In particolare, la ChessGamePort
espone le stesse funzionalità di un ChessGameServer
, generalizzandole con la possibilità di applicarle a un qualunque ChessGameServer
tra quelli registrati nel servizio.
Alla connessione di un giocatore verso uno specifico ChessGameServer
, il giocatore si sottoscrive automaticamente a tutti gli eventi del ChessGameServer
relativi ai suoi cambiamenti di stato, in modo da essere notificato ogni volta che avviene uno di questi cambiamenti. Quindi, durante la connessione, la ChessGamePort
esporrà le funzionalità di quel ChessGameServer
al giocatore, permettendogli di agire sulla partita.
Chess Game Management
La ChessGamePort
espone le seguenti funzionalità relative al Chess Game Management:
getGames
: restituisce laChessGameMap
del servizio, ovvero una mappa dagli identificatori delle partite ai server di gioco che le ospitano, ovvero iChessGameServer
;createGame
: crea unChessGameServer
a partire da unaGameConfiguration
;deleteGame
: termina ed elimina unChessGameServer
dato il suo identificatore;findPublicGame
: ricerca il primoChessGameServer
pubblico ancora in attesa di giocatori e restituisce il suo identificatore;findPrivateGame
: ricerca ilChessGameServer
privato con un identificatore specificato e, se esiste ed è ancora in attesa di giocatori, restituisce il suo identificatore.
L’implementazione della ChessGamePort
è modellata dal ChessGameModel
. Oltre ad implementare le funzionalità della ChessGamePort
, il ChessGameModel
gestisce l’integrazione del servizio con uno statistics-service.
In maggior dettaglio, il ChessGameModel
gestisce l’inoltro dei risultati delle partite al loro termine, affidandosi a uno StatisticsServiceProxy
, che funge da Anti-Corruption Layer verso uno Statistics Service. In particolare, alla creazione di una partita, si sottoscrive agli eventi relativi al suo termine, quindi invia il risultato della partita allo Statistics Service alla ricezione di tali eventi.
Per quanto riguarda il ChessGameHttpAdapter
che espone le funzionalità della ChessGamePort
agli utenti del servizio, si è deciso di implementare un server HTTP e Websocket. Le rotte definite dal server sono gestite da un insieme di HttpHandler
, per gestire le richieste HTTP, e di WebsocketHandler
, per gestire le richieste Websocket.
Le richieste gestite dagli HttpHandler
sono quelle relative alla funzionalità del Chess Game Management, mentre le richieste gestite dai WebsocketHandler
sono quelle relative alle funzionalità della Chess Game Execution. In particolare, tra i WebsocketHandler
, il PlayerConnectionHandler
gestisce la ricezione e l’invio di messaggi tra un giocatore e uno specifico ChessGameServer
.
Il ChessGameHttpAdapter
e il ChessGameModel
possono generare delle eccezioni, appartenenti alla classe ChessGameServiceException
. In particolare, oltre alle eccezioni dei ChessGameServer
, l’utente che utilizza il servizio potrebbe essere notificato delle seguenti ChessGameServiceException
:
GameAlreadyStartedException
: indica all’utente che la partita privata da lui richiesta non è in attesa di altri giocatori, ma è già cominciata;GameIdAlreadyTakenException
: indica all’utente che l’identificatore da lui specificato per creare una nuova partita è già assegnato a un’altra partita in esecuzione nel servizio;GameNotFoundException
: indica all’utente che l’identificatore da lui specificato non è associato a nessuna partita nel sistema.MalformedInputException
: indica all’utente che l’input specificato per una certa funzionalità da lui richiesta non è corretto;NoAvailableGamesException
: indica all’utente che nessuna partita pubblica è in attesa di altri giocatori.
Verifica
Per quanto riguarda le funzionalità relative al Chess Game Management, per verificare il sistema è stata creata una suite di test manuali su Postman, in modo da accertarsi che tali funzionalità, esposte dal contratto REST del servizio, producessero i risultati attesi.
Invece, per quanto riguarda le funzionalità relative alla Chess Game Execution, per verificare il sistema è stata creata una suite di test automatici, in modo da accertarsi che un ChessGameServer
risponda correttamente alle interazioni dei giocatori durante lo svolgimento della partita che ospita.
Esecuzione
Per eseguire il sistema è disponibile un jar al seguente link.
Per eseguire il jar è sufficiente utilizzare il seguente comando:
java -jar chess-game-service-<version>.jar \
--statistics-service STATISTICS_SERVICE_HOST
In particolare, il jar permette di specificare i seguenti argomenti a linea di comando:
--statistics-service STATISTICS_SERVICE_HOST
: obbligatorio. Permette di specificare il nome dell’host che ospita lo statistics-service (STATISTICS_SERVICE_HOST
) che sarà utilizzato dal servizio per memorizzare i risultati delle partite. Default:localhost:8082
.--http-host HOST
: opzionale. Permette di indicare il nome dell’host (HOST
) su cui sarà esposto il contratto REST del servizio. Default:localhost
.--http-port PORT
: opzionale. Permette di indicare la porta dell’host (PORT
) su cui sarà esposto il contratto REST del servizio. Default:8080
.--allowed-origins ORIGIN_1;ORIGIN_2;...;
: opzionale. Permette di indicare una lista dei siti web che saranno autorizzati a comunicare con il servizio. Tale lista consiste in una sequenza di URL separati da;
. Default: nessun sito web autorizzato.
In alternativa, un’immagine per eseguire il jar è stata pubblicata anche su Docker. Per eseguire il servizio tramite Docker è sufficiente utilizzare il seguente comando:
docker run -it -p 8083:8080 jahrim/io.github.jahrim.chess.chess-game-service:<version> \
--statistics-service STATISTICS_SERVICE_HOST \
--http-host 0.0.0.0 \
--http-port 8080
Dopodiché, il servizio è accessibile al seguente URL: http://localhost:8083.