La seguente relazione mira a mettere in evidenza 1. Scelte progettuali e 2. Dettagli implementativi del progetto per Metodi Avanzati di Programmazione sviluppato da Luigi Porcelli nell'A.A. 2019/2020. Il progetto in esame è un'avventura testuale, la cui struttura è ispirata da prodotti storici dello stesso genere, tra cui le avventure testuali di Scott Adams.
-
Le classi sono strutturate in modo da avere responsabilità precise, nel rispetto della tassonomia ECB.
-
L'avventura testuale sviluppata fa uso del framework
Swingper l'interfaccia grafica, nella quale l'utente può leggere il testo di output ed inserire comandi in modo semplice e lineare. L'interfaccia grafica ci permette di eseguire azioni non inerenti alla trama del gioco (cambio della lingua, salvataggio e caricamento dei progressi, ecc.) senza doverle riconoscere come comandi di gioco (necessario in un applicativo CLI). -
Le stringhe di output vengono salvate in molteplici file: grazie all'utilizzo della classe
ResourceBundleriusciamo ad ottenere una localizzazione completa dell'intero software grazie all'individuazione a Runtime della lingua della JVM sulla quale il software viene eseguito. Il programma è localizzato in due lingue: italiano e inglese. -
La logica dell'ambiente circostante è contenuta nella classe
Room.- Gli oggetti della suddetta classe sono connessi tra di loro attraverso un oggetto
RoomTransition, il quale si occupa di valutare la presenza di ostacoli sul cammino verso una determinata direzione attraverso un riferimento all'ID dell'oggettoRoomche vogliamo ottenere ed un eventuale oggettoObstacle. - Ogni oggetto
Roomcontiene una stringa che identifica l'evento che si vuole eseguire quando quell'oggettoRoomviene letto per la prima volta (dal punto di vista del giocatore, la prima volta che si entra in quella stanza).
- Gli oggetti della suddetta classe sono connessi tra di loro attraverso un oggetto
-
Lo stato corrente di gioco è mantenuto nella classe statica
GameProgress, il quale mantiene un riferimento all'oggetto della classeRoomnella quale si trova il giocatore e l'illuminazione indipendete dalla stanza (come l'accensione di una fiaccola). -
Per il riconoscimento dei comandi, ci serviamo della classe
Parser, la quale interpreta la stringa in ingresso utilizzando un'espressione regolare. In base all'input, riconosce il tipo di azione che si vuole svolgere e il soggetto, andando a chiamare i metodi coerenti con l'espressione. L'input non è case sensitive. -
È possibile conservare in un file i progressi di gioco (contenuto dell'inventario, posizione corrente, progressi nella mappa) attraverso il
MenuItemcon etichetta salva, per poter riprendere il gioco in un secondo momento, aprendo lo stesso file attraverso ilMenuItemcon etichetta Carica. -
La mappa di gioco (ovvero l'insieme degli oggetti
Room) viene letta da file, rendendo ulteriormente indipendente il software dalla storia sviluppata. -
Le classi
InventoryeUIFramesono state scelte ed implementate come classi Singleton, proprio perché, ad ogni esecuzione, avremo un solo frame principale ed un solo inventario. -
Gli strumenti di gioco sono stati implementati come classi che ereditano dalla classe astratta
Item, dalla quale ereditano il metodo astrattouse: andiamo così ad implementare metodi diversi per ogni strumento (questo metodo rappresenta l'effetto sul gioco dell'utilizzo di tale strumento). Le sottoclassi della classeItemereditano dalla superclasse anche metodi per il ritrovamento del nome dello strumento e la sua descrizione (da qui la decisione di non avere un'interfaccia ma una classe astratta).
-
Il testo viene inserito all'interno di un
JEditorPanesimulando il "Typewriter Effect", ovvero la stampa di caratteri in modo animato: ciò è possibile creando un nuovoThreadper ogni stringa che vogliamo stampare, inserendo nelJEditorPaneun carattere per volta, e chiamando il metodosleep()su questo Thread dopo la stampa di ogni carattere.- Per evitare la scrittura simultanea di più stringhe contemporaneamente, ognuno di questi Thread di stampa delle stringhe viene inserito in una
LinkedBlockingQueue, dal quale possiamo ottenere atomicamente l'elemento in testa. Tali elementi vengono poi eseguiti da un'ulteriore Thread, il quale verifica che: 1)NellaLinkedBlockingQueueci sia un elemento in testa, e 2) l'elemento in testa (un thread) non sia in esecuzione. Con la 2 ci assicuriamo che venga stampata una stringa per volta. La 1 è necessaria poiché potremmo voler ripulire il JEditorPane nel quale andiamo ad inserire i caratteri della stringa, ergo anche le stringhe in attesa di stampa fino a quel momento devono essere ripulite.
La scelta di voler inserire un'istanza di thread nella coda anziché la stringa che vogliamo stampare è dovuta al fatto che vogliamo verificare che vi sia un Thread in esecuzione che si occupa di stampare quella stessa stringa.
In un dato momento, con n stringhe in attesa di stampa, avremo n+1 processi in esecuzione (n thread handler ed 1 di effettiva stampa).
- Per evitare la scrittura simultanea di più stringhe contemporaneamente, ognuno di questi Thread di stampa delle stringhe viene inserito in una
-
Per permettere l'utilizzo di sinonimi (destra al posto di est, prendi al posto di raccogli ecc.), gli enumerativi
DirectioneCommandsono forniti di unaMap, la quale ha come chiave un array di stringhe (i sinonimi) e come valore l'enum corrispondente, in modo da poter ottenere il valore dell'enumeratore sul quale far lavorare la classeParsernel riconoscimento del comando.- Proprio attraverso questo processo di riconoscimento di comandi e direzioni possiamo accettare stringhe solo nella lingua corrente del gioco, poiché la
Mapdei sinonimi vieni aggiornata ogni volta che vi è un cambio di lingua.
- Proprio attraverso questo processo di riconoscimento di comandi e direzioni possiamo accettare stringhe solo nella lingua corrente del gioco, poiché la
-
I nomi degli strumenti vengono recuperati a Runtime da un file
.properties, utilizzando come chiave il nome della classe.- Analogamente ai comandi/direzioni, in questo modo possiamo accettare nomi di strumenti solo nella lingua corrente, andando a prelevare i nomi da file adeguati alla lingua eseguita.