View Full Version : Aiutino con Swing e Threads
Groove89
21-01-2013, 13:48
Mi appello agli utenti che hanno partecipato a questa discussione:
http://www.hwupgrade.it/forum/showthread.php?t=2038842 :D
Mi sembrano abbastanza esperti nell'argomento e credo che possano aiutarmi.
Il discorso mi è stato molto utile ma siccome necessito di accelerare i tempi
non posso approfondire da solo l'argomento ed ho bisogno di qualche consiglio
pratico per il mio caso che è un po più articolato.
Cerco di farla breve. Sto creando il gioco briscola con interfaccia grafica.
Ogni volta che l'utente gioca (clicca sulla carta) ovviamente c'è la logica
che fa i suoi calcoli e la grafica che si aggiorna. Ho problemi tecnici con
l'aggiornamento della grafica. Sfruttando gli esempi postati nella discussione
che ho linkato ho scritto questo ascoltatore per i pulsanti (le carte) del
giocatore umano:
package gui;
import gui.GuiPartita;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingUtilities;
import singleplayer.Partita;
public class MyListenerGiocatore implements ActionListener {
private GuiPartita gp;
private Partita p;
public MyListenerGiocatore(GuiPartita gp, Partita p) {
this.gp = gp;
this.p = p;
}
public void actionPerformed(ActionEvent event) {
computazione(gp,event);
}
private void computazione(final GuiPartita gp, final ActionEvent e)
{
Runnable computazione = new Runnable() {
@Override
public void run() {
//calcolo
if(e.getSource().equals(gp.primaCartaGiocatore)) {
System.out.println("Il giocatore gioca la carta: " + p.getGiocatore().getPrimaCarta() + "\n");
p.setStato(1);
p.interfacciamentoUtente(1);
}
if(e.getSource().equals(gp.secondaCartaGiocatore)) {
System.out.println("Il giocatore gioca la carta: " + p.getGiocatore().getSecondaCarta() + "\n");
p.setStato(1);
p.interfacciamentoUtente(2);
}
if(e.getSource().equals(gp.terzaCartaGiocatore)) {
System.out.println("Il giocatore gioca la carta: " + p.getGiocatore().getTerzaCarta() + "\n");
p.setStato(1);
p.interfacciamentoUtente(3);
}
// qui ha finito: accoda evento grafico
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run() {
gp.aggiornaGrafica();
gp.repaint();
}
});
}
};
(new Thread(computazione)).start();
}
}
Ovviamente non basta perché così facendo la grafica si aggiorna
correttamente solo quando è l'umano a giocare. Deve aggiornarsi anche
quando è il computer a giocare e lo fa in automatico. Come risolvo questo
piccolo problema?
C'è anche il caso particolare della fine della partita. Da qualche parte
devo controllare se la partita è ancora in corso, e se finisce aprire una
nuova finestra che mostra il risultato. Può essere un'azione svolta in
background utilizzando un worker thread? Vi prego un piccolo aiuto, grazie :)
Groove89
22-01-2013, 15:38
Ok la discussione ha spaventato :p
Facciamo così, cerco di essere molto più specifico.
Logica --> classe Partita.
Grafica --> classe GuiPartita.
GuiPartita prende un'istanza di Partita nel costruttore.
Il "motore" della logica che muove il tutto è dato da questo codice presente nella classe Partita:
private void start() {
/* Controlla turno e stato del gioco dell'utente
* e si ramifica in 4 possibili situazioni
*/
/* turno PC */
if(isTurnoPc()) {
/* user non ha giocato (inizio della mano) */
if(!userHavePlayed) {
giocoPcScarta();
System.out.println("E' il turno del giocatore.\n");
}
/*user ha già risposto al gioco pc: calcolo punti e vincitore*/
else {
fineMano();
}
}
/* turno user */
else {
/* user non ha giocato (inizio della mano) */
if(!userHavePlayed) {
/* non accade nulla
* serve il gioco dell'utente
*/
}
/* user ha giocato e attende risposta pc */
else {
giocoPcRisposta(); /* risposta pc */
fineMano(); /* punti e vincitore */
}
}
}
L'idea che mi è venuta in mente poco fa per aggiornare la grafica di volta in volta è inserire nella classe partita una variabile stato che può assumere i
valori 0,1,2,3,4,5.
Stato 0 = Sta iniziando la nuova mano.
Stato 1 = Ha giocato il computer.
Stato 2 = Hanno giocato sia computer che giocatore. Prima il computer e poi
il giocatore.
Stato 3 = Il giocatore ha giocato.
Stato 4 = Hanno giocato sia computer che giocatore. Prima il giocatore e poi
il computer.
Stato 5 = Fine della partita.
Per aggiornare lo stato di volta in volta prenderei il codice di sopra e lo
modificherei così:
private void start() {
stato = 0;
/* Controlla turno e stato del gioco dell'utente
* e si ramifica in 4 possibili situazioni
*/
/* turno PC */
if(isTurnoPc()) {
/* user non ha giocato (inizio della mano) */
if(!userHavePlayed) {
giocoPcScarta();
stato = 1;
System.out.println("E' il turno del giocatore.\n");
}
/*user ha già risposto al gioco pc: calcolo punti e vincitore*/
else {
fineMano();
stato = 2;
}
}
/* turno user */
else {
/* user non ha giocato (inizio della mano) */
if(!userHavePlayed) {
/* non accade nulla
* serve il gioco dell'utente
*/
}
stato = 3;
/* user ha giocato e attende risposta pc */
else {
giocoPcRisposta(); /* risposta pc */
fineMano(); /* punti e vincitore */
stato = 4;
}
}
}
Ovviamente nella medesima classe inserisco il metodo get per lo stato.
La grafica deve poter controllare ciclicamente lo stato della Partita
e vedere in che stato si trova. Questo deve avvenire senza bloccare
la grafica, quindi usando edt o worker thread o non so cosa xD.
Farei una cosa del tipo (non so come e dove):
public void checkPartita() {
while(partita.inPartita()) {
if(stato == 0) {
aggiornaGrafica0();
}
if(stato == 1) {
aggiornaGrafica1();
}
if(stato == 2) {
aggiornaGrafica2();
}
if(stato == 3) {
aggiornaGrafica3();
}
if(stato == 4) {
aggiornaGrafica4();
}
if(stato == 5) {
aggiornaGrafica5;
}
}
}
Potrebbe andar bene? Consigli o botte per me? :D
wingman87
22-01-2013, 21:27
Botte.
No scherzo, ma non capisco esattamente qual è il problema. Senza entrare nel dettaglio, nel primo post hai scritto che riesci ad aggiornare la grafica quando è il giocatore a giocare ma non quando è il computer... non capisco perché.
Vedendo il secondo post sembra che non sai quando dire al computer: "gioca tu". Beh, è semplice, dopo che il giocatore ha giocato no? D'altra parte dal codice che hai scritto è chiaro che il computer gioca if(userHavePlayed).
Se invece inizia a giocare il computer, farà la prima mossa all'inizio del gioco e poi adotterai la stessa logica: appena il giocatore ha giocato, il computer fa la sua mossa.
L'idea che
La grafica deve poter controllare ciclicamente lo stato della Partita
e vedere in che stato si trova.
E' decisamente sconsigliata, molto meglio adottare il pattern Observer, che, detto in estrema sintesi, funziona così: quando lo stato cambia, avvisa l'interfaccia grafica che quindi si aggiorna. In questo modo l'interfaccia non interroga continuamente lo stato ma lo fa solo quando è effettivamente necessario.
Groove89
22-01-2013, 22:39
Forse non sono stato molto chiaro :p La logica funziona, il computer gioca, il giocatore gioca, riesco a svolgere una partita (tranne la fine che devo ancora gestire bene). Il mio problema primario è come far comunicare la grafica e la partita. L'interattività è la cosa che mi sta facendo uscire di testa :mc:
La grafica non può essere aggiornata da dentro la classe stessa quindi ho letto ieri dell'event dispatch thread e del worker thread che usati in modo opportuno permettono di gestire ciò che mi serve senza compromettere la grafica stessa. Questa strada andrebbe bene o il tuo consiglio di usare questo pattern observer (che ho già sentito accennare) è più di un consiglio?
Comunque devo riprecisare che la grafica prende un'istanza di partita. Ho il dubbio che si debba fare al contrario, ossia avviare la grafica
dalla classe partita, è così?
Sono riuscito ad aggiornare in modo poco carino la grafica quando gioca il giocatore perché quando deve giocare il giocatore la logica è ferma ed in attesa. Lo scegliere una carta equivale all'attivazione dell'actionListener relativo che sblocca la logica e aggiorna la grafica utilizzando proprio l'event dispatch thread. Il computer gioca in modo autonomo, per questo non posso fare la stessa identica cosa.
banryu79
23-01-2013, 08:31
@Groove89: la tua soluzione al post #2 secondo me è valida in questo caso, perchè hai solo 6 stati da gestire. Se hai letto bene il vecchio post che citavi all'inizio (e soprattuto il tutorial su Swing di PGI-Bis che ho in firma) non avrai problemi.
Devi solo stare attento in quale thread fai cosa:
- mutazioni di componenti grafiche --> EDT (SwingUtilities.invokeLater o invokeAndWait a seconda dei casi per inserire delle istruzioni nella event queue).
- tutto il resto --> thread non EDT (il "main" thread più eventuali altri thread nuovi).
L'importante è che, leggendo e scrivendo il tuo codice, tu sappia dire senza incertezze quale metodo viene eseguito in quale thread.
Groove89
23-01-2013, 09:15
Stavo giusto approfondendo ora il pattern observer e devo dire che mi piace veramente tanto, il codice guadagnerebbe eleganza e andrei a disaccoppiare per bene grafica e logica :) Inoltre espanderò questo gioco in futuro aggiungendo una chat e il funzionamento in lan/rete quindi forse è meglio applicare fin da subito il pattern observer.
@banryu79
Penso che comunque devo andare ad utilizzare anche l'edt e cose varie unite al pattern observer giusto? Sono costretto. In particolare a me la classe GuiPartita estende JFrame ed implementa Observer, quindi avrà il metodo public void update(Observable obs, Object arg) che in base al tipo di notifica ricevuta dalla Partita (che implementa Observable) aggiorna la grafica. Per aggiornare la grafica devo usare l'edt. Sinceramente ho già provato ad aggiornare la grafica invocando invokeAndWait (creando un nuovo oggetto Runnable ecc.. ecc..) ma ho avuto una bella eccezione >.< Comunque, è questa la strada giusta almeno? :)
L'unica cosa che devo ancora capire bene è se la GuiPartita deve o non deve prendere la Partita nel costruttore e se è la Partita che deve avviare la GuiPartita.
Aggiornamento:
Sto lavorando da solo sempre in attesa di consigli ed ho cambiato un po l'impostazione visto che secondo me era corretta. La mia finestra principale (per avviare una nuova partita contro il pc oppure online) avvia la Gui a cui passa i Giocatori. La Gui a sua volta
avvia una partita a cui passa i Giocatori. Sembra funzionare. Per completezza riporto i pezzetti di codice interessanti:
Avvio dalla finestra principale
...
record = new RecordClassifica(nome,0,0,0);
classifica1.aggiungiRecord(record);
//creo i giocatori
Giocatore a = new Giocatore(nome);
Giocatore b = new GiocatoreElettronico("Computer");
//Avvio la partita
GuiPartita gp = new GuiPartita("JBriscola 0.1",a,b,record);
this.setVisible(false);
...
Avvio della partita (che estende observable) dalla gui (che implementa Observer):
...
private Partita partita;
//Players and Record
private Giocatore giocatore;
private GiocatoreElettronico computer;
private RecordClassifica record;
//costruttore
public GuiPartita(String titolo, Giocatore g, Giocatore c, RecordClassifica r) {
super(titolo);
this.giocatore = g;
this.computer = (GiocatoreElettronico)c;
this.record = r;
partita = new Partita(giocatore,computer,record);
partita.addObserver(this);
}
//Fuori dal costruttore..
//Update Graphics Method
public void update(Observable obs, Object obj) {
MsgObserver msg = (MsgObserver)obj;
//Da completare con tutti i thread relativi alle swing..
}
Che ne dite? :) Questa discussione può essere utile a molti utenti! ^^
Ah ps, ho creato questa classe per i messaggi che la classe Partita può inviare alla grafica.
package singleplayer;
public class MsgObserver {
public final static int START = 0;
public final static int PC_GIOCATO = 1;
public final static int TUTTI_GIOCATO = 2;
public final static int USER_GIOCATO = 3;
public final static int FINE = 4;
public int code;
public Object msg;
public MsgObserver(int code, Object msg) {
this.code = code;
this.msg = msg;
}
}
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.