|
Java Tutorial - Parte 2 0.1
Un tutorial per esempi
|
Nel capitolo precedente abbiamo usato diversi componenti grafici della libreria Java Swing: etichette, bottoni, pannelli, layout managers, etc. Ci sono moltissimi altri componenti in Swing ma andremo con ordine: quando ci serviranno lo useremo. I più curiosi e smaniosi possono leggere la documentazione ufficiale in lingua inglese a questo link: Using Swing Components che da una panoramica generale su tutti i componenti della libreria.
In questo capitolo implementeremo il pannello del giocatore che si compone di due sottopannelli:
SequencePanel: il pannello delle sequenze che abbiamo già scritto e testato nel capitolo precedente ma che modificheremo in questo capitolo CommandPanel: il pannello dei bottoni di comandoQuello che vogliamo ottenere è un pannello di questo tipo:
Il sottopannello in alto è il pannello delle sequenze che contiene la solution e tutte le guess con i risultati del confronto con la solution. Questo pannello viene posizionato nel BorderLayout.CENTER Il sottopannello in basso è il pannello dei comandi che avevamo scritto in Il sotto-pannello dei bottoni e faceva parte della classe principale Main02. Questo pannello viene posizionato nel BorderLayout.PAGE_END.
Schematicamente, i due sottopannelli saranno posizionati come segue:
--title----------- | CENTER | | | | SequencePanel | | | | | | | ------------------ | PAGE_END | | | | CommandPanel | | | ------------------
L'intero pannello del giocatore viene racchiuso in un bordo di colore bianco con un titolo anch'esso di colore bianco: il titolo sarà il nome del giocatore.
In questo capitolo analizzeremo dei nuovi files sorgente che fanno parte del package mastermind.gui e la nuova classe principale Main03 che contiene l'entry-point per testare il codice:
| nome file | package | descrizione |
|---|---|---|
| SequencePanel03.java | mastermind.gui | il nuovo pannello delle sequenze |
| CommandPanel.java | mastermind.gui | il pannello dei bottoni di comandi |
| PlayerPanel.java | mastermind.gui | il pannello del giocatore |
| InputListener.java | mastermind.gui | la interfaccia di input delle sequenze |
| Main03.java | mastermind.app | la classe principale della versione 0.3 |
Il pannello del giocatore viene usato per gestire le sequenze, sia la solution che le guess: i bottoni del sottopannello dei comandi (il CommandPanel) vengono usati per aggiungere, togliere e cancellare uno o più simboli delle sequenze. Ma quale delle dieci sequenze deve essere aggiornata?
Ebbene, dipende dalla fase di gioco: come descritto in Le regole del gioco nel MasterMind vi sono due fasi:
Definiremo pertanto una variabile intera di nome guessIndex che, per convenzione, può assumere i seguenti valori:
-1: siamo nella prima fase del gioco, i bottoni di comando aggiornano la solution ZERO alla lunghezza della array delle guess : siamo nella seconda fase del gioco e il valore della variabile rappresenta l'indice della tabella delle guess; i bottoni di comando aggiornano la guess alla riga indicata da guessIndex Se guessIndex è uguale a 99 e la partita è terminata. Il pannello dei comandi dovrebbe essere abilitato soltanto se il giocatore a cui si riferisce è un giocatore umano e solo se egli è in turno di gioco. Ma non è tutto. Per dare maggiore feedback all'utente, anche i singoli bottoni del pannello dei comandi dovrebbero essere abilitati o meno:
n è minore di dieci, per esempio otto, significa che i codici "8" e "9" non sono disponibili per le sequenze; i relativi bottoni dovrebbero essere disabilitati repeat (=consenti la ripetizione dei codici) è uguale a false, quando l'user inserisce un simbolo in sequenza il bottone di comando relativo a quel simbolo dovrebbe essere disabilitatoQueste features devono essere gestite dal pannello del giocatore interagendo tra il sottopannello delle sequenze e quello dei bottoni di comando: a questo proposito, oltre a mantenere il membro dati guessIndex il pannello del giocatore deve avere accesso anche ai parametri di gioco.
Ricorderete che quando avevamo scritto il pannello delle sequenze con i bottoni Done e Check (vedi Il pannello delle sequenze) la resa estetica di detti bottoni di comando era insoddisfacente.
In effetti, i bottoni di comando quadrati con il testo Done e Check non si abbinano con le icone circolari delle sequenze e anche il loro colore di fondo (grigio) è il classico pugno nell'occhio. Non solo, anche i bottoni nel pannello comandi della app di test (vedi La classe derivata Main02) erano brutti e poco intuitivi. Che significa la "b" e la "c" sul bottone? In questa versione della app disegneremo bottoni più eleganti e più intuitivi; nelle due immagini seguenti potete vederne il confronto:
| PRIMA | DOPO |
|---|---|
|
|
Anche la classe JButton, al pari di JLabel, può essere contenere, oltre al testo da visualizzare anche una icona; abbiamo usato questo espediente per disegnare i simboli delle sequenze e useremo lo stesso sistema per disegnare i bottoni di comando esteticamente più belli. Cerchiamo in Internet le immagini per i bottoni Check/Done, backspace e cancel.
Nella tabella seguente potete vedere quelli che userò io. Le immagini sono in formato PNG e si trovano nella cartella mastermind/gui/images.
| backspace | cancel | done/check |
|---|---|---|
|
|
|
Per creare un bottone circolare con le immagini di cui sopra si usa uno dei costruttori del componente JButton e precisamente il seguente:
Il primo argomento è il testo che deve apparire nel bottone esattamente come già visto per i bottoni di comando nella prima versione. Nel caso dei bottoni di comando di cui sopra, il testo è nullo.
Il secondo argomento al costruttore è un oggetto di tipo Icon che viene creata da un metodo specifico leggendo la immagine da disco.
Nella classe CommandPanel è stato scritto un metodo specifico per creare la icona di un bottone scalando una immagine su disco; Il metodo è il readButtonIcon ed è statico.
Il metodo accetta tre argomenti: un oggetto dal quale ottenere la risorsa immagine, il nome del file che contiene la immagine del bottone in formato PNG e le dimensioni in pixels della icona da restituire. Coloro che hanno letto la prima parte del mio tutorial su Java già conoscono l'argomento; per gli altri o per coloro che vogliono rinfrescare la memoria clikkare sul link seguente: Java Tutorial Parte Prima - I formati grafici.
Per ulteriori informazioni consiglio di cercare informazioni nella Grande Rete ed in special modo sulla preziossima Wikipedia
Il file PNG viene letto come risorsa URL: una caratteristica di Java che consente di astrarre la effettiva locazione fisica di una qualsiasi risorsa: è sufficiente specificarne il URL: nel nostro caso un path relativo su disco.
Una volta determinato il URL, la immagine viene letta in un oggetto di classe BufferedImage e scalata con il metodo getScaledInstance che restituisce la stessa immagine ma di dimensioni ridotte a btnSize che è l'argomento fornito al metodo stesso.
La icona necessaria a costruire il bottone personalizzato viene creata come istanza della classe ImageIcon; infine la istanza viene restituita al chiamante, che in questa versione, è il costruttore di SequencePanel.
Avrete anche notato che il metodo può sollevare una eccezione checked: questo nel caso la risorsa sia indisponibile oppure sono intervenuti errori nella lettura del file immagine. La eccezione viene intercettata nel metodo createPanel dei due pannelli delle sequenze e dei bottoni tuttavia la app non termina: se le immagini non sono disponibili i bottoni vengono comunque costruiti con il testo ed avremo i brutti bottoni che avevamo visto nella versione precedente.
Il sottopannello dei bottoni viene gestito dalla classe specializzata CommandPanel ed esso occupa la posizione BorderLayout.PAGE_END nel pannello del giocatore. Schematicamente, è costruito come una griglia di 4x3 (o 6x2) = 12 celle disposte, ovviamente, in un GridLayout:
----------------- | (0) (1) (2) (3) | | (4) (5) (6) (7) | | (8) (9) (b) (c) | ----------------- | RESIGN | -----------------
I 12 bottoni della griglia sono i codici da "0" a "9" che rappresentano i simboli della sequenza più il bottone backspace (codice 10) ed il bottone cancel (codice 11).
Al di fuori della griglia inseriremo il bottone Resign; quest'ultimo sarà un bottone normale, con il testo "Resign" (=abbandona, mi arrendo) dal momento che non ho trovato una icona abbastanza intuitiva per rappresentarlo. Siete ovviamente invitati ad ovviare a questo mio limite: il sorgente di questo piccolo progetto è rilaciato sotto licenza GPL (la licenza open source per eccellenza) e quindi potete modificare questi sorgenti come vi pare e piace purchè rispettiate i termini imposti dalla GPL, primo tra tutti il dovere di rilasciare sotto GPL anche le vostre modifiche.
I componenti grafici del pannello dei bottoni di comando vengono creati nel metodo createPanel della classe CommandPanel.
Il metodo che crea il componente JButton da visualizzare nella GUI è il createButton:
Il metodo è ampiamente parametrizzato in modo da poter creare qualsiasi bottone. La lista degli argomenti al metodo è piuttosto corposa:
ac: questo rappresenta la action-command dell'evento button-clikked: essa è personalizzata poichè non è possibile lasciarla al mero testo visualizzato sul bottone di comando in quanto il testo è variabile in base al tipo di simbolo listener il gestore degli eventi ActionEvent è il pannello del giocatore: poichè quest'ultimo è il contenitore di entrambi i sotto-pannelli ( il pannello dei comandi ed il pannello delle sequenze) è più che logico impostare il pannello contenitore quale gestore degli eventi icon la icona del bottone che può essere lo sfondo grigio chiaro o scuro per il tipo di simbolo NUMBERS e LETTERS oppure il cerchio del colore specifico per il tipo di simboli COLORS text il testo da visualizzare nel bottone che sarà numerico per il tipo di simbolo NUMBERS, alfabetico per il tipo di simbolo LETTERS è nullo per il tipo di simbolo COLORSNel metoco createButton vengono impostate altre caratteristiche del componente JButton quali l'allineamento orizzontale e verticale, il font da usare ed il colore di sfondo e primo piano per il testo.
Un altro metodo degno di nota è il createSymbolButton che crea i 10 bottoni di comandi dei simboli da "0" a "9". Questo metodo viene richiamato 10 volte nel metodo createPanel iterando tra i 10 codici dei simboli a disposizione. A questo proposito il lettore avrà notato che se il parametro di gioco n è inferiore a 10 il bottone corrispondente al codice non disponibile non viene creato ma, al suo posto, viene inserito nella griglia un bottone nullo creato dal metodo createNullButton
Questo componente è già stato implementato nella classe SequencePanel nel capitolo precedente ma in questa nuova versione vogliamo cambiare la resa grafica dei bottoni di comando "Done" e "Check" sostituendo il testo (brutto) con delle icone più intuitive. E' ovvio che dobbiamo modificare il sorgente della classe; operazione banale disponendo di un editor di testi ma ... non vogliamo perdere del tutto il vecchio codice.
Nella sezione Gestire le versioni della app avevo accennato al fatto che tenere traccia delle versioni di un progetto non è una operazione banale e che sono stati creati appositi strumenti, anche molto complessi, per ottenere questo risultato. L'uso di tali strumenti è assolutamente off-topic (=fuori tema) per questo tutorial però vi posso insegnare un trucchetto che unisce la facilità d'uso ad una discreta efficacia.
Uno strumento di gestione del versionamento deve soffisfare almeno i seguenti punti:
L'approccio usato in questo progetto è quello delle versioni incrementali; si tratta di una tecnica piuttosto semplice ed adatta a piccoli progetti scritti da un solo sviluppatore. Siete ovviamente liberi di usare qualsiasi altro approccio desideriate tenendo conto che qualunque tecnica intendiate usare deve soddisfare almeno i criteri elencati poc'anzi.
Scriveremo pertanto una nuova classe per il pannello delle sequenze che chiameremo SequencePanel03. Già il nome ci indica che questa nuova classe è stata introdotta nella versione 0.3 del progetto. Poichè non vogliamo riscrivere i metodi della classe originale (che funzionano perchè li avevamo testati in main02) deriviamo la nuova classe da SequencePanel: in questo modo, la nuova classe eredita tutti i metodi della vecchia versione, non dobbiamo duplicare nemmeno un metodo, nè un membro dati.
Se la nuova classe ha bisogno di nuovi membri dati, li definiamo: questo ci consente di cogliere "al volo" le modifiche apportate ai membri dati del componente.
Per quanto riguarda i metodi, se abbiamo bisogno di un nuovo metodo basta definirlo; anche in questo caso possiamo notare subito che il metodo è nuovo della classe in nuova versione dal momento che è stato definito.
Ma la classe SequencePanel03 non deve aggiungere alcun metodo: deve invece modificare il metodo createPanel in modo da usare le icone per i bottoni speciali anzichè il testo. Ma come distinguiamo un metodo aggiunto da uno modificato nella nuova classe? Facile, con la annotazione @Override!
Quindi le regole di questo approccio al versionamento possono essere riepilogate così:
Gli argomenti al costruttore di ogni pannello giocatore sono:
id l'identificativo del pannello: convenzionalmente, si imposta come ID=0 il pannello di sinistra e come ID=1 il pannello di destra. title: il titolo del pannello che viene visualizzato nel bordo bianco che incornicia l'intero pannello del giocatore (vedi Introduzione) l: il listener (=gestore) degli eventi di input; questo argomento sarà approfondito in Il listener di input. params i parametri di gioco poichè la visualizzazione dei componenti dipende da essiUna piccola annotazione merita l'argomento params: benchè i parametri di gioco siano assolutamente necessari solo alla logica della applicazione, il pannello del giocatore ne ha bisogno perchè nella strategia della applicazione abbiamo deciso di dare un forte feedback all'utente già nella grafica. In particolare:
n (=numero di codici a disposizione) viene usato per abilitare solo quei bottoni di comando minori di n k (= lunghezza della sequenza) viene usato per stabilire quante icone dei simboli visualizzare nella classe SequenceGUI repeat viene usato per disabilitare il bottone corrispondente ad un simbolo inserito in sequenza se repeat è FALSE maxTries viene usato per stabilire quante righe di guess saranno visualizzate nel pannello delle sequenzeIl componente GUI del pannello del giocatore PlayerPanel viene creato col metodo createPanel al quale vanno passati due argomenti: il tipo di simboli e la dimensione delle icone per le sequenze ed i bottoni di comando. Il codice è di facile lettura; unico appunto è che il metodo non crea due o più copie del componente JPanel. Anche se dovesse essere richiamato due o più volte, restituisce sempre e solo il componente creato la prima volta:
L'ultima istruzione che vedete nel codice sopra esposto merita un commento: il metodo setName è implementato in java.awt.Component da cui la classe JPanel deriva indirettamente. Questo metodo può essere usato per impostare una stringa a piacere in ogni componente GUI; la stringa può essere recuperata dal metodo getName in ogni momento ma, in particolar modo, quando si verifica un evento.
Qualsiasi evento inserito nella coda (vedi Il gestore degli eventi) deriva da EventObject il cui unico compito è quello di memorizzare quale oggetto ha causato l'evento. Il suo unico metodo interessante è il getSource che ritorna, appunto, l'oggetto sorgente cioè l'oggetto che ha accodato (o generato) l'evento. Per mezzo di questo metodo e del getName del componente è possibile quindi conoscere quale tra tutti i componenti dello stesso tipo ha causato l'evento:
Il pannello del giocatore deve rispondere agli eventi button-clikked generati dai bottoni di comando e per questo motivo il pannello deve implementare l'interfaccia ActionListener:
Il metodo actionPerformed ottiene la commandAction specifica per ogni bottone e per ogni case richiama un metodo privato specifico. Il blocco switch analizza solo il primo carattere della commandAction dal momento che per ogni bottone il primo carattere della action-command è unico:
case D (=Done): richiama il metodo privato done case C (=Check): richiama il metodo privato check case R (=Resign): richiama il metodo privato resign case 0 .. 9 (i codici dei 10 simboli): richiama il metodo privato addSymbol case b (=backspace): richiama il metodo privato backspace case c (=cancel): richiama il metodo privato cancel Il metodo addSymbol viene richiamato quando l'user clikka uno dei bottoni dei simboli nel pannello comandi; i bottoni dei simboli dipendono dal tipo di simbolo desiderato dallo user (possono essere i numeri da "0" a "9", le lettere da "A" a "J" oppure uno dei dieci colori previsti dalla applicazione, vedi La classe Params) ma la loro actionCommand è sempre la stessa: un codice da "0" a "9".
Il simbolo deve ovviamente essere aggiunto alla sequenza ma ... quale? La solution oppure la guess? La risposta a questa domanda sta nella fase del gioco: se siamo nella prima fase cioè quella della scelta della solution, il simbolo và aggiunto alla solution mentre se siamo nella seconda fase và aggiunto alla guess.
La fase di gioco viene determinata dal membro dati guessIndex che stabilisce anche la riga delle guess che deve essere aggiornata:
I metodi addSolutionSymbol e addGuessSymbol ritornano un booleano che vale true se la sequenza è completa. Nel caso la sequenza non fosse completa, il metodo addSymbol del pannello giocatore disabilita il bottone relativo al simbolo appena aggiunto alla sequenza ma solo se nei parametri di gioco il flag di ripetizione è repeat=false.
Questo perchè se la ripetizione dei simboli non è ammessa, è opportuno disabilitare il bottone del simbolo appena aggiunto. Viceversa, se la sequenza è completa, oltre alla abilitazione del bottone Done/Check è necessario riabilitare tutti i bottoni dei simboli dal momento che l'user può scegliere una nuova sequenza semplicemente aggiungendo altri simboli.
Il metodo privato backspace funziona più o meno allo stesso modo ma a parti invertite: il membro dati guessIndex stabilisce ancora la fase di gioco: se negativo siamo nella prima fase, quella della solution; in caso contrario siamo nella seconda fase, quella delle ipotesi di soluzione.
La differenza rispetto alla addSymbol in cui il bottone relativo al simbolo aggiunto alla sequenza veniva disabilitato, in questo metodo dobbiamo abilitare il bottone in argomento dal momento che il simbolo è stato rimosso dalla sequenza e può essere aggiunto nuovamente.
Infine, la rimozione di un simbolo dalla sequenza presuppone sempre che la sequenza non sia completa, semmai lo fosse stata, e quindi è necessario disabilitare il bottone Done o Check a seconda della fase del gioco:
Il metodo cancel cancella l'intera sequenza della solution se guessIndex è negativo, oppure della guess nel caso il membro dati anzidetto sia positivo o pari a zero.
Si deve anche disabilitare il bottone Done o Check a seconda della fase di gioco e, infine, abilitare tutti i bottoni dei simboli nel sottopannello dei comandi dal momento che la sequenza non contiene più alcun simbolo:
I metodi visti finora gestivano le sequenze nel sottopannello delle sequenze ma non avevano un significato particolare: tutte le azioni che riguardano la scelta di una sequenza possono essere gestite dal pannello del giocatore.
Ma la pressione dei bottoni Done, Check e Resign hanno un significato particolare: indicano che il giocatore ha scelto una sequenza e questa deve essere considerata come un vero e proprio input che deve essere gestito dalla logica del gioco; il pannello del giocatore è insufficiente a gestire l'input della sequenza. A questo proposito và ricordato che anche il resign può essere considerata una sequenza di guess: si tratta di una sequenza di resa.
Ma noi non abbiamo ancora implementato la logica del gioco e, quindi, dove và a finire l'input? La risposta più immediata è: ma nel Main, è ovvio! E' nel programma principale che c'è la logica del gioco, no? Ebbene, la risposta è NO, non sarà nel programma principale che manterremo la logica del gioco ma in una classe specializzata, scritta proprio per gestire la logica del gioco e solo quella.
Allora, adesso dove lo mandiamo visto che la classe deputata a gestirlo non è stata ancora scritta? Questa è la classica situazione nella quale ci serve una interfaccia: non è necessario sapere adesso a quale classe invieremo l'input del giocatore, è sufficiente sapere che qualsiasi classe và bene, oggi è la classe Main, domani sarà la classe MasterMind e dopodomani sarà una altra ancora. L'importante è che qualsiasi classe venga utilizzata per gestire l'input dello user definisca alcuni fondamentali metodi che vengono richiamati dal pannello giocatore quando l'input è disponible.
Ma questa è proprio la caratteristica delle interfaccie!
Definiamo pertanto la interfaccia InputListener la cui implementazione dovrà essere passata al costruttore del pannello giocatori in modo che i metodi privati del pannello possano passare l'input ad una classe specifica che definiremo in seguito:
Avrete sicuramente notato che ai metodi della classe che implementa la interfaccia InputListener abbiamo passato la ID del pannello del giocatore come argomento. Il perchè lo scopriremo nella prossima sezione.
La interfaccia che gestisce l'input dell'utente è la seguente:
Gli argomenti sono i seguenti:
id l'identificativo del giocatore o del pannello GUI che invia l'input solution la sequenza segreta guess la ipotesi di soluzione gui TRUE se l'input proviene dalla GUI cioè da un pannello giocatore: in questo caso id si riferisce alla ID del pannello FALSE se l'input proviene da un oggetto giocatore, cioè derivato da Player: in questo caso id si riferisce al turno del giocatoreA che cosa serve l'argomento gui? In fondo, ogni giocatore ha il suo pannello giocatore con la propria ID: ZERO per il pannello di sinitra e UNO per quello di destra. Ebbene, non è così e l'argomento booleano gui è assolutamente indispensabile. Facciamo un esempio concreto:
Supponiamo che un umano il cui nome giocatore è "Lukas" giochi una partita contro il computer il cui nome di default è "PlayerAI1". Questa è la situazione nella prima fase, quando i giocatori scelgono la solution:
Il giocatore umano usa il pannello comandi del proprio pannello-giocatore per scegliere la solution e, subito dopo questa operazione, ha inizio la seconda fase del gioco nella quale i giocatori tentano di indovinare la solution dell'avversario. E' piuttosto intuitivo che le guess del computer dovrebbero essere inserite nel pannello del giocatore umano, subito sotto alla solution del giocatore umano in modo che sia visivamente chiaro come il computer sta procedendo.
Allo stesso modo, le guess del giocatore umano dovrebbero essere inserite nel pannello del giocatore I.A. e che, quindi, i bottoni abilitati per l'umano siano quelli del pannello con ID=1 (il pannello del computer). Per intenderci, la situazione è la seguente:
Appare quindi piuttosto chiaro che le guess del giocatore umano vengono registrate nella actionPerformed del pannello destro, quello con ID=1, quello associato al computer. Per questo motivo è necessario passare al listener di input, oltre alla ID del pannello/giocatore anche un flag che indica se l'input avviene dalla GUI oppure no.
Il turno del giocatore che ha inviato l'input deve essere determinato dal server di gioco in base alla seguente elaborazione:
gui è FALSE, l'argomento id è il turno di giocogui è TRUE:id id Ora scriviamo la classe principale Main03 per valutare la interfaccia del pannello giocatore. Creiamo tre pannelli giocatore con diversi parametri di gioco e usiamo il pannello comandi per editare la solution e le guess. Ogni pannello giocatore gestisce le sequenze in modo autonomo: quando si arriva alla fine delle possibili guess, il pannello si resetta e si può ricominciare daccapo. I parametri usati nei tre pannelli sono i seguenti:
| Parametro | Panel_0 | Panel_1 | Panel_2 |
|---|---|---|---|
| codex | NUMBERS | LETTERS | COLORS |
| n | 8 | 10 | 10 |
| k | 3 | 3 | 4 |
| repeat | false | true | false |
Questo è lo screenshot di ciò che vogliamo ottenere e verificare:
Dal test eseguito possiamo verificare che:
n è uguale a 8: i codici disponibili vanno da 0 a 7 repeat è impostato a TRUE, i bottoni devono essere abilitati repeat è impostato a FALSE nel Panel_2, e quindi i bottoni dei colori arancio, rosso e verde sono correttamente disabilitati poichè già inseriti nella sequenza parzialeQuindi tutto ok? Non proprio: quando implementeremo la logica del gioco ci accorgeremo che c'è un bug (letteralmente significa "insetto", "bacherozzo" ma in informatica indica un errore logico) nella classe PlayerPanel che consente al giocatore umano di inserire simboli ripetuti anche in presenza del parametro di gioco repeat=false. Sfrutteremo questo bug (ovviamente per scopi disonesti) nella sezione Le backdoors
Il codice della classe principale è di facile lettura; solo un paio di appunti. Il primo appunto riguarda il layout manager usato per posizionare i tre pannelli: invece del BerderLayout usato nel capitolo precedente, è stato scelto il BoxLayout che è uno dei più versatili manager di posizionamento dei componenti. Per i più curiosi, sul portale ufficiale Oracle™è disponibile una guida in lingua inglese che ne riassume le caratteristiche: How to Use BoxLayout.
Useremo alcune delle caratteristiche peculiari del layout manager BoxLayout quando scriveremo la barra di stato della applicazione (vedi La barra di stato).
Il secondo commento da fare è che la app principale implementa la interfaccia InputListener in modo che il pannello dei giocatori richiami il metodo preposto alla pressione dei bottoni di comando Done, Check e Resign
Degno di nota è anche lo spezzone di codice nel metodo haveGuess che avanza automaticamente alla riga successiva ogni volta che una guess viene confrontata con la solution (bottone Check). Quando non ci sono più righe di guess disponibili, il pannello del giocatore si resetta e si può ricominciare daccapo.
Questo si ottiene ricreando il content-pane principale istanziano dei nuovi pannelli giocatore ed impostando il nuovo content-pane nel frame utilizzando il metodo JFrame.setContentPane.
Infine, come ultimo commento al codice della classe principale possiamo notare che in questa app di test l'avanzamento alla riga di guess successiva avviene richiamando il metodo PlayerPanel.nextGuessRow. Ma questo metodo non è quello canonico da usare nella logica del gioco in cui, invece, l'indice della riga delle guess dovrebbe essere impostata attraverso il metodo setGuessRow che viene pilotato dal server di gioco.
Il compilatore non può evitare di farci usare il metodo non-canonico ma può comunque aiutarci a prevenirne l'uso nelle versioni future inserendo la annotazione @Deprecated subito prima di definire il metodo:
Con la annotazione @Deprecated il compilatore emette un warning ogni volta che in qualche sorgente usiamo il metodo "deprecato" (=sconsigliato):
mastermind>javac mastermind\game\Main03.java Note: mastermind\game\Main03.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details.
Avete provato a giocare un pò con questa versione della applicazione? Se lo avete fatto avrete notato che il layout manager BoxLayout gestisce lo spazio destinato ai tre pannelli delle sequenze in un modo diverso dal BorderLayout della versione precedente (vedi Il ridimensionamento del frame): mentre il BorderLayout ridimensiona solo il pannello centrale il layout usato in questa versione (il BoxLayout) ridimeniona i tre pannelli in modo uniforme aumentando e diminuendo le dimensioni di tutti i tre pannelli.
La documentazione completa del package descritto in questo capitolo può essere visualizzata clikkando il seguente link che riporta alla documentazione Javadoc della versione 0.3 del progetto: The MasterMind Project Version 0.3
Argomento precedente - Argomento successivo - Indice Generale