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. UnaServerSituationpuò 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 ilChessGameServersta 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 nelChessGameServersono modellati dalla classeChessGameExceptione possono essere:GameConfiguredException: generato se si prova a configurare ilChessGameServerquando è già stato configurato.GameNotReadyException: generato se si prova a cominciare la partita delChessGameServerquando non è pronto a cominciarla.GameNotRunningException: generato se si prova a inviare un comando alChessGameServerquando non sta eseguendo la partita.GameNotWaitingForPlayersException: generato se si prova a partecipare alla partita delChessGameServerquando non è in attesa di giocatori.GameNotWaitingForPromotionException: generato se si prova a promuovere un pedone nella partita delChessGameServerquando non è in attesa della promozione di un pedone.GameTerminatedException: generato se si prova ad eseguire un’azione sulChessGameServerquando è terminato e l’azione richiede che non lo sia.GameWaitingForPromotionException: generato se si prova ad eseguire un’azione sulChessGameServerdiversa 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 alChessGameServercome 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 classeMessageConsumerdi 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 delGameStatein 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,KnightePawn. 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 valoriWHITEeBLACK.-
moveHistory: lo storico delle mosse effettuate durante la partita, modellato dalla classeMoveHistory. LaMoveHistorypermette 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,CastlingMoveeEnPassantMove. 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 diChecke 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,TimeouteSurrender. 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 dellaGameConfigurationin un formato comprensibile per il Legacy Chess Engine.gameMode: la modalità di gioco della partita, modellata dall’enumerazione legacyGameMode. UnaGameModepuò 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. UnTimeConstraintpuò 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, ilChessGameServerpuò 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, ilChessGameServerentra 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
ChessGameServerentra nello statoRunning. Mentre, allo scadere di uno dei timer, se presenti, ilChessGameServerentra 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, ilChessGameServerentra 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 è
StaleoCheckmate, ilChessGameServerentra 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 classePromotionChoicee sono la regina, la torre, l’alfiere e il cavallo (rispettivamente i valoriQueen,Rook,BishopeKnight).Al termine dell’operazione, se la situazione nella partita è
StaleoCheckmate, ilChessGameServerentra 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 dalChessGameServersono 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
ChessGameServersono 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 unChessGameServeraggiunge un nuovo messaggio al suo registro dei messaggi;ServerStateUpdateEvent: un evento generato quando lo stato di unChessGameServerviene modificato. Esso può essere:ServerSituationUpdateEvent: un evento generato quando la situazione di unChessGameServerviene modificata.SubscriptionUpdateEvent: un evento generato quando le sottoscrizioni a unChessGameServervengono modificate.ServerErrorUpdateEvent: un evento generato quando l’ultimo errore occorso all’interno di unChessGameServerviene modificato.GameStateUpdateEvent: un evento generato quando lo stato della partita ospitata da unChessGameServerviene modificato. Esso può essere:ChessboardUpdateEvent: un evento generato quando la scacchiera nella partita ospitata da unChessGameServerviene modificata.GameOverUpdateEvent: un evento generato quando il risultato della partita ospitata da unChessGameServerviene modificato, ovvero quando la partita termina.GameSituationUpdateEvent: un evento generato quando la situazione nella partita ospitata da unChessGameServerviene modificata.MoveHistoryUpdateEvent: un evento generato quando lo storico delle mosse nella partita ospitata da unChessGameServerviene modificato.TurnUpdateEvent: un evento generato quando il turno corrente nella partita ospitata da unChessGameServerviene modificato.PlayerUpdateEvent: un evento generato quando un giocatore nella partita ospitata da unChessGameServerviene modificato. Esso può essere:BlackPlayerUpdateEvent: un evento generato quando il giocatore della squadra nera nella partita ospitata da unChessGameServerviene modificato.WhitePlayerUpdateEvent: un evento generato quando il giocatore della squadra bianca nella partita ospitata da unChessGameServerviene modificato.
TimerUpdateEvent: un evento generato quando il tempo rimasto a uno dei due giocatori nella partita ospitata da unChessGameServerviene modificato.BlackTimerUpdateEvent: un evento generato quando il tempo rimasto al giocatore della squadra nera nella partita ospitata da unChessGameServerviene modificato.WhiteTimerUpdateEvent: un evento generato quando il tempo rimasto al giocatore della squadra bianca nella partita ospitata da unChessGameServerviene 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. IlBasicChessGameServerExecutionManagerviene principalmente utilizzato per garantire che le funzionalità delBasicChessGameServersiano eseguite nello stato adeguato, come descritto dal diagramma degli stati. In caso contrario, ilBasicChessGameServerExecutionManagergenera un’opportunaChessGameServiceException.-
BasicChessGameServerEventManager: gestisce la sottoscrizione agli eventi delBasicChessGameServere la loro pubblicazione. Inoltre, fornisce un insieme diReceivercorrispondenti a diverse porzioni dello stato delBasicChessGameServer.Un
Receiverè una funzione che osserva dei valori. In particolare, ilBasicChessGameServerEventManagerutilizza deiReceiverper 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 alBasicChessGameServersiano 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 laChessGameMapdel servizio, ovvero una mappa dagli identificatori delle partite ai server di gioco che le ospitano, ovvero iChessGameServer;createGame: crea unChessGameServera partire da unaGameConfiguration;deleteGame: termina ed elimina unChessGameServerdato il suo identificatore;findPublicGame: ricerca il primoChessGameServerpubblico ancora in attesa di giocatori e restituisce il suo identificatore;findPrivateGame: ricerca ilChessGameServerprivato 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.