DOMANDA [Java] Migliorare gestione pattern observer

Loris89

Nuovo Utente
34
0
CPU
AMD Athlon 64 X2 4200+
Scheda Madre
Asus A8N sli-premium
HDD
Hdd maxtor 80 Gb, Hdd maxtor 120 gb
RAM
2Gb (1+1 Corsair PC3200)
GPU
GeForce 6800GS
Audio
integrata
Monitor
Samsung SyncMaster 152b
PSU
500W (nn ricordo la marca)
OS
Windows XP Service Pack 2
Salve a tutti. Ho realizzato un giochino dei dadi, in cui a turno utente e computer tirano un dado per tot volte, e vince chi alla fine ha totalizzato un punteggio più alto. Il tutto è interattivo. Per aggiornare in tempo reale la grafica ho sfruttato il pattern observer. Il "problema" è che il codice è orribile.
Ho la grafica --> classe View (observer)
Ho la logica --> classe Partita (observable)
Ho un mio JPanel personalizzato --> classe Grid (observer)
Il JPanel personalizzato mostra i dadi giocati mano a mano.

La classe Partita è un runnable, e il suo metodo run() è questo:

Codice:
public synchronized void run() {
        int i = 0;
        Messaggio m = new Messaggio();
        while (start <= 5) {
            //E' il turno del computer
            if (turno == 0) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int risultato = dado.getNumeroCasuale();
                giocatoreA.setGiocata(risultato);
                setChanged();
                String text = buildMessage(1);
                m.setText(text);
                m.setX(x);
                m.setY(y);
                m.setNotIsFineMano();
                m.setUrlDado(dado.getUrlImage());
                notifyObservers(m);
                turno = 1;
                x++;
                i++;
            }
            // E' il turno dell'utente
            if (turno == 1) {
                while (userHavePlayed == false) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                userHavePlayed = false;
                setChanged();
                int risultato = dado.getNumeroCasuale();
                giocatoreB.setGiocata(risultato);
                String text = buildMessage(2);
                m.setText(text);
                m.setX(x);
                m.setY(y);
                m.setNotIsFineMano();
                m.setUrlDado(dado.getUrlImage());
                notifyObservers(m);
                turno = 0;
                x++;
                i++;
            }
            if (i == 2) {
                // Aggiorna il punteggio dei giocatori
                if (giocatoreA.getGiocata() > giocatoreB.getGiocata()) {
                    setChanged();
                    String text = buildMessage(3);
                    m.setText(text);
                    m.setIsFineMano();
                    notifyObservers(m);
                    giocatoreA.addPunti(1);
                }
                if (giocatoreA.getGiocata() < giocatoreB.getGiocata()) {
                    setChanged();
                    String text = buildMessage(4);
                    m.setText(text);
                    m.setIsFineMano();
                    notifyObservers(m);
                    giocatoreB.addPunti(1);
                }
                if (giocatoreA.getGiocata() == giocatoreB.getGiocata()) {
                    setChanged();
                    String text = buildMessage(5);
                    m.setText(text);
                    m.setIsFineMano();
                    notifyObservers(m);
                }
                System.out.println();
                start++;
                y++;
            }
            i = 0; //reset indice mano
            x = 0; //reset indice colonna
        }
        
        // Fine partita
        if(giocatoreA.getPunti() > giocatoreB.getPunti()) {
            setChanged();
            String text = buildMessage(6);
            m.setText(text);
            notifyObservers(m);
            finePartita();
        }
        if(giocatoreA.getPunti() < giocatoreB.getPunti()) {
            setChanged();
            String text = buildMessage(7);
            m.setText(text);
            notifyObservers(m);
            finePartita();
        }
        if(giocatoreA.getPunti() == giocatoreB.getPunti()) {
            setChanged();
            String text = buildMessage(8);
            m.setText(text);
            notifyObservers(m);
            finePartita();
        }
    }

Come vedete io costruisco messaggi troppo complessi per far capire alla view cosa deve aggiornare.
Vorrei che ci fosse una separazione più netta. Ad esempio vorrei che la classe Messaggio fosse decisamente più banale, tipo questa (trovata in rete):

Codice:
public class MsgObserver {
  public final static int START = 0;
  public final static int DOWNLOAD = 1;
  public final static int READLINE = 2;
  public final static int END = 3;
  public final static int EXCEPTION = 4;
  public int code;
  public Object msg;
  public MsgObserver(int code, Object msg) {
    this.code = code;
    this.msg = msg;
  }
}

In partita vorrei evitare di agire su Messaggio quindi, e lanciare semplicemente:
Codice:
changedState(new MsgObserver(MsgObserver.START, null));

Creando tutti gli stati necessari, in modo tale che i vari osservatori sappiano esattamente cosa fare
ogni volta.

Non lo so fare però, perché da quel che potete vedere nel metodo run() ci sono parecchie variabili
che vado ad impostare a Messaggio che poi verranno utilizzate dalle View. Come posso fare per passare
i medesimi dati alle View senza passare per Messaggio?
 

1nd33d

Utente Attivo
653
279
CPU
Intel i5 3570K @ 4,5Ghz
Dissipatore
Scythe Mugen 2
Scheda Madre
Gigabyte Z77X-UD3H
HDD
Samsung 840 PRO 256GB + Sandisk Ultra 250GB + Sandisk Plus 960GB
RAM
2x8GB Crucial Ballistix Tactical @2000Mhz CL9
GPU
XFX RX480 GTR Black Edition
Audio
Auzentech X-Fi Forte
Monitor
AOC i2369VW
PSU
Seasonic P660
Case
eh?
Periferiche
Razer Naga HEX v2
OS
Windows 10 64bit - Linux Mint 18
Ultima modifica:

Loris89

Nuovo Utente
34
0
CPU
AMD Athlon 64 X2 4200+
Scheda Madre
Asus A8N sli-premium
HDD
Hdd maxtor 80 Gb, Hdd maxtor 120 gb
RAM
2Gb (1+1 Corsair PC3200)
GPU
GeForce 6800GS
Audio
integrata
Monitor
Samsung SyncMaster 152b
PSU
500W (nn ricordo la marca)
OS
Windows XP Service Pack 2
Forse non sono stato molto chiaro. Si le ho usate XD. Però notificare così i cambiamenti è orribile. Creo un messaggio decisamente complesso. Non sarebbe più bello e pulito notificare un cambiamento in una sola riga di codice ogni dove lo desidero?
Poi il metodo update() dentro la classe che osserva, in base al tipo di messaggio (variabile static di MsgObserver), capisce cosa
fare, e dovrebbe essere in grado di accedere a determinate variabili. Ad esempio un problema è questo. Ora come ora se volessi far
capire all'utente quando è il suo turno tramite una setText("E' il tuo turno") ad una label, dovrei notificare un messaggio sia appena è il turno del pc (per togliere il testo alla label), sia appena è il turno dell'utente, quando non ho ancora settato altre variabili. Se ci provo ottengo svariate eccezioni, perché ora come ora quando chiamo notifyObservers(m), i metodi update vanno comunque ad accedere a quelle variabili non settate che troveranno vuote: nullpointerexception. Dovrei aggiungere un ulteriore controllo andando ad incrementare la """bruttità""" XD.

Quello che vorrei avere è questo (fai un confronto con il vecchio run() che ho messo sopra):

Codice:
   [MENTION=75845]Override[/MENTION]
	public synchronized void run() {
		int i = 0;
		while (start <= 5) {
			// E' il turno del computer
			if (turno == 0) {
				changedState(new MsgObserver(MsgObserver.TURNOCOMPUTER, null));
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				int risultato = dado.getNumeroCasuale();
				giocatoreA.setGiocata(risultato);
				changedState(new MsgObserver(MsgObserver.PCGIOCATO, null));
				turno = 1;
				x++;
				i++;
			}
			// E' il turno dell'utente
			if (turno == 1) {
				changedState(new MsgObserver(MsgObserver.TURNOUTENTE, null));
				while (userHavePlayed == false) {
					try {
						wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				userHavePlayed = false;
				int risultato = dado.getNumeroCasuale();
				giocatoreB.setGiocata(risultato);
				changedState(new MsgObserver(MsgObserver.UTENTEGIOCATO, null));
				turno = 0;
				x++;
				i++;
			}
			if (i == 2) {
				// Aggiorna il punteggio dei giocatori
				if (giocatoreA.getGiocata() > giocatoreB.getGiocata()) {
					changedState(new MsgObserver(MsgObserver.VINTOMANOCOMPUTER,
							null));
					giocatoreA.addPunti(1);
				}
				if (giocatoreA.getGiocata() < giocatoreB.getGiocata()) {
					changedState(new MsgObserver(
							MsgObserver.VINTOMANOGIOCATORE, null));
					giocatoreB.addPunti(1);
				}
				if (giocatoreA.getGiocata() == giocatoreB.getGiocata()) {
					changedState(new MsgObserver(MsgObserver.PAREGGIOMANO, null));
				}
				System.out.println();
				start++;
				y++;
			}
			i = 0; // reset indice mano
			x = 0; // reset indice colonna
		}


		// Fine partita
		if (giocatoreA.getPunti() > giocatoreB.getPunti()) {
			changedState(new MsgObserver(MsgObserver.VINTOCOMPUTER, null));
			// finePartita(); chiamato ora dal metodo update della view!
		}
		if (giocatoreA.getPunti() < giocatoreB.getPunti()) {
			changedState(new MsgObserver(MsgObserver.VINTOUTENTE, null));
			// finePartita();
		}
		if (giocatoreA.getPunti() == giocatoreB.getPunti()) {
			changedState(new MsgObserver(MsgObserver.PAREGGIO, null));
			// finePartita();
		}
	}


	private void changedState(MsgObserver msg) {
		setChanged();
		notifyObservers(msg);
	}

Però dovrei capire come dagli update() possa accedere ai dati che mi servono (variabili x, y soprattutto)

- - - Updated - - -

Ho risolto. Ho fatto esattamente come ho detto, tramite variabili statiche che indicano il tipo di cambio di stato. Per i dati ho creato un'altra classe sulla quale vado a fare side effect dalla classe Partita. I metodi update() attingono i dati da questa.

- - - Updated - - -

Però ogni tanto il mio programma si blocca. Delle volte può farlo dopo 20 volte che giochi, altre volte può farlo subito. Capita alla fine, e per la precisione quando deve uscire la finestrella (message dialog) che mostra il vincitore con il risultato. L'immagine dell'ultimo dado non viene caricata, esce il message dialog, vuoto e tutta la grafica risulta bloccata. Preferirei che lo facesse sempre, palesando un errore grave, ma dato che lo fa solo ogni tanto non so cosa cambiare.

Intanto chiedo se è giusto questo schema quì, dentro la classe Runnable:

Codice:
synchronized void run() {
	...
	if(...) {
		while(variabile == false) {
			wait();
		}
                prosegui con le altre operazioni
	}
}

synchronized void metodo() {
	variabile = true;
	notify();
}
 

1nd33d

Utente Attivo
653
279
CPU
Intel i5 3570K @ 4,5Ghz
Dissipatore
Scythe Mugen 2
Scheda Madre
Gigabyte Z77X-UD3H
HDD
Samsung 840 PRO 256GB + Sandisk Ultra 250GB + Sandisk Plus 960GB
RAM
2x8GB Crucial Ballistix Tactical @2000Mhz CL9
GPU
XFX RX480 GTR Black Edition
Audio
Auzentech X-Fi Forte
Monitor
AOC i2369VW
PSU
Seasonic P660
Case
eh?
Periferiche
Razer Naga HEX v2
OS
Windows 10 64bit - Linux Mint 18
Intanto chiedo se è giusto questo schema quì, dentro la classe Runnable:

Codice:
synchronized void run() {
    ...
    if(...) {
        while(variabile == false) {
            wait();
        }
                prosegui con le altre operazioni
    }
}

synchronized void metodo() {
    variabile = true;
    notify();
}
E' sbagliato per il semplice fatto che il primo metodo synchronized quando trova la variabile falsa, si blocca sul wait() in attesa di un notify.
Ora il problema è che il metodo che usi per portare a vera la variabile è anch'esso synchronized, per cui non potrà mai essere eseguito fino a che non termina il primo metodo, che però non terminerà mai in quanto in attesa di un notify che non arriva.
Prova a togliere i synchronized, eventualmente ti sincronizzi su un altro oggetto.
 

Loris89

Nuovo Utente
34
0
CPU
AMD Athlon 64 X2 4200+
Scheda Madre
Asus A8N sli-premium
HDD
Hdd maxtor 80 Gb, Hdd maxtor 120 gb
RAM
2Gb (1+1 Corsair PC3200)
GPU
GeForce 6800GS
Audio
integrata
Monitor
Samsung SyncMaster 152b
PSU
500W (nn ricordo la marca)
OS
Windows XP Service Pack 2
Ottengo eccezione se li tolgo entrambi o da uno o dall'altro! Come posso risolvere? Lo scopo finale sarebbe generare un'attesa. Cioè il computer gioca il suo dado e il run si mette in attesa che l'utente clicchi sul bottone, la cui pressione fa capire al run che deve proseguire.

- - - Updated - - -

Ho provato a fare una modifica ma non capisco cosa sbaglio questa volta ;_;

In sostanza ho creato una nuova classe:
Wait

Codice:
public class Wait {
	
	private boolean userHavePlayed;
	
	public Wait() {
		userHavePlayed = false;
	}
	
	public synchronized void setUserHavePlayed() {
		userHavePlayed = true;
		notifyAll();
	}
	
	public boolean getUserHavePlayed() {
		return userHavePlayed;
	}
	
	public void setFalseUserHavePlayed() {
		userHavePlayed = false;
	}
}

Questo è l'ascoltatore:

Codice:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


public class DadoListener implements ActionListener {


	private Wait wait;
	
	public DadoListener(Wait w) {
		wait = w;
	}
	
 [MENTION=75845]Override[/MENTION]
	public void actionPerformed(ActionEvent e) {
		wait.setUserHavePlayed();
	}
}

E questo è il punto critico del metodo run():

Codice:
while (wait.getUserHavePlayed() == false) {
					try {
						wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				wait.setFalseUserHavePlayed();

Ma se premo il pulsante non accade nulla, il mio dado non viene giocato ._.
 

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!