diff --git a/docs/src/02-requirements/index.md b/docs/src/02-requirements/index.md index 99e796e..c5ea733 100644 --- a/docs/src/02-requirements/index.md +++ b/docs/src/02-requirements/index.md @@ -58,9 +58,12 @@ La mappa di gioco è composta da tasselle esagonali che, unite tra loro a nido d - Il sistema deve poter visualizzare le azioni disponibili a seconda della fase in cui si trova. [R3.2](../others/tutorial.md#pannello-informazioni-e-mosse-r32) - Il sistema deve poter visualizzare le carte di sviluppo di ogni giocatore. - Il sistema deve poter gestire il commercio di risorse tra i giocatori. +[R5](../../../src/test/scala/scatan/model/game/state/ops/TradeOpsTest.scala) - Il sistema deve poter verificare quali giocatori detengono i certificati di strada più lunga e armata più grande. +[R6](../../../src/test/scala/scatan/model/game/state/ops/AwardOpsTest.scala) - Il sistema deve poter gestire la fine della partita, controllando se un giocatore ha raggiunto i 10 punti vittoria. + ## Non funzionali - Usabilità: l'interfaccia grafica deve essere semplice e intuitiva. diff --git a/docs/src/04-design/index.md b/docs/src/04-design/index.md index 0e1f4cd..6a17725 100644 --- a/docs/src/04-design/index.md +++ b/docs/src/04-design/index.md @@ -116,6 +116,94 @@ A questo punto una volta definito il dominio del gioco si è andato a definire u + + +## Stato della partita + +Lo stato della partita è modellato tramite un entità rinominata `ScatanState`, +nella quale son presenti tutte le informazioni a riguardo, rappresentandone uno Snapshot. +In particolare vi sono contenute le seguenti informazioni: + +- la lista dei giocatori +- la mappa di gioco +- la lista di edifici assegnati ai giocatori +- la lista di premi assegnati ai giocatori +- le liste di carte risorse assegnate ai giocatori +- le liste di carte sviluppo assegnate ai giocatori +- il mazzo di carte sviluppo +- la posizione del ladro + +![ScatanState](../img/04-design/state/scatan-state.jpg) + +### Componenti del gioco & Operations + +Le informazioni precedentemente elencate sono modellate come componenti (es: `Award`, `Buildings`, `ResourceCard`, ...), del gioco, molti di questi indipenti tra loro. +In seguito, a ciò le operations per gestire queste singole parti del dominio sono realizzate indipendentemente, optando quindi per un approccio modulare facile da estendere e mantenere. +Inoltre, così facendo, il core della partita (`ScatanState`) risulta essere indipendente dalle funzionalità del gioco. + + +![ScatanState](../img/04-design/state/scatan-stateOps.jpg) + +#### Resource Cards Ops + +- `ResourceCardOps`: contiene le operations per gestire le carte risorse, fornendo funzionalità per: + - aggiungere carte risorsa: utilizzabile in seguito al lancio dei dadi e in seguito alla fase iniziale + - rimuovere carte risorsa: utili per l'acquisto di carte sviluppo e per la costruzione di edifici + - rubare carte risorsa: utili per il furto tramite il ladro e in seguito al "7 ai dadi" + +![Resource Cards](../img/04-design/state/resCardOps.jpg) + +#### Development Cards Ops + +- `DevelopmentCardOps`: contiene le operations per gestire le carte sviluppo, fornendo funzionalità che permettono ad un giocatore di: + - aggiungere carte sviluppo al proprio mazzo effettuandone un acquisto + - giocare carte sviluppo dal proprio mazzo applicando gli effetti di queste ultime sulla partita + - rimuovere carte sviluppo dal proprio mazzo in seguito all'uso di queste ultime + +![Development Cards](../img/04-design/state/devCardOps.jpg) + +#### Buildings Ops + +- `BuildingOps`: contiene le operations per gestire gli edifici, aggiungendo funzionalità per: + - assegnare edifici ai giocatori in seguito alla fase iniziale di preparazione + - far costruire edifici ai giocatori in seguito all'acquisto di questi ultimi, verificando che il giocatore abbia le risorse necessarie e che la posizione scelta sia valida + +![Buildings](../img/04-design/state/buildingOps.jpg) + +#### Awards Ops + +- `AwardOps`: contiene l'operazione per calcolare i premi allo stato attuale della partita, in particolare: + - il premio per il giocatore con il maggior numero di strade + - il premio per il giocatore con il maggior numero di carte sviluppo di tipo cavaliere + +![Awards](../img/04-design/state/awardOps.jpg) + +#### Trade Ops + +- `TradeOps`: contiene le operations per gestire gli scambi, permettendo ai giocatori di: + - scambiare carte risorsa con altri giocatori + - scambiare carte risorsa con la banca + +![Trade](../img/04-design/state/tradeOps.jpg) + +#### Score Ops + +- `ScoreOps`: contiene l'operazione per calcolare il punteggio di un giocatore, in particolare: + - il punteggio per gli edifici costruiti + - il punteggio per le carte sviluppo di tipo 'punto vittoria' + - il punteggio per i premi + - il punteggio totale + +![Score](../img/04-design/state/scoreOps.jpg) + +#### Robber Ops + +- `RobberOps`: contiene le operations per gestire il ladro, permettendo ai giocatori di: + - spostare il ladro in una nuova posizione + +![Robber](../img/04-design/state/robberOps.jpg) + + ## Map L'analisi del dominio ha portato alla schematizzazione della mappa di gioco, con i seguenti elementi: @@ -206,90 +294,3 @@ Questo meccanismo funge anche da `Anti Corruption Layer`, in quanto permette di ![Context Map](../img/04-design/view/context-map.jpg) - - -## Stato della partita - -Lo stato della partita è stato modellato tramite un entità rinominata `ScatanState`, -nella quale son presenti tutte le informazioni a riguardo, rappresentandone uno Snapshot. -In particolare vi sono contenute le seguenti informazioni: - -- la lista dei giocatori -- la mappa di gioco -- la lista di edifici assegnati ai giocatori -- la lista di premi assegnati ai giocatori -- le liste di carte risorse assegnate ai giocatori -- le liste di carte sviluppo assegnate ai giocatori -- il mazzo di carte sviluppo -- la posizione del ladro - -![ScatanState](../img/04-design/state/scatan-state.jpg) - -### Componenti del gioco & Operations - -Le informazioni precedentemente elencate sono state modellate come componenti (es: `Award`, `Buildings`, `ResourceCard`, ...), del gioco, molti di questi indipenti tra loro. -In seguito a ciò sono state realizzate indipendentemente le operations per gestire queste singole parti del dominio, optando quindi per un approccio modulare facile da estendere e mantenere. -Inoltre, così facendo, siamo riusciti a rendere il core della partita (`ScatanState`) indipendente dalle funzionalità del gioco. - - - -![ScatanState](../img/04-design/state/scatan-stateOps.jpg) - -#### Resource Cards Ops - -- `ResourceCardOps`: contiene le operations per gestire le carte risorse, fornendo funzionalità per: - - aggiungere carte risorsa: utilizzabile in seguito al lancio dei dadi e in seguito alla fase iniziale - - rimuovere carte risorsa: utili per l'acquisto di carte sviluppo e per la costruzione di edifici - - rubare carte risorsa: utili per il furto tramite il ladro e in seguito al "7 ai dadi" - -![Resource Cards](../img/04-design/state/resCardOps.jpg) - -#### Development Cards Ops - -- `DevelopmentCardOps`: contiene le operations per gestire le carte sviluppo, fornendo funzionalità che permettono ad un giocatore di: - - aggiungere carte sviluppo al proprio mazzo effettuandone un acquisto - - giocare carte sviluppo dal proprio mazzo applicando gli effetti di queste ultime sulla partita - - rimuovere carte sviluppo dal proprio mazzo in seguito all'uso di queste ultime - -![Development Cards](../img/04-design/state/devCardOps.jpg) - -#### Buildings Ops - -- `BuildingOps`: contiene le operations per gestire gli edifici, aggiungendo funzionalità per: - - assegnare edifici ai giocatori in seguito alla fase iniziale di preparazione - - far costruire edifici ai giocatori in seguito all'acquisto di questi ultimi, verificando che il giocatore abbia le risorse necessarie e che la posizione scelta sia valida - -![Buildings](../img/04-design/state/buildingOps.jpg) - -#### Awards Ops - -- `AwardOps`: contiene l'operazione per calcolare i premi allo stato attuale della partita, in particolare: - - il premio per il giocatore con il maggior numero di strade - - il premio per il giocatore con il maggior numero di carte sviluppo di tipo cavaliere - -![Awards](../img/04-design/state/awardOps.jpg) - -#### Trade Ops - -- `TradeOps`: contiene le operations per gestire gli scambi, permettendo ai giocatori di: - - scambiare carte risorsa con altri giocatori - - scambiare carte risorsa con la banca - -![Trade](../img/04-design/state/tradeOps.jpg) - -#### Score Ops - -- `ScoreOps`: contiene l'operazione per calcolare il punteggio di un giocatore, in particolare: - - il punteggio per gli edifici costruiti - - il punteggio per le carte sviluppo di tipo 'punto vittoria' - - il punteggio per i premi - - il punteggio totale - -![Score](../img/04-design/state/scoreOps.jpg) - -#### Robber Ops - -- `RobberOps`: contiene le operations per gestire il ladro, permettendo ai giocatori di: - - spostare il ladro in una nuova posizione - -![Robber](../img/04-design/state/robberOps.jpg) diff --git a/docs/src/05-implementation/index.md b/docs/src/05-implementation/index.md index 5d917c1..09403a1 100644 --- a/docs/src/05-implementation/index.md +++ b/docs/src/05-implementation/index.md @@ -708,7 +708,7 @@ private val game = Game[ScatanState, ScatanPhases, ScatanSteps, ScatanActions, S Per quanto riguarda il mio contributo al progetto, mi sono occupato principalmente delle seguenti parti: -- Modellazione dello stato della partita, dei singoli componenti relativi a quest'ultimo, e delle loro corrispondenti operazioni nonchè: +- Modellazione dello stato della partita, dei singoli componenti relativi a quest'ultimo, e delle loro corrispondenti operazioni nonché: - Gestione delle carte risorse - Gestione delle carte sviluppo - Gestione delle costruzioni @@ -722,12 +722,11 @@ Per quanto riguarda il mio contributo al progetto, mi sono occupato principalmen Di seguito saranno descritte con maggior dettaglio le parti più salienti. ### Creazione e modellazione dei singoli componenti della partita +Per modellare lo stato della partita, come prima cosa, sono individuate e definite quelle che sarebbero le sue componenti principali, ovvero i **buildings**, le **resource cards**, le **development cards**, i **trades**, gli **awards** e gli **scores**. -Per la modellazione dello stato della partita, come prima cosa, ho individuato quelle che sarebbero state le sue componenti principali, individuando così le entità dei **buildings**, le **resource cards**, le **development cards**, i **trades**, gli **awards** e gli **scores**. +Una volta individuati, sono definite le eventuali strutture dati necessarie a modellarli, cercando di mantenere una certa coerenza tra di esse, e soprattutto con il dominio del gioco. -Una volta individuati, ho subito organizzato le eventuali strutture dati necessarie a modellarli, cercando di mantenere una certa coerenza tra di esse, e soprattutto con il dominio del gioco. - -Dopo di che, per facilitare la lettura e sviluppo del codice stesso, ho optato per definire per ognuno dei componenti, dei **type alias**, corrispondenti a codeste strutture dati, in modo da poterle utilizzare in modo più semplice e diretto. +Dopo di che, per facilitare la lettura e sviluppo del codice stesso, vengono definiti per ognuno dei componenti, dei **type alias**, corrispondenti a codeste strutture dati, in modo da semplificarne l'utilizzo. Di seguito, sono riportati due esempi di definizione di **type alias**: @@ -772,7 +771,7 @@ type Awards = Map[Award, Option[(ScatanPlayer, Int)]] ### Modellazione dello stato della partita -In concomitanza alla realizzazione di questi componenti, ho iniziato a modellare anche quella che sarebbe stata l'entità principale dello stato della partita, scegliendo di utilizzare una **case class** chiamata `ScatanState`, contenente solo le informazioni necessarie per poter catturare i vari snapshot dello stato della partita durante il suo svolgimento. +In concomitanza alla realizzazione di questi componenti, è stata modellata anche l'entità principale dello stato della partita, scegliendo di utilizzare una **case class** chiamata `ScatanState`, contenente solo le informazioni necessarie per poter catturare i vari snapshot dello stato della partita durante il suo svolgimento. ```scala final case class ScatanState( @@ -787,13 +786,12 @@ final case class ScatanState( ) ``` -### Realizzazione delle ScatanState Ops - -Dopo aver individuato quelle che sarebbero state le principali operazioni da poter effettuare sullo stato della partita, ho deciso di raggrupparle e dividerle in più moduli, ognuno relativo ad una specifica sotto-parte del dominio. Riuscendo così a rendere le varie funzionalità indipendenti (o semi-indipendenti) tra loro. +### ScatanState Ops +Le principali operazioni effettuabili sullo stato della partita, sono raggruppate e divise in più moduli, ognuno relativo ad una specifica sotto-parte del dominio. Riuscendo così a rendere le varie funzionalità indipendenti (o semi-indipendenti) tra loro. Per fare ciò, ho realizzato all'interno del package `scatan.model.game.state.ops` una serie di **object** ognuno dei quali contiene una serie di **extension methods** per la case class `ScatanState`, in modo da poterla arricchire di funzionalità. -Inoltre, per favorire un approccio più funzionale, ho deciso di realizzare tutti questi metodi senza side-effect, facendo in modo che ogni volta che verrà effettuata una modifica allo stato della partita, verrà ritornato un `Option[ScatanState]` contenente il nuovo stato della partita, o `None` altrimenti, permettendo agli strati superiori dell'applicazione di catturare eventuali errori e gestirli di conseguenza. +Inoltre, per favorire un approccio più funzionale, lo ScatanState è reso immutabile e tutti questi metodi sono senza side-effect, facendo in modo che ogni volta che viene effettuata una modifica allo stato della partita, viene ritornato un `Option[ScatanState]` contenente il nuovo stato della partita aggiornato, o `None` altrimenti, permettendo agli strati superiori dell'applicazione di catturare eventuali errori e gestirli di conseguenza. Di seguito, viene riportato un esempio di definizione dell' **object** contenente le **extension methods** per la gestione delle **resource cards**: @@ -830,15 +828,12 @@ object ResourceCardOps: ``` ### Operazioni tail recursive - -Nello sviluppo delle operazioni sullo stato, ho posto una particolare enfasi sull'utilizzo pervasivo della funzione `foldLeft` per l'elaborazione delle informazioni in molte delle nostre strutture dati. L'obiettivo di questa scelta è stato duplice: da un lato, ottimizzare le prestazioni del codice attraverso l'uso di questa funzione altamente efficiente; d'altro lato, migliorare la coerenza e la leggibilità del codice, aumentandone la dichiaratività. +Nello sviluppo delle operazioni sullo stato, pongo una particolare enfasi sull'utilizzo pervasivo della funzione `foldLeft` per l'elaborazione delle informazioni in molte delle nostre strutture dati. +L'obiettivo di questa scelta è duplice: da un lato, ottimizzare le prestazioni del codice attraverso l'uso di questa funzione altamente efficiente; d'altro lato, migliorare la coerenza e la leggibilità del codice, aumentandone la dichiaratività. Di seguito, viene riportato un esempio di utilizzo di `foldLeft` nella gestione dei **trades**: ```scala - /** Trade between two players. The sender must have the senderCards and the receiver must have the receiverCards The - * sender will give the senderCards to the receiver and vice versa - */ def tradeBetweenPlayers( sender: ScatanPlayer, receiver: ScatanPlayer, @@ -860,15 +855,41 @@ Di seguito, viene riportato un esempio di utilizzo di `foldLeft` nella gestione yield stateWithCardAssignedToSender ) stateWithReceiverCardsProcessed - ``` - - +Di seguito, un esempio di utilizzo di `foldLeft` nella gestione degli **awards**: + +```scala + def awards: Awards = + val precedentLongestRoad = state.assignedAwards(Award(AwardType.LongestRoad)) + val longestRoad = + state.assignedBuildings.asPlayerMap.foldLeft(precedentLongestRoad.getOrElse((ScatanPlayer(""), 0)))( + (playerWithLongestRoad, buildingsOfPlayer) => + val roads = buildingsOfPlayer._2.filter(_ == BuildingType.Road) + if roads.sizeIs > playerWithLongestRoad._2 then (buildingsOfPlayer._1, roads.size) + else playerWithLongestRoad + ) + val precedentLargestArmy = state.assignedAwards(Award(AwardType.LargestArmy)) + val largestArmy = + state.developmentCards.foldLeft(precedentLargestArmy.getOrElse(ScatanPlayer(""), 0))( + (playerWithLargestArmy, cardsOfPlayer) => + val knights = cardsOfPlayer._2.filter(_.developmentType == DevelopmentType.Knight) + if knights.sizeIs > playerWithLargestArmy._2 then (cardsOfPlayer._1, knights.size) + else playerWithLargestArmy + ) + Map( + Award(AwardType.LongestRoad) -> (if longestRoad._2 >= minimumRoadLengthForAward then + Some((longestRoad._1, longestRoad._2)) + else precedentLongestRoad), + Award(AwardType.LargestArmy) -> (if largestArmy._2 >= minimumKnightsForAward then + Some((largestArmy._1, largestArmy._2)) + else precedentLargestArmy) + ) +``` ### Calcolo degli Scores -Per implementare il modulo dedicato al calcolo dei punteggi, come prima cosa, è stato definito tramite la libreria **cats**, un semigruppo per il tipo `Scores`, in modo da poter dichiarare come combinare più elementi di questo tipo: +Per implementare il modulo dedicato al calcolo dei punteggi, come prima cosa, viene definito tramite la libreria **cats**, un semigruppo per il tipo `Scores` (un semigruppo ha un'operazione binaria associativa), in modo da poter dichiarare come combinare più elementi di questo tipo: ```scala import cats.kernel.Semigroup @@ -891,9 +912,6 @@ Successivamente, ho definito una serie di funzioni, ognuna delle quali si occupa Di seguito, viene riportato un esempio di definizione di una di queste funzioni: ```scala -/** Computes the partial scores of each player, taking into account the development cards they have assigned. A - * victory point card is worth 1 point. - */ private def partialScoresWithVictoryPointCards: Scores = val playersWithVictoryPointCards = state.developmentCards.filter(_._2.exists(_.developmentType == DevelopmentType.VictoryPoint)).map(_._1) @@ -902,14 +920,9 @@ Di seguito, viene riportato un esempio di definizione di una di queste funzioni: ) ``` -Infine, ho combinato queste funzioni di punteggio parziale mediante l'operazione di combine (`|+|`). Questo processo di combinazione ha permesso di ottenere una funzione di punteggi totali: +Infine, vengono combinate queste funzioni di punteggio parziale mediante l'operazione di combine (`|+|`). Questo processo di combinazione permette ottenere i punteggi totali: ```scala -/** This method calculates the total scores of all players in the game by combining the partial scores with awards - * and buildings. It uses the `|+|` operator from the `cats.syntax.semigroup` package to combine the scores. - * @return - * the total scores of all players in the game. - */ def scores: Scores = import cats.syntax.semigroup.* import scatan.model.components.Scores.given @@ -921,7 +934,7 @@ Infine, ho combinato queste funzioni di punteggio parziale mediante l'operazione L'implementazione di tutte le operazioni sullo stato con tipo di ritorno opzionale, rischiava di rendere più macchinosa la fase di testing, e di rendere il codice di quest'ultima confuso e poco leggibile se non gestito correttamente. -Per ovviare a questo problema, è stato utilizzato in maniera pervasiva il costrutto `for comprehension`, ottenendo così codice più pulito e meno suscettibile a errori dovuti alla gestione dei valori opzionali. +Per ovviare a questo problema, viene utilizzato in maniera pervasiva il costrutto `for comprehension`, ottenendo così codice più pulito e meno suscettibile a errori dovuti alla gestione dei valori opzionali. Di seguito, viene riportato un esempio di utilizzo di `for comprehension` nella fase di testing: @@ -941,3 +954,12 @@ Di seguito, viene riportato un esempio di utilizzo di `for comprehension` nella ``` ## Pair programming + +Lo sviluppo di alcune parti del progetto non è stato effettuato propriamente in pair programming, ma sono state sviluppate in maniera collaborativa, come ad esempio la parte grafica dell'applicazione nelle sue varie schermate. + +### Setup View & Game View (Andruccioli & Borriello) +La grafica dell'applicazione è implementata grazie al framework **Laminar**, per disegnare l'interfaccia grafica, e realizzare i componenti reattivi. + +Per quanto riguarda la schermata di setup, è realizzata in maniera molto semplice, in quanto non presenta particolari funzionalità, se non la possibilità di inserire il nome dei giocatori, di selezionare il numero di giocatori e di scegliere la mappa di gioco. + +