Syra

Votes taken by RootkitNeo

  1. .
    Il lister personalizzato lo utilizzo per creare un ascoltatore dedicato al Model. Quando il model viene aggiornato e lo stato interno cambia, la GUI viene aggiornata grazie alla notifica di questo evento.
  2. .
    Non penso abbia copiato male dal libro, penso che il libro stesse piuttosto dimostrando che senza puntatori lo scambio non avviene (ed ecco perchè il libro stesso ha detto che si devono utilizzare i puntatori).

    L'esempio che ti sta mostrando il libro è l'esempio classico sui puntatori. Non prendere però l'esempio "alla lettera", in quanto ciò che vuole passarti è che utilizzando i puntatori passi un indirizzo di memoria (per meglio dire, è il puntatore che memorizza l'indirizzo). La potenza dei puntatori la coglierai più avanti, così come il loro utilizzo.
  3. .


    Progettazione di GUI in Java


    Marco 'RootkitNeo' C.


    La progettazione di un software è sempre una parte molto delicata e spesso giunti alla GUI sorge un problema: come la si separa dal resto del codice?
    In questo articolo darò alcune linee guida generiche, e poi mostrerò 3 esempi veri e propri, utilizzando il gioco del Tris. Mi preme sottolineare che gli esempi sono molto limitati e che non coprono sicuramente tutti i possibili scenari.
    L'implementazione di Tris sarà nella versione più semplice possibile: Human vs. Human.






    Intruduzione e Panoramica



    L'articolo affronterà i punti così come da indice. Il primo caso non verrà analizzato tramite codice, trattandosi dell'approccio errato.
    Il secondo caso mostra invece l'approccio più semplice per separare i dati su cui opera il software dalla grafica dell'applicazione.
    Il terzo invece presenta uno degli approcci più interessanti ed ordinati: MVC. Si tratta del classico Model View Controller dove il Model manipola i dati su cui il software opera (e ne implementa la logica); il Controller fa da tramite tra la logica e la grafica, gestendo gli eventi della GUI; ed il View mostra l'elaborazione dei dati e permette di interagire (provocando eventi).




    Approccio Errato: mischiare dati e logica con la GUI



    Questo tipo di approccio è tipicamente utilizzato - inizialmente, si spera - da chi non ha competenze di progettazione e non conosce probabilmente bene nemmeno le GUI. Consiste nel creare un'interfaccia grafica che gestisce anche la logica. Un classico esempio potrebbe essere l'estensione di una finestra grafica di Swing (JFrame), l'implementazione di uno o più listeners per gestire gli eventi, e la creazione dei dati su cui si deve operare, come ad esempio un elenco di persone memorizzato in un array e metodi incaricati magari all'inserimento ed all'eliminazione.

    Ora è lecito chiedersi: cosa rende così errato questo approccio?
    Un'implementazione come questa ha differenti ripercussioni, sia da un punto di vista della manutenibilità della GUI, sia da quello della logica. La GUI ne risentirà in quanto un minimo cambio al suo aspetto grafico provocherà la riscrittura - o il riadattamento - di parti di codice che accede ai dati, non avendo separazioni. Di fatto quindi non si potrà modificare solo la GUI, ma si è costretti a toccare anche i dati su cui si opera.
    Un cambio della logica dell'applicativo si rifletterà invece sulla GUI, in quanto non si ha alcun livello di astrazione tra i due.

    L'importanza dell'Astrazione
    La programmazione così come tanti altri aspetti del nostro quotidiano si basano su qualche forma di astrazione. L'astrazione consiste nell'incapsulare la complessità di qualcosa, così da renderne semplice l'utilizzo. Un classico esempio è l'automobile: è necessario sapere cosa accade dettagliatamente quando si preme l'acceleratore, o la frizione? Fondamentalmente no, non è necessario. Tutto ciò che facciamo è premere l'acceleratore per incrementare la velocità, non ci serve sapere cosa accade esattamente.

    Nell'ambiente informatico questo aspetto torna molto. Si pensi ad esempio alla gestione dell'I/O da un dispositivo, sia esso una tastiera. Quando leggiamo un input dal così definito "flusso di input predefinito" (la tastiera, stdin), tutto ciò che facciamo è utilizzare una funzione o una classe - nel caso della OOP - che incapsula la complessità su più strati.
    Il discorso è analogo all'allocazione della memoria. In Java si alloca memoria per un oggetto utilizzando l'operatore new. In C esistono funzioni per allocare dinamicamente la memoria, come malloc(). Questa funzione a sua volta si appoggia - sotto Windows - alla libreria di Windows per gestire l'allocazione (invocando HeapAlloc).

    Tutto ciò cosa ci permette di comprendere? Che quando creiamo una o più classi per gestire la logica del nostro programma, forniremo dei metodi di accesso a tali dati, e sarà sufficiente richiamare questi metodi per astrarre tutta la complessità di una data operazione.





    Panoramica sui componenti grafici utilizzati nelle applicazioni



    La libreria di Java mette a disposizioni due librerie per la gestione delle GUI. Una è l'Abstract Window Toolkit, conosciuto come AWT, che fornisce gli strumenti necessari al rendeering grafico, alla gestione di finestre, bottoni, pannelli, campi di testo, etichette, menu a tendina, ed eventi su essi. Questa libreria attualmente - e da un pò di anni a questa parte in realtà - non viene più utilizzata direttamente. La libreria inizialmente era poco più di un'astrazione della libreria reale del sistema operativo; la si considera una libreria ad un più basso livello rispetto alla seconda, Swing.

    Swing è stata introdotta a partire da Java 1.2 ed è scritta in Java, utilizzando librerie di Java (come Java 2D). Tuttavia essendo ad un livello più alto, si va ad appoggiare su AWT. Se guardiamo ad esempio il componente JFrame, ci accorgeremo che estende java.awt.Frame.

    Tutto ciò che serve per creare una GUI lo si trova tipicamente in 2 package: java.awt.*; e javax.swing.* e per la gestione degli eventi java.awt.event.*. Per capire se quello che stiamo vedendo nel codice è un oggetto appartenente ad AWT o a Swing è sufficiente guardare il suo prefisso; tutti i componenti Swing iniziano infatti con una J. Alcuni esempi sono: JFrame, JPanel, JComboBox, JTextField, etc. Tutte le versioni senza J appartengono ad AWT.

    Per una trattazione completa sul funzionamento di tutte le parti coinvolte vi invito a leggere sul sito di Oracle il tutorial Using Swing Components.

    Chiariti questi aspetti, mostrerò di seguito una rapidissima panoramica sui componenti principali e su come si utilizzano.
    JFrame: si tratta della finestra principale di un'applicazione, o di una finestra secondaria di un applicazione. Ogni qualvolta si necessita di una finestra, può essere istanziato un nuovo oggetto JFrame. Ci sono più approcci per la creazione della finestra: si può estendere JFrame, o si può creare un oggetto JFrame.

    JPanel: è il pannello che racchiude i componenti della finestra, come campi di testo, etichette di testo, menu a tendina, bottoni etc. Tutto di solito lo si racchiude in un JPanel, che a sua volta, verrà aggiunto ad un JFrame. Un JPanel dispone i componenti sulla base del LyoutManager che sta utilizzando. Quando si crea un'istanza di JPanel senza passare nulla al costruttore, internamente JPanel utilizzerà un layout chiamato FlowLayout. La regola di questo layout è che tutti i componenti verranno aggiunti sulla riga: quando non ci sarà più spazio, continueranno sulla riga sottostante.

    I Layout sono molti, ed inoltre è possibile creare un proprio Layout (non che ve ne sia sempre bisogno, anzi). Giusto per citarne alcuni: FlowLayout, BorderLayout, GridLayout, GridBagLayout.

    JPanel è molto utilizzato, come si può intuire. Permette di incapsulare gli eleemnti della GUI, ma anche di disegnare su essa. E' infatti possibile effettuare un override del metodo public void paintComponent(Graphics); per disegnare su un pannello. Solitamente quando si ha questo tipo di necessità si crea un proprio pannello. Creare un proprio pannello è come creare una propria finestra: è sufficiente estendere il componente, tipo class MyPanel extends JPanel { ... }.

    JLabel: come dice il nome stesso si tratta della classica etichetta di testo che solitamente è affiancata ad un componente come un'area di testo o un menu a tendina. E' anche possibile disegnare su un JLabel, come si fa per JPanel.

    Questi sono più o meno i componenti che verranno utilizzati. Non trattandosi di un articolo dedicato alla creazione di GUI ma alla loro progettazione, non tratterò in maniera approfondita null'altro.



    Primo Approccio: separare la logica dall'interfaccia grafica



    Il primo approccio che presenterò è largamente utilizzato da chi - di solito - scrive in maniera corretta un'applicazione grafica. Ciò che presenterò non dovrà essere però preso alla lettera in quanto vi possono essere sfaccettature: io utilizzerò ciò che si vede più comunemente, l'estensione di JFrame. Tuttavia, un'implementazione analoga la si può avere creando un oggetto JFrame nella classe che gestisce la GUI.



    L'esempio che vedrete sarà composto da 3 sole classi. Ci sarà una classe chiamata TrisModel che gestirà la logica dell'applicativo; una classe chiamata TrisView che gestirà grafica ed eventi con tanto di main(); ed una classe GridCell che gestirà il disegno su una JLabel (GridCell estende JLabel).

    E' importante sottolineare che si potranno creare il numero di classi più opportune per gestire Model e View. Non è necessario - ed anzi non dovrebbe - stare tutto il model in un'unica classe (il discorso è sempre quello dell'astrazione, ed anche dell'incapsulamento). Ho utilizzato questi nomi di classi solo ed unicamente per far intendere la differenza tra la logica e la GUI, ma non sono necessariamente correlate ad un implementazione MVC.

    Analisi di TrisModel.java



    Le variabili presenti sono:

    CODICE
    class TrisModel {
     // ----------------------------------------------------------------------
     public static final int DRAW         = 0;   //Pareggio
     public static final int WIN1         = 1;   // Vittoria Player X
     public static final int WIN2         = 2;   // Vittoria Player O
     public static final int PLAY         = 3;   // La partita continua
     public static final int ILLEGAL_MOVE = -1;  // Mossa non valida
     
     
     private final int  W = 3;       // Dimensione di un lato della griglia
     private final char X = 'X';
     private final char O = 'O';
     
     private char next,old;             // Prossimo giocatore
     
     private char[] field;          // Campo di gioco
     // ----------------------------------------------------------------------


    Le prime costanti verranno utilizzate all'esterno della classe, per capire in quale stato si trova attualmente il model (ed aggiornare la GUI). Le altre sono nel perfetto stile di una classe Java: privati e definiti come costanti, quando serve.
    Il campo di gioco l'ho gestito con un'array al posto di utilizzare una matrice.

    Il costruttore inizializza il campo:
    CODICE
    // ----------------------------------------------------------------------
     TrisModel() {
       field = new char[W*W];
       for(int i=0; i<W*W; i++) field[i] = ' ';
       next  = X;
       old   = X;
     }
     // ----------------------------------------------------------------------


    Le variabili next e old memorizzano il prossimo giocatore ed il precedente, rispettivamente.
    Come si sarà capito il cuore della logica del gioco è sicuramente il campo di gioco, o per meglio dire, gli stati del campo di gioco. Ecco quindi in che modo avviene una mossa:
    CODICE
    /*
      * Mossa di un giocatore
      */
     // ---------------------------------------------------------------
     int move(int x, int y) {
       if(checkIfValid(x,y)) {
         field[x*W+y] = next;
         
         /*
          * 0: pareggio
          * 1: vittoria Player 1
          * 2: vittoria Player 2
          */
         if(isTerminalState()) {
           return (next == X) ? WIN1 : WIN2;
         }
         
         if(noWhiteSpace()) return DRAW;
           
         next = (next == X) ? O : X;
         old = next;
         return PLAY; // La partita continua
       }
       
       return ILLEGAL_MOVE; // Mossa non possibile (posizione non valida)
     }
     // ----------------------------------------------------------------



    checkIfValid() verifica se la posizione clickata e rappresentata da (x,y) è vuota (valida) oppure è già occupata, restituendo true/false.
    L'altro metodo permette di capire se lo stato corrente del campo di gioco è uno stato terminale (vittoria di X o vittoria di O):
    CODICE
    // Verifica se lo stato attuale e' terminale (vittoria/sconfitta)
     private boolean isTerminalState() {
       if(field[0] == next && field[1] == next && field[2] == next ||  /* orizzontale */
          field[3] == next && field[4] == next && field[5] == next ||
          field[6] == next && field[7] == next && field[8] == next ||
         
          field[0] == next && field[3] == next && field[6] == next ||  /* verticale */
          field[1] == next && field[4] == next && field[7] == next ||
          field[2] == next && field[5] == next && field[8] == next ||
         
          field[0] == next && field[4] == next && field[8] == next ||  /* obliquo */
          field[2] == next && field[4] == next && field[6] == next
         ) return true;
         
       return false; // Stato non terminale, partita non vinta
     }


    Il pareggio l'ho gestito separatamente con il metodo noWhiteSpace() che restituisce true se non vi sono più spazi vuoti (quindi lo stato è DRAW).
    Come vedete, qualsiasi modifica interna ad una qualsiasi di questi metodi non ha ripercussioni sull'esterno.


    Analisi di TrisView.java



    La GUI inizia con la dichiarazione delle seguenti variabili:

    CODICE
    class TrisView extends JFrame implements MouseListener {
     // --------------------------------------------------------------------------
     private TrisModel tm;
     private JPanel panel;    // Pannello contenitore dei componenti
     // --------------------------------------------------------------------------


    È infatti prassi comune in questo caso delegare le azioni sul model direttamente alla classe che si occupa della GUI, in modo da poter ottenere dal model i dati necessari all'aggiornamento della GUI e da aggiornare il model.

    CODICE
    // Costruttore
     // ---------------------------------------------------------------------
     TrisView() {
       super("Tris Human vs Human");
       tm = new TrisModel();
       
       panel = new JPanel(new GridLayout(3,3));
       
       for(int i=0; i<3; i++) {
         for(int j=0; j<3; j++) {
           GridCell label = new GridCell(i,j);
           label.addMouseListener(this);
           panel.add(label);
         }
       }
       
       add(panel);
     }
     // ----------------------------------------------------------------------


    Ad ogni label personalizzato si aggiunge un listener. I parametri che riceve GridCell riguardano le coordinate dell'elemento.

    CODICE
    public void mouseReleased(MouseEvent me) {
       GridCell cell = (GridCell) me.getSource();

       // Ottengo il player che ha clickato ed aggiorno il model
       char p = tm.getPlayer();
       int s  = tm.move(cell.getXCoords(), cell.getYCoords());

       // Aggiorno il view
       cell.setItem(p);
       
       // Scelgo l'azione da intraprendere
       switch(s) {
         case TrisModel.WIN1: // Vittoria di uno dei giocatori
         case TrisModel.WIN2:
           gameEnding(p);
           break;
         case TrisModel.DRAW: // Pareggio
           gameDraw();
           break;
       }
     }


    Tutta l'azione si svolge qui. Grazie alle coordinate inserite in GridCell ora possiamo ottenere la posizione dell'elemento all'interno del model, e settarne il relativo valore. Grazie al valore restituito dal model sappiamo quale stato si è verificato.

    Analisi di GridCell.java



    CODICE
    void setItem(char item) {
       this.item = item;
       repaint(); // Chiama paintComponent
     }
     
     // Disegno sul label
     public void paintComponent(Graphics g) {
       super.paintComponent(g);
       
       Graphics2D g2 = (Graphics2D) g;
       setBorder(border);
       
       if(item == 'X') {
         g2.setColor(Color.RED);
         g2.draw(new Line2D.Double(10, 160, 160, 10));
         g2.draw(new Line2D.Double(10,10, 160,160));
       } else if(item == 'O') {
         g2.setColor(Color.BLUE);
         g2.draw(new Ellipse2D.Double(10, 10,158,158));
       }
       
     }


    Questa classe gestisce il disegno del componente - ed il ridisegno ad ogni nuovo assengnamento.



    Esempio: Separare la logica dalla GUI



    Di seguito il codice delle 3 classi:

    TrisModel.java
    CODICE
    /*
    *
    * Model
    *
    */



    class TrisModel {
     // ----------------------------------------------------------------------
     public static final int DRAW         = 0;   //Pareggio
     public static final int WIN1         = 1;   // Vittoria Player X
     public static final int WIN2         = 2;   // Vittoria Player O
     public static final int PLAY         = 3;   // La partita continua
     public static final int ILLEGAL_MOVE = -1;  // Mossa non valida
     
     
     private final int  W = 3;       // Dimensione di un lato della griglia
     private final char X = 'X';
     private final char O = 'O';
     
     private char next,old;             // Prossimo giocatore
     
     private char[] field;          // Campo di gioco
     // ----------------------------------------------------------------------
     
     
     // ----------------------------------------------------------------------
     TrisModel() {
       field = new char[W*W];
       for(int i=0; i<W*W; i++) field[i] = ' ';
       next  = X;
       old   = X;
     }
     // ----------------------------------------------------------------------
     
     /*
      * Mossa di un giocatore
      */
     // ---------------------------------------------------------------
     int move(int x, int y) {
       if(checkIfValid(x,y)) {
         field[x*W+y] = next;
         
         /*
          * 0: pareggio
          * 1: vittoria Player 1
          * 2: vittoria Player 2
          */
         if(isTerminalState()) {
           return (next == X) ? WIN1 : WIN2;
         }
         
         if(noWhiteSpace()) return DRAW;
           
         next = (next == X) ? O : X;
         old = next;
         return PLAY; // La partita continua
       }
       
       return ILLEGAL_MOVE; // Mossa non possibile (posizione non valida)
     }
     // ----------------------------------------------------------------
     
       
     char getPlayer() {
       return old;
     }
     
     /* METODI PRIVATI */
     // ----------------------------------------------------------------
     
     // Verifica se la posizione (x,y) &#232; vuota
     private boolean checkIfValid(int x, int y) {
       return field[x*W+y] == ' ';
     }
     
     
      // Verifica se lo stato attuale e' terminale (vittoria/sconfitta)
     private boolean isTerminalState() {
       if(field[0] == next && field[1] == next && field[2] == next ||  /* orizzontale */
          field[3] == next && field[4] == next && field[5] == next ||
          field[6] == next && field[7] == next && field[8] == next ||
         
          field[0] == next && field[3] == next && field[6] == next ||  /* verticale */
          field[1] == next && field[4] == next && field[7] == next ||
          field[2] == next && field[5] == next && field[8] == next ||
         
          field[0] == next && field[4] == next && field[8] == next ||  /* obliquo */
          field[2] == next && field[4] == next && field[6] == next
         ) return true;
         
       return false; // Stato non terminale, partita non vinta
     }
     
     // Verifica la presenza di spazi vuoti
     private boolean noWhiteSpace() {
       int i = 0;
       for(; i<W*W && field[i] != ' '; i++);
       return i==W*W;
     }
    }


    TrisView
    CODICE
    /*
    *
    * View
    *
    */

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.event.*;


    class TrisView extends JFrame implements MouseListener {
     // --------------------------------------------------------------------------
     private TrisModel tm;
     private JPanel panel;    // Pannello contenitore dei componenti
     // --------------------------------------------------------------------------
     
     // Costruttore
     // ---------------------------------------------------------------------
     TrisView() {
       super("Tris Human vs Human");
       tm = new TrisModel();
       
       panel = new JPanel(new GridLayout(3,3));
       
       for(int i=0; i<3; i++) {
         for(int j=0; j<3; j++) {
           GridCell label = new GridCell(i,j);
           label.addMouseListener(this);
           panel.add(label);
         }
       }
       
       add(panel);
     }
     // ----------------------------------------------------------------------
     
     
     /* METODI */
     
     public void mouseReleased(MouseEvent me) {
       GridCell cell = (GridCell) me.getSource();

       // Ottengo il player che ha clickato ed aggiorno il model
       char p = tm.getPlayer();
       int s  = tm.move(cell.getXCoords(), cell.getYCoords());

       // Aggiorno il view
       cell.setItem(p);
       
       // Scelgo l'azione da intraprendere
       switch(s) {
         case TrisModel.WIN1: // Vittoria di uno dei giocatori
         case TrisModel.WIN2:
           gameEnding(p);
           break;
         case TrisModel.DRAW: // Pareggio
           gameDraw();
           break;
       }
     }
     
     
     // Non utilizzati
     public void mouseClicked(MouseEvent me) {}
     public void mousePressed(MouseEvent me) {}
     public void mouseEntered(MouseEvent me) {}
     public void mouseExited(MouseEvent me)  {}
     
     
     // --------------------------------------------------------------------------  
     void gameDraw() {
       JOptionPane.showMessageDialog(this, "Pareggio");
     }
     
     void gameEnding(char p) {
       JOptionPane.showMessageDialog(this,"<html>Ha vinto il player <span style='color:red'>"+p+"</span></html>");
     }
     
     
     // Entry Point
     // --------------------------------------------------------
     public static void makeGUI() {
       TrisView frame = new TrisView();
       frame.setSize(520,520);
       frame.setResizable(false);
       frame.setVisible(true);
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     }
     
     public static void main(String[] args) {
       try {
         SwingUtilities.invokeAndWait(new Runnable() {
           public void run() {
             makeGUI();
           }
         });
       } catch(Exception e) {}
     }
     
     
    }


    GridCell.java
    CODICE
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.awt.geom.*;
    import javax.swing.border.Border;

    class GridCell extends JLabel {
     // -------------------------------------------------------------------
     // Bordo dei GridCell
     private final Border border = BorderFactory.createLineBorder(Color.BLACK);
     private char item;  // Player che ha clickato
     private int x, y;   // Coordinate del GridCell
     // -------------------------------------------------------------------
     
     
     // ------------------------------------------------------------------
     GridCell(int x, int y) {
       this.x = x;
       this.y = y;
       
       item = ' ';
       setOpaque(true);
       setBackground(Color.WHITE);
       
     }
     // ------------------------------------------------------------------
     
     
     /* METODI */
     
     int getXCoords() {
       return x;
     }
     int getYCoords() {
       return y;
     }

     void setItem(char item) {
       this.item = item;
       repaint(); // Chiama paintComponent
     }
     
     // Disegno sul label
     public void paintComponent(Graphics g) {
       super.paintComponent(g);
       
       Graphics2D g2 = (Graphics2D) g;
       setBorder(border);
       
       if(item == 'X') {
         g2.setColor(Color.RED);
         g2.draw(new Line2D.Double(10, 160, 160, 10));
         g2.draw(new Line2D.Double(10,10, 160,160));
       } else if(item == 'O') {
         g2.setColor(Color.BLUE);
         g2.draw(new Ellipse2D.Double(10, 10,158,158));
       }
       
     }

    }





    Secondo Approccio: MVC



    Riporterò solo le parti di codice rilevanti ai fini della spiegazione



    In questo esempio ci soffermeremo sulla progettazione MVC (Model, View, Controller). La classe Model gestirà tutti gli aspetti relativi alla logica ed allo stato del programma, sarà quella che avrà - ad esempio - delle strutture dati ed altro ancora. Nel nostro caso il Model è praticamente inalterato, quindi eviterò di commentarlo.
    Tutto questa volta sarà gestito con 5 classi:

    • TrisModel

    • TrisView

    • TrisController

    • GridCell

    • TrisMain


    Il model come già anticipato è quello di prima, vi sono solo piccole modifiche (come la scomparsa della variabile old).

    Come vedremo a breve, tutto è basato su questo concetto: sul view accade qualcosa (un evento) che il Controller gestisce aggiornando anche lo stato interno (Model). Se necessario poi aggiorna anche il View con la risposta del Model... dipende dai casi insomma.

    Analisi di TrisView.java


    Il cambiamento al View è stato importante: gli eventi ora vengono gestiti dal Controller, quindi il View non avrà più bisogno della interface; non solo, come si sarà intuito il Controller fa da tramite tra il Model ed il View, quindi il View non ha più nemmeno bisogno di un riferimento al Model.

    CODICE
    class TrisView {
     // --------------------------------------------------------------------------
     private JFrame frame;    // Finestra dell'applicazione
     private JPanel panel;    // Pannello contenitore dei componenti
     // --------------------------------------------------------------------------
     
     TrisView() {}


    È cambiato inoltre anche il modo di utilizzare il JFrame. Siamo passati da is a a has a; ora TrisView non è un JFrame, ma usa (ha) un JFrame.

    CODICE
    // Creo i componenti ed aggiungo l'ascoltatore
     void makeComponents(MouseListener ml) {
       panel = new JPanel(new GridLayout(3,3));
       
       for(int i=0; i<3; i++) {
         for(int j=0; j<3; j++) {
           GridCell label = new GridCell(i,j);
           label.addMouseListener(ml);
         
           panel.add(label);
         }
       }
       
       frame.add(panel);
     }


    Il seguente metodo sarà invocato dal Controller e passerà ad esso il Listener dei componenti.

    Sono stati inoltre introdotti due metodi per gestire i messaggi in arrivo dal Controller:
    CODICE
    // Questi metodi sono richiamati dal Controller
     void gameDraw() {
       JOptionPane.showMessageDialog(frame, "Pareggio");
     }
     
     void gameEnding(char p) {
       JOptionPane.showMessageDialog(frame,"<html>Ha vinto il player <span style='color:red'>"+p+"</span></html>");
     }


    Analisi di TrisController.java



    L'aspetto più interessante riguarda questa classe.

    CODICE
    class TrisController implements MouseListener {
     // ----------------------------------------------------------
     private TrisModel tm; // Model (dati utilizzati)
     private TrisView tv;  // View (grafica dell'applicazione)
     // ----------------------------------------------------------
     
     // ----------------------------------------------------------
     TrisController(TrisModel tm, TrisView tv) {
       this.tm = tm;
       this.tv = tv;
       
       tv.makeWindow();
       tv.makeComponents(this);
     }
     // ----------------------------------------------------------


    Nel nostro caso il model non ha bisogno di particolari inizializzazioni, il costruttore è sufficiente. Altrimenti avremmo chiamato magari un metodo di TrisModel prima di invocare l'inizializzazione della GUI che crea finestra e componenti. Ricordo che un listeners viene attaccato ad una sorgente di evento (bottoni, finestre etc) usando gli opportuni metodi addXxx; se come in questo caso la gestione dell'evento avviene all'sterno della classe della grafica, sarà sufficiente passare ad essa un riferimento. Dall'altra parte - se guardate il codice sopra - noterete infatti che il metodo makeComponent() ha come parametro un MouseListener (e TrisController implementa proprio quel listener).

    L'altro aspetto che voglio far notare è come all'interno del Controller questa volta siano presenti entrambi gli oggetti. In questo modo possiamo dedurre che l'avvio dell'applicazione avvenga proprio chiamando TrisController, ma creando prima i due oggetti principali relativi al Model ed al View.

    L'unico metodo di TrisController è la gestione dell'evento:

    CODICE
    // ----------------------------------------------------------
     public void mouseReleased(MouseEvent me) {
       GridCell cell = (GridCell) me.getSource();
       
       // Ottengo il player che ha clickato ed aggiorno il model
       char p = tm.getPlayer();
       int s  = tm.move(cell.getXCoords(), cell.getYCoords());
       
       // Aggiorno il view
       cell.setItem(p);
       
       // Scelgo l'azione da intraprendere
       switch(s) {
         case TrisModel.WIN1: // Vittoria di uno dei giocatori
         case TrisModel.WIN2:
           tv.gameEnding(p);
           break;
         case TrisModel.DRAW: // Pareggio
           tv.gameDraw();
           break;
       }
       
       cell.removeMouseListener(this);
     }


    Come si può notare non differisce molto dall'implementazione precedente che aveva nel View.


    Analisi di TrisMain.java



    L'altra novità è TrisMain, che crea gli oggetti iniziali del Model e del View e che li passa al Controller, istanziandolo:
    CODICE
    TrisModel tm = new TrisModel();
       TrisView tv = new TrisView();
       
       TrisController tc = new TrisController(tm,tv);




    Esempio MVC



    Di seguito il codice delle classi:

    TrisModel.java
    CODICE
    /*
    *
    * Model
    *
    */



    class TrisModel {
     // ----------------------------------------------------------------------
     public static final int DRAW         = 0;   //Pareggio
     public static final int WIN1         = 1;   // Vittoria Player X
     public static final int WIN2         = 2;   // Vittoria Player O
     public static final int PLAY         = 3;   // La partita continua
     public static final int ILLEGAL_MOVE = -1;  // Mossa non valida
     
     
     private final int  W = 3;       // Dimensione di un lato della griglia
     private final char X = 'X';
     private final char O = 'O';
     
     private char next;             // Prossimo giocatore
     
     private char[] field;          // Campo di gioco
     // ----------------------------------------------------------------------
     
     
     // ----------------------------------------------------------------------
     TrisModel() {
       field = new char[W*W];
       for(int i=0; i<W*W; i++) field[i] = ' ';
       next  = X;
     }
     // ----------------------------------------------------------------------
     
     /*
      * Mossa di un giocatore
      */
     // ---------------------------------------------------------------
     int move(int x, int y) {
       if(checkIfValid(x,y)) {
         field[x*W+y] = next;
         
         /*
          * 0: pareggio
          * 1: vittoria Player 1
          * 2: vittoria Player 2
          */
         if(isTerminalState()) {
           return (next == X) ? WIN1 : WIN2;
         }
         
         if(noWhiteSpace()) return DRAW;
           
         next = (next == X) ? O : X;
         
         return PLAY; // La partita continua
       }
       
       return ILLEGAL_MOVE; // Mossa non possibile (posizione non valida)
     }
     // ----------------------------------------------------------------
     
       
     char getPlayer() {
       return next;
     }
     
     /* METODI PRIVATI */
     // ----------------------------------------------------------------
     
     // Verifica se la posizione (x,y) &#232; vuota
     private boolean checkIfValid(int x, int y) {
       return field[x*W+y] == ' ';
     }
     
     
      // Verifica se lo stato attuale e' terminale (vittoria/sconfitta)
     private boolean isTerminalState() {
       if(field[0] == next && field[1] == next && field[2] == next ||  /* orizzontale */
          field[3] == next && field[4] == next && field[5] == next ||
          field[6] == next && field[7] == next && field[8] == next ||
         
          field[0] == next && field[3] == next && field[6] == next ||  /* verticale */
          field[1] == next && field[4] == next && field[7] == next ||
          field[2] == next && field[5] == next && field[8] == next ||
         
          field[0] == next && field[4] == next && field[8] == next ||  /* obliquo */
          field[2] == next && field[4] == next && field[6] == next
         ) return true;
         
       return false; // Stato non terminale, partita non vinta
     }
     
     // Verifica la presenza di spazi vuoti
     private boolean noWhiteSpace() {
       int i = 0;
       for(; i<W*W && field[i] != ' '; i++);
       return i==W*W;
     }
    }


    TrisView.java
    CODICE
    /*
    *
    * View
    *
    */

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.util.ArrayList;


    class TrisView {
     // --------------------------------------------------------------------------
     private JFrame frame;    // Finestra dell'applicazione
     private JPanel panel;    // Pannello contenitore dei componenti
     // --------------------------------------------------------------------------
     
     TrisView() {}
     
     /* METODI */
     
     // --------------------------------------------------------------------------
     
     // Creo la finestra
     void makeWindow() {
       try {
         SwingUtilities.invokeAndWait(new Runnable() {
           public void run() {
             frame = new JFrame("Tris Human vs. Human");
             frame.setSize(520,520);
             frame.setResizable(false);
             frame.setVisible(true);
             
             frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           }
         });
       } catch(Exception e) {}
     }
     
     // Creo i componenti ed aggiungo l'ascoltatore
     void makeComponents(MouseListener ml) {
       panel = new JPanel(new GridLayout(3,3));
       
       for(int i=0; i<3; i++) {
         for(int j=0; j<3; j++) {
           GridCell label = new GridCell(i,j);
           label.addMouseListener(ml);
         
           panel.add(label);
         }
       }
       
       frame.add(panel);
     }
     
     // Questi metodi sono richiamati dal Controller
     void gameDraw() {
       JOptionPane.showMessageDialog(frame, "Pareggio");
     }
     
     void gameEnding(char p) {
       JOptionPane.showMessageDialog(frame,"<html>Ha vinto il player <span style='color:red'>"+p+"</span></html>");
     }
    }


    TrisController.java
    CODICE
    /*
    * Controller
    *
    */
    import java.awt.event.*;


    class TrisController implements MouseListener {
     // ----------------------------------------------------------
     private TrisModel tm; // Model (dati utilizzati)
     private TrisView tv;  // View (grafica dell'applicazione)
     // ----------------------------------------------------------
     
     // ----------------------------------------------------------
     TrisController(TrisModel tm, TrisView tv) {
       this.tm = tm;
       this.tv = tv;
       
       tv.makeWindow();
       tv.makeComponents(this);
     }
     // ----------------------------------------------------------
     
     
     
             /* * * * * * * * * * * *
              * METODI DELLA CLASSE *
              * * * * * * * * * * * */
             
     // ----------------------------------------------------------
     public void mouseReleased(MouseEvent me) {
       GridCell cell = (GridCell) me.getSource();
       
       // Ottengo il player che ha clickato ed aggiorno il model
       char p = tm.getPlayer();
       int s  = tm.move(cell.getXCoords(), cell.getYCoords());
       
       // Aggiorno il view
       cell.setItem(p);
       
       // Scelgo l'azione da intraprendere
       switch(s) {
         case TrisModel.WIN1: // Vittoria di uno dei giocatori
         case TrisModel.WIN2:
           tv.gameEnding(p);
           break;
         case TrisModel.DRAW: // Pareggio
           tv.gameDraw();
           break;
       }
       
       cell.removeMouseListener(this);
     }
     
     
     // Non utilizzati
     public void mouseClicked(MouseEvent me) {}
     public void mousePressed(MouseEvent me) {}
     public void mouseEntered(MouseEvent me) {}
     public void mouseExited(MouseEvent me) {}
     // ----------------------------------------------------------

    }


    GridCell.java
    CODICE
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.awt.geom.*;
    import javax.swing.border.Border;

    class GridCell extends JLabel {
     // -------------------------------------------------------------------
     // Bordo dei GridCell
     private final Border border = BorderFactory.createLineBorder(Color.BLACK);
     private char item;  // Player che ha clickato
     private int x, y;   // Coordinate del GridCell
     // -------------------------------------------------------------------
     
     
     // ------------------------------------------------------------------
     GridCell(int x, int y) {
       this.x = x;
       this.y = y;
       
       item = ' ';
       setOpaque(true);
       setBackground(Color.WHITE);
       
     }
     // ------------------------------------------------------------------
     
     
     /* METODI */
     
     int getXCoords() {
       return x;
     }
     int getYCoords() {
       return y;
     }

     void setItem(char item) {
       this.item = item;
       repaint(); // Chiama paintComponent
     }
     
     // Disegno sul pannello
     public void paintComponent(Graphics g) {
       super.paintComponent(g);
       
       Graphics2D g2 = (Graphics2D) g;
       setBorder(border);
       
       if(item == 'X') {
         g2.setColor(Color.RED);
         g2.draw(new Line2D.Double(10, 160, 160, 10));
         g2.draw(new Line2D.Double(10,10, 160,160));
       } else if(item == 'O') {
         g2.setColor(Color.BLUE);
         g2.draw(new Ellipse2D.Double(10, 10,158,158));
       }
     }
    }


    TrisMain.java
    CODICE
    // Main dell'applicazione

    class TrisMain {
     public static void main(String[] args) {
       TrisModel tm = new TrisModel();
       TrisView tv = new TrisView();
       
       TrisController tc = new TrisController(tm,tv);
     
     }
    }





    Terzo Approccio: MVC con un proprio evento personalizzato



    Il codice di questo esempio è più complesso rispetto ai precedenti. Java fornisce classi ed interfacce per gestire un tipo di situazione particolare: osservare un oggetto. E' il caso di Observer ed Observable, ad esempio. Dove un oggetto si registra come ascoltatore, ed ogni cambiamento ad uno dei dati del caso verrà notificato a chi ascolta.
    In questo esempio tuttavia faremo direttamente un passo oltre. L'esempio di Observer ed Observable è comodo anche nella realtà, ma ci si può trovare di fronte ad una spiacevole situazione. Observable è una classe e deve essere estesa da quell'oggetto che verrà modificato (nel caso di specie, è il Model). Tuttavia l'inconveniente è che se la nostra classe estende già un'altra classe... questa situazione non si può gestire (Java non supporta l'ereditarietà multipla).

    L'esempio citato, di Observer/Observable, è noto a chi conosce i design patterns. Infatti l'osservatore è proprio uno dei più importanti design pattern. Il caso che mostrerò rientra quindi tra questi più importanti essendo una soluzione alternativa all'implementare Observer e Observable.

    Le classi che utilizza questo esempio saranno:

    • TrisListener (interface)

    • Coords

    • TrisEvent

    • TrisModel

    • TrisController

    • TrisView

    • GridCell

    • TrisMain


    Come ho indicato nell'elenco, una di esse non è una classe, ma un'interfaccia.

    Ciò che fa tutto questo codice lo si può riassumere così: crea un evento (TrisEvent) estendendo quelli già esistenti, crea un'interfaccia (TrisListener) estendendo quelle già esistenti, e di fatto crea un nuovo ascoltatore personalizzato.


    Analisi di TrisListener.java



    Questa interfaccia verrà implementata dalla classe TrisController, e fornirà solamente il metodo che essa dovrà implementare per gestire l'evento.
    CODICE
    import java.util.EventListener;

    interface TrisListener extends EventListener {
     public void modelChanged(TrisEvent te);
    }


    Analisi di Coords.java


    Niente più che una classe che incapsula le coordinate di un punto (x,y) e che fornisce i relativi metodi getter/setter. Non merita di essere mostrata ora.

    Analisi di TrisEvent.java


    Questa classe è invece molto interessante. Incapsula tutte le informazioni generate quando si verifica un evento (generato dal Model, come vedremo tra poco). Al suo interno incapsula tutti i dati utili ad un cambio di stato:
    CODICE
    class TrisEvent extends EventObject {
     private int state;
     private char player;
     private Coords coords;
     
     TrisEvent(Object source, int state, char player, Coords coords) {
       super(source);
       
       this.state  = state;
       this.player = player;
       this.coords = coords;
     }


    Tutti gli eventi devono derivare da EventObject, è la superclasse di tutti gli eventi ed in effetti è seconda solo ad Object. Richiede solo "source", che è un Object, e che viene infatti passato alla superclasse invocando super().

    Analisi di TrisModel.java


    Questa volta la classe ha subito importanti modifiche; tra le variabili compare infatti private TrisListener trisListener;, che è un riferimento all'interfaccia che vedremo più avanti.
    Anche il metodo move() è cambiato:

    CODICE
    // ---------------------------------------------------------------
     void move(int x, int y) {
       if(checkIfValid(x,y)) {
         field[x*W+y] = next;
         old = next;
         /*
          * 0: pareggio
          * 1: vittoria Player 1
          * 2: vittoria Player 2
          */
         if(isTerminalState()) {
           stato = (next == X) ? WIN1 : WIN2;
         }
         else if(noWhiteSpace()) {
           stato = DRAW;
         }
         else {
           next = (next == X) ? O : X;
           stato = PLAY; // La partita continua
         }
       }
       else {    
         stato = ILLEGAL_MOVE; // Mossa non possibile (posizione non valida)
       }
       
       coords = new Coords(x,y);
       fireModelChange();
     }


    L'altro metodo invocato è mostrato di seguito:
    CODICE
    // Notifico l'evento
     protected void fireModelChange() {
       TrisEvent te = new TrisEvent(this, stato, old, coords);
       trisListener.modelChanged(te);
     }


    In pratica accade questo: quando viene chiamato il metodo move() il codice setta la nuova mossa all'interno del campo di gioco; al termine della sua esecuzione, prima di terminare, notifica l'evento chiamando fireModelChange(). La scelta del nome è evocativa: in Java ci sono altri metodi con nomi simili che svolgono funzioni analoghe, si veda ad esempio PropertyChangeSupport.

    Analisi di TrisController.java


    Anche il Controller ora ha un nuovo aspetto:

    CODICE
    class TrisController implements MouseListener, TrisListener {
     // ----------------------------------------------------------
     private TrisModel tm; // Model (dati utilizzati)
     private TrisView tv;  // View (grafica dell'applicazione)
     // ----------------------------------------------------------
     
     // ----------------------------------------------------------
     TrisController(TrisModel tm, TrisView tv) {
       this.tm = tm;
       this.tv = tv;
       
       tm.addTrisListener(this);
       
       tv.makeWindow();
       tv.makeComponents(this);
     }
     // ----------------------------------------------------------


    Questa parte è più o meno invariata, se non nell'implementazione dell'interfaccia. Sta praticamente dicendo che anche l'evento del model (TrisListener) è gestito qui, in questa classe (ecco perchè passa this al Model).

    Proseguendo nel codice della classe si troverà l'implementazione del metodo fornito dall'interfaccia:
    CODICE
    public void modelChanged(TrisEvent te) {
       int state = te.getState();
       char player = te.getPlayer();
       Coords coords = te.getCoords();
       
       GridCell gc = tv.getCell(coords);
       gc.setItem(player);
       
       if(state == TrisModel.WIN1 || state == TrisModel.WIN2) {
         tv.gameEnding(this,player);
       }
       else if(state == TrisModel.DRAW) {
         tv.gameDraw(this);
       }
     }


    Il Controller, ricevuto l'evento, estrae quelli che sono i dati utili alla sua elaborazione. I dati possono essere in generale N, non vi è una quantità di dati (infatti l'oggetto è totalmente personalizzato).
    Presi i dati di suo interesse, notifica alla GUI l'azione da intraprendere e che rifletta lo stato interno (Il Model).

    Quando avviene il click sulla GUI, l'evento viene gestito dal metodo di MouseListener, implementata appunto dal Controller:

    CODICE
    public void mouseReleased(MouseEvent me) {
       GridCell cell = (GridCell) me.getSource();
       tm.move(cell.getXCoords(), cell.getYCoords());
     }


    Il metodo non fa altro che prendere l'oggetto che ha generato l'evento ed estrarne le coordinate; una volta ottenute, comunica al model quale elemento è stato clickato.
    Questo come abbiamo già visto provocherà la modifica del Model e di conseguenza poi la notifica alla GUI tramite il metodo mostrato sopra.

    Analisi di TrisView.java


    Il View vede la comparsa di un array di GridCell, private GridCell[] cells;. Questo array ci permette di avere tutti gli elementi aggiunti alla GUI così da poter recuperare la GridCell anche dall'evento del model (passando un oggetto Coords sappiamo quale elemento deve essere aggiornato, come evidenziato da modelChanged()).
    Oltre a questo mette a disposizione una serie di metodi utilizzabili dall'esterno, come quello dedicato alla vittoria di un giocatore o al pareggio.





    Esempio: MVC con un proprio evento personalizzato



    TrisModel.java
    CODICE
    /*
    *
    * Model
    *
    */


    class TrisModel {
     // ----------------------------------------------------------------------
     public static final int DRAW         = 0;   //Pareggio
     public static final int WIN1         = 1;   // Vittoria Player X
     public static final int WIN2         = 2;   // Vittoria Player O
     public static final int PLAY         = 3;   // La partita continua
     public static final int ILLEGAL_MOVE = -1;  // Mossa non valida
     
     
     private final int  W = 3;       // Dimensione di un lato della griglia
     private final char X = 'X';
     private final char O = 'O';
     
     private char next, old;             // Prossimo giocatore e giocatore precedente
     private int stato;
     
     private Coords coords;
     
     private char[] field;          // Campo di gioco
     
     private TrisListener trisListener;
     // ----------------------------------------------------------------------
     
     
     // ----------------------------------------------------------------------
     TrisModel() {
       field = new char[W*W];
       for(int i=0; i<W*W; i++) field[i] = ' ';
       next  = X;
     }
     // ----------------------------------------------------------------------
     
     /*
      * Mossa di un giocatore
      */
     // ---------------------------------------------------------------
     void move(int x, int y) {
       if(checkIfValid(x,y)) {
         field[x*W+y] = next;
         old = next;
         /*
          * 0: pareggio
          * 1: vittoria Player 1
          * 2: vittoria Player 2
          */
         if(isTerminalState()) {
           stato = (next == X) ? WIN1 : WIN2;
         }
         else if(noWhiteSpace()) {
           stato = DRAW;
         }
         else {
           next = (next == X) ? O : X;
           stato = PLAY; // La partita continua
         }
       }
       else {    
         stato = ILLEGAL_MOVE; // Mossa non possibile (posizione non valida)
       }
       
       coords = new Coords(x,y);
       fireModelChange();
     }
     // ----------------------------------------------------------------
     
     // Notifico l'evento
     protected void fireModelChange() {
       TrisEvent te = new TrisEvent(this, stato, old, coords);
       trisListener.modelChanged(te);
     }
     
     
     public void addTrisListener(TrisListener tl) {
       trisListener = tl;
     }
     
     public void removeTrisListener() {
       trisListener = null;
     }
     
       
     char getPlayer() {
       return next;
     }
     
     /* METODI PRIVATI */
     // ----------------------------------------------------------------
     
     // Verifica se la posizione (x,y) &#232; vuota
     private boolean checkIfValid(int x, int y) {
       return field[x*W+y] == ' ';
     }
     
     
      // Verifica se lo stato attuale e' terminale (vittoria/sconfitta)
     private boolean isTerminalState() {
       if(field[0] == next && field[1] == next && field[2] == next ||  /* orizzontale */
          field[3] == next && field[4] == next && field[5] == next ||
          field[6] == next && field[7] == next && field[8] == next ||
         
          field[0] == next && field[3] == next && field[6] == next ||  /* verticale */
          field[1] == next && field[4] == next && field[7] == next ||
          field[2] == next && field[5] == next && field[8] == next ||
         
          field[0] == next && field[4] == next && field[8] == next ||  /* obliquo */
          field[2] == next && field[4] == next && field[6] == next
         ) return true;
         
       return false; // Stato non terminale, partita non vinta
     }
     
     // Verifica la presenza di spazi vuoti
     private boolean noWhiteSpace() {
       int i = 0;
       for(; i<W*W && field[i] != ' '; i++);
       return i==W*W;
     }
    }


    TrisController.java
    CODICE
    /*
    * Controller
    *
    */
    import java.awt.event.*;


    class TrisController implements MouseListener, TrisListener {
     // ----------------------------------------------------------
     private TrisModel tm; // Model (dati utilizzati)
     private TrisView tv;  // View (grafica dell'applicazione)
     // ----------------------------------------------------------
     
     // ----------------------------------------------------------
     TrisController(TrisModel tm, TrisView tv) {
       this.tm = tm;
       this.tv = tv;
       
       tm.addTrisListener(this);
       
       tv.makeWindow();
       tv.makeComponents(this);
     }
     // ----------------------------------------------------------
     
     
     
             /* * * * * * * * * * * *
              * METODI DELLA CLASSE *
              * * * * * * * * * * * */
             
     // ----------------------------------------------------------
     
     public void modelChanged(TrisEvent te) {
       int state = te.getState();
       char player = te.getPlayer();
       Coords coords = te.getCoords();
       
       GridCell gc = tv.getCell(coords);
       gc.setItem(player);
       
       if(state == TrisModel.WIN1 || state == TrisModel.WIN2) {
         tv.gameEnding(this,player);
       }
       else if(state == TrisModel.DRAW) {
         tv.gameDraw(this);
       }

     }
     
     
     
     public void mouseReleased(MouseEvent me) {
       GridCell cell = (GridCell) me.getSource();
       tm.move(cell.getXCoords(), cell.getYCoords());
     }
     
     
     // Non utilizzati
     public void mouseClicked(MouseEvent me) {}
     public void mousePressed(MouseEvent me) {}
     public void mouseEntered(MouseEvent me) {}
     public void mouseExited(MouseEvent me) {}
     // ----------------------------------------------------------

    }


    TrisView.java
    CODICE
    /*
    *
    * View
    *
    */

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;


    class TrisView {
     // --------------------------------------------------------------------------
     private JFrame frame;    // Finestra dell'applicazione
     private JPanel panel;    // Pannello contenitore dei componenti
     
     private GridCell[] cells;
     // --------------------------------------------------------------------------
     
     TrisView() {
       cells = new GridCell[9];
     }
     
     /* METODI */
     
     // --------------------------------------------------------------------------
     
     // Creo la finestra
     void makeWindow() {
       try {
         SwingUtilities.invokeAndWait(new Runnable() {
           public void run() {
             frame = new JFrame("Tris Human vs. Human");
             frame.setSize(520,520);
             frame.setResizable(false);
             frame.setVisible(true);
             
             frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           }
         });
       } catch(Exception e) {}
     }

     
     // Creo i componenti ed aggiungo l'ascoltatore
     void makeComponents(MouseListener ml) {
       panel = new JPanel(new GridLayout(3,3));
       
       for(int i=0; i<3; i++) {
         for(int j=0; j<3; j++) {
           GridCell label = new GridCell(i,j);
           label.addMouseListener(ml);
           panel.add(label);
           cells[i*3+j] = label;
         }
       }
       
       frame.add(panel);
     }
     
     // Questi metodi sono richiamati dal Controller
     void gameDraw(MouseListener ml) {
       JOptionPane.showMessageDialog(frame, "Pareggio");
       removeListener(ml);
     }
     
     void gameEnding(MouseListener ml, char p) {
       JOptionPane.showMessageDialog(frame,"<html>Ha vinto il player <span style='color:red'>"+p+"</span></html>");
       removeListener(ml);
     }
     
     GridCell getCell(Coords coords) {
       return cells[coords.getX()*3+coords.getY()];
     }
     
     private void removeListener(MouseListener ml) {
       for(int i=0; i<cells.length; i++) cells[i].removeMouseListener(ml);
     }
     
    }


    TrisEvent.java
    CODICE
    import java.util.EventObject;

    class TrisEvent extends EventObject {
     private int state;
     private char player;
     private Coords coords;
     
     TrisEvent(Object source, int state, char player, Coords coords) {
       super(source);
       
       this.state  = state;
       this.player = player;
       this.coords = coords;
     }
     
     public int getState() {
       return state;
     }
     
     public char getPlayer() {
       return player;
     }
     
     public Coords getCoords() {
       return coords;
     }
     
    }


    GridCell.java
    CODICE
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.awt.geom.*;
    import javax.swing.border.Border;

    class GridCell extends JLabel {
     // -------------------------------------------------------------------
     // Bordo dei GridCell
     private final Border border = BorderFactory.createLineBorder(Color.BLACK);
     private char item;  // Player che ha clickato
     private int x, y;   // Coordinate del GridCell
     // -------------------------------------------------------------------
     
     
     // ------------------------------------------------------------------
     GridCell(int x, int y) {
       this.x = x;
       this.y = y;
       
       item = ' ';
       setOpaque(true);
       setBackground(Color.WHITE);
       
     }
     // ------------------------------------------------------------------
     
     
     /* METODI */
     
     int getXCoords() {
       return x;
     }
     int getYCoords() {
       return y;
     }

     void setItem(char item) {
       this.item = item;
       repaint(); // Chiama paintComponent
     }
     
     // Disegno sul pannello
     public void paintComponent(Graphics g) {
       super.paintComponent(g);
       
       Graphics2D g2 = (Graphics2D) g;
       setBorder(border);
       
       if(item == 'X') {
         g2.setColor(Color.RED);
         g2.draw(new Line2D.Double(10, 160, 160, 10));
         g2.draw(new Line2D.Double(10,10, 160,160));
       } else if(item == 'O') {
         g2.setColor(Color.BLUE);
         g2.draw(new Ellipse2D.Double(10, 10,158,158));
       }
     }
    }



    Coords.java
    CODICE
    class Coords {
     private int x, y;
     
     Coords(int x, int y) {
       this.x = x;
       this.y = y;
     }
     
     int getX() {
       return x;
     }
     
     int getY() {
       return y;
     }
    }



    TrisListener.java
    CODICE
    import java.util.EventListener;

    interface TrisListener extends EventListener {
     public void modelChanged(TrisEvent te);
    }


    TrisMain.java
    CODICE
    // Main dell'applicazione

    class TrisMain {
     public static void main(String[] args) {
       TrisModel tm = new TrisModel();
       TrisView tv = new TrisView();
       
       TrisController tc = new TrisController(tm,tv);
     
     }
    }




    Conclusione



    Ebbene, l'articolo è giunto al termine. Spero vi sia stato utile. Qui sotto ho lasciato 2 screenshot e l'ultimo degli esempi compilato (l'unica differenza rispetto al sorgente qui sopra è un leggero miglioramente delle coordinate di disegno oltre che alla modifica al titolo della finestra). Per eseguirlo è necessario Java 8.

    Tra i 3 approcci mostrati in questo articolo io solitamente utilizzo il secondo. Il terzo - o qualcosa di analogo utilizzando Observer/Observable - risulta molto utile quando si deve aggiornare l'interfaccia rispecchiando il model (il caso di specie rientra in questa situazione, come anche i giochi in generale). Tutti e 3 gli approcci vengono utilizzati (e vi sono tanti altri approcci simili o anche differenti e basati su altri design patterns). Sicuramente la cosa da non fare è mischiare logica e grafica.


    Sotto spoiler troverete due i screenshot.


    Il Download del file Jar è disponibile al questo indirizzo su Mediafire: download
  4. .
    Riguardo allo Stack, dipende come lo stai rappresentando. Teoricamente non lo staresti rappresentando bene, in quanto lo stack cresce verso il basso e non verso l'alto (credo sia così in quasi tutte le architetture). Sulla base di questo credo cambi il modo in cui hai interpretato alcune parti del "codice" (in effetti è un autentico casino visto in questo modo).




    ATTENZIONE: la sintassi è mov destinazione, sorgente, quindi banalmente se vedi mov bp, sp, significa bp = sp.


    Perdonami, però mi esce più semplice spiegartelo con situazioni reali. Inizio con una breve spiegazione, così da chiarire i concetti fondamentali.
    In moltissime architetture lo stack cresce verso il basso. Ciò significa che come nel tuo caso ci sarà un registro che punta in cima, su un indirizzo più basso.

    stack1



    Nell'architettura x86 di Intel ci sono pochi registri da ricordare:
    SS rappresenta la base dello stack, o per meglio dire, il segmento dello stack (negli esempi non lo utilizzerò, è implicita la sua presenza)
    SP rappresenta il puntatore nello stack; per intenderci, è quello che nella figura si chiama "Top of stack" (corrisponde in pratica al tuo SP);
    BP questo è l'indirizzo base, viene utilizzato dal programmatore per accedere allo stack (al posto di manipolare SP);

    IP non ha nulla ache fare con lo stack, ma è un puntatore anch'esso (Instruction Pointer); come dice il nome, punta all'istruzione da eseguire (nel tuo caso è il program counter, PC)

    Queste sono le informazioni base.
    Nell'architettura di Intel, il programmatore può aggiungere o rimuovere dati dallo stack utilizzando le istruzioni PUSH e POP.

    Detto ciò, considera questa semplicissima situazione in linguaggio C:

    CODICE
    int   somma() {
     return 0;
    }

    int main() {
     int n =  somma();

     return 0;
    }


    L'aspetto importante, è cosa accade quando avviene la chiamata a somma()?

    In asm, la chiamata avviene nel seguente modo:

    CODICE
    call  funzione


    Quando avviene una chiamata come in questo caso, la CPU effettua il push del registro IP sullo stack, e come seconda cosa assegna al registro IP un nuovo valore: quello della funzione (il suo indirizzo).
    Fatto questo, l'esecuzione inizia da quel punto (e la subroutine viene eseguita).

    Supponiamo quindi che l'inizio dello stack (l'indirizzo più alto) sia 100 (decimale, è più comodo). Quindi SP = 100.
    Nel momento della CALL avviene il PUSH (implicito, nel senso che non è controllato dal programmatore) del valore di IP per non perdere l'istruzione successiva da eseguire.

    CODICE
    [SP] = IP  // 100


    ora avremo SP = 99. La PUSH fa di fatto questo, che è ciò che nel tuo codice continua a comparire: esegue una sottrazione in pratica, decrementando SP. Nella pratica rispecchierà la dimensione del dato (se l'architettura è a 32bit, il decremento avverrà di 32bit, o se preferisci 4byte).

    Sino a qui spero sia chiaro.
    Quando passi dei parametri, i valori devono essere inseriti sullo stack prima della chiamata alla funzione. In C la situazione ora è la seguente:

    CODICE
    int   somma(int a, int b) {
     return 0;
    }

    int main() {
     int a = 1, b = 1;
     int n =  somma(a, b);

     return 0;
    }


    In asm possiamo scrivere la parte del main() come:
    CODICE
    push   b
    push   a
    call      somma


    In questo caso ci sono 2 PUSH subito. Quindi se iniziamo da SP=100, dopo alla prima PUSH avremo SP=99 e dopo la seconda SP=98. La CALL provoca un altro decremento del valore, e quindi SP=97 (perchè viene inserito il registro IP).
    Questa è la parte facile.

    Ho parlato inizialmente del registro BP. Questo registro è quello che tecnicamente controlla lo stack frame. Quando chiami una procedura hai un frame; BP punta praticamente li, e viene utilizzato per accedere allo stack (in maniera sicura).

    Se hai letto l'altro topic, avrai notato che la procedura inizia con quello che viene definito prologo:

    CODICE
    push     bp
    mov      bp, sp


    Il push avviene in quanto (solitamente, come dice anche Intel lol) BP punta all'indirizzo di ritorno. Quindi quella PUSH preserva il valore. La situazione dello stack, eseguita quindi anche quella PUSH di BP è la seguente:

    CODICE
    100: b
    099: a
    098: [valore di IP]
    097: [valore di BP]

    SP => 0097


    Giunti a questo punto, il codice prosegue con mov bp, sp; ora possiamo accedere allo stack tramite BP. Visto che lo stack cresce verso il basso ed inizia da indirizzi più alti, per "andare sopra" e prendere i parametri "pushati", dobbiamo incrementare di un certo numero BP.

    Il certo numero in questo caso è [BP+2] per prelevare il valore di 'a' e [BP+3] per prelevare il valore di 'b'.

    NOTA:
    In una situazione reale su 32bit i valori non saranno esattametne questi, in quanto i dati sono di 4byte e non di 1 (come nell'esempio sopra). Quindi avremo:
    CODICE
    100: b
    096: a
    092: [valore di IP]
    088: [valore di BP]


    e di conseguenza, il primo parametro della funzione sarà a [BP+8] ed il secondo a [BP+12].
    Nel caso del topic che hai letto inizialmente, i dati sono a 16bit, quindi solo 2 byte... e di conseguenza, l'accesso sarà diverso rispetto al caso mostrato ora: avrai [BP+4] e [BP+6].

    Spero che sino a qui sia tutto chiaro ora - perdona le continue disgressioni, però preferisco essere preciso per non fornirti informazioni fuorvianti.

    Bene, a questo punto, c'è un altro aspetto che non sapevo se toccare... i parametri locali, o meglio, quelle che si chiamano semplicemente "variabili locali della funzione" (o con nomi analoghi).
    Ragiona sullo stack che vedi nel penultimo CODE: se lo stack cresce verso il basso, vuol dire per logica che la parte sopra è piena, e quella sotto è vuota... ed in effetti contiene i parametri della funzione; ergo non rimane che andare nella direzione opposta. Andare nella direzione opposta implica dover sottrarre qualcosa a BP.
    In questo caso la situazione si complica un pochino:

    CODICE
    push   bp
    mov    bp, sp
    sub     sp,1


    decrementando il valore di sp, riserviamo spazio per la variabile locale. L'accesso alla variabile locale avviene con [bp-1].

    Ricorda sempre che con dati reali, si tratta di 16/32bit e non 1byte come in questo caso (ergo, se salvi un dato a 32bit, dovrai sottrarre 4 ad SP).




    A questo punto, è il momento dell'epilogo:
    CODICE
    mov  sp, bp
    pop   bp
    ret


    Preciso una cosa molto importante: arrivato a questo punto, è importante che lo stack sia stato manipolato correttamente e che sia coerente: questo significa banalmente che se nella funzione per qualche motivo hai fatto N push, prima del termine della funzione, dovrai fare N pop. In caso contrario la funzione non terminerà correttamente ed il risultato è imprevedibile... al 99.9% si verificherà un crash in quanto il dato non è un indirizzo valido, oppure non puoi accedervi.

    Solitamente all'istruzione ret fa seguito il numero di byte da sottrarre per liberare lo stack. Una volta liberato tramite la ret, viene fatto il push dell'indirizzo IP inserito inizialmente... e l'esecuzione riprende in pratica dalla riga successiva alla chiamata.


    Spero perdonerai la mia spiegazione al posto di un commento passo-passo del tuo codice... e spero ti sia stata utile.
    In caso di domande chiedi pure!
  5. .
    Stacco ora da lavoro, verso sera leggo bene e ti rispondo. ;)
  6. .
    Nemmeno io so giocare, ma non serve saper giocare. E' sufficiente guardare quali sono le mosse di Re e Regine. il Re si muove nelle 8 posizioni attorno a lui, e la regina in orizzontale, verticale e diagonale.
  7. .
    Dev'essere il massimo questo prof. :asd: Sono identici anche i commenti li sotto, oltretutto.
  8. .
    Si bhe anche, specie se non hai tante configurazioni da gestire. Però boh, trovo più comodo un file... alla fine lo parsi solo quando crei l'oggetto la prima volta.
  9. .
    In PHP lo scope di una variabile è la richiesta HTTP.
    Solitamente sono una brutta pratica, in quanto è difficile da debuggare. Inoltre leggere un codice di quel tipo diventa complesso/difficile. Probabilmente raxell aggiungerà altro.
  10. .
    raxell domanda relativa alla lunghezza dell'username e dei dati in generale: ma non dovrebbero venir spezzati se troppo lunghi?
  11. .
    In ita non conosco nulla, oltre ai tutorial ufficiali su MSDN, tipo: https://msdn.microsoft.com/it-it/library/67ef8sbd.aspx
  12. .
    Si raxell, io ho erroneamente utilizzato un if-elseif-else, hai ragione. Tuttavia raxell dipende dal compilatore che stai utilizzando e cosa importante dal fatto che io sto utilizzando un assembler al momento.

    L'ho scritto appositamente utilizzando la "sintassi al alto livello" di MASM32, ovvero:
    CODICE
    mov      al, a
     xor      ebx, ebx
     
     .IF   al < b
       mov      bl, b
       sub      bl, al
     .ENDIF
     
     .IF   al > b
       sub      al, b
       mov      bl, al
     .ENDIF
     
     .IF al == b
       mov    bl, 0
     .ENDIF



    Il disassemblato è:
    da3m0ns questo è in pratica ciò che dovrai scrivere. Al posto dello XOR puoi usare "mov bx, 0".
    La locazione 403001 è la variabile b. Il resto dovresti riuscire a tradurlo: al posto delle etichette testuali ci sono degli indirizzi.

    CODICE
    CPU Disasm
    Address   Hex dump          Command                                  Comments
    00401025  |$  A0 00304000   MOV AL,BYTE PTR DS:[403000]
    0040102A  |.  33DB          XOR EBX,EBX
    0040102C  |.  3A05 01304000 CMP AL,BYTE PTR DS:[403001]
    00401032  |.  73 08         JNB SHORT 0040103C
    00401034  |.  8A1D 01304000 MOV BL,BYTE PTR DS:[403001]
    0040103A  |.  2AD8          SUB BL,AL
    0040103C  |>  3A05 01304000 CMP AL,BYTE PTR DS:[403001]
    00401042  |.  76 08         JBE SHORT 0040104C
    00401044  |.  2A05 01304000 SUB AL,BYTE PTR DS:[403001]
    0040104A  |.  8AD8          MOV BL,AL
    0040104C  |>  3A05 01304000 CMP AL,BYTE PTR DS:[403001]
    00401052  |.  75 02         JNE SHORT 00401056
    00401054  |.  B3 00         MOV BL,0


    E' in pratica lo stesso codice di prima, con una sola differenza: io ho utilizzato un if-elseif-else, quindi inseriva dei JMP dopo il corpo di ogni IF.


    Nel caso di C/C++ concordo con quanto dici, il codice è praticamente quello da te mostrato (questo è il compilatore di Microsoft).

    CODICE
    CPU Disasm
    Address   Hex dump          Command                                  Comments
    00C81274    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]
    00C81277    3B45 F8         CMP EAX,DWORD PTR SS:[EBP-8]
    00C8127A    7D 09           JGE SHORT 00C81285
    00C8127C    8B4D F8         MOV ECX,DWORD PTR SS:[EBP-8]
    00C8127F    2B4D FC         SUB ECX,DWORD PTR SS:[EBP-4]
    00C81282    894D F4         MOV DWORD PTR SS:[EBP-0C],ECX
    00C81285    8B55 FC         MOV EDX,DWORD PTR SS:[EBP-4]
    00C81288    3B55 F8         CMP EDX,DWORD PTR SS:[EBP-8]
    00C8128B    7E 09           JLE SHORT 00C81296
    00C8128D    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]
    00C81290    2B45 F8         SUB EAX,DWORD PTR SS:[EBP-8]
    00C81293    8945 F4         MOV DWORD PTR SS:[EBP-0C],EAX
    00C81296    8B4D FC         MOV ECX,DWORD PTR SS:[EBP-4]
    00C81299    3B4D F8         CMP ECX,DWORD PTR SS:[EBP-8]
    00C8129C    75 07           JNE SHORT 00C812A5
    00C8129E    C745 F4 0000000 MOV DWORD PTR SS:[EBP-0C],0
  13. .
    da3m0ns

    Ho acceso il PC solo per risponderti, sentiti onorato. :asd:


    Non ho compilatori a 16bit qui sul PC, ma le considerazioni espresse valgono per tutte le architetture Intel a partire dalla 8086.

    In primis, una cosa da avere in mano è 231455.pdf: www.mediafire.com/download/u4aa3ry00j1545r/231455.zip
    Se vai alla pagina 26 vedrai le istruzioni e gli operandi che puoi utilizzare. Come noti non esiste un "mem to mem" con le MOV. E' così ovviamente anche a 32bit e - tralasciando tecniche complesse - quando si devono spostare dati tra due locazioni di memoria si seguono due strade:
    - utilizzare un registro come appoggio:

    mov ax, [variabile1]
    mov [variabile2], ax


    - utilizzare le istruzioni PUSH/POP:
    push variabile1
    pop variabile2


    La seconda strade è preferibile per diversi motivi: il primo, è che la CPU esegue più rapidamente operazioni con i registri (e le PUSH/POP sono comunque molto rapide); la seconda è che gli OPcodes di queste istruzioni sono i più brevi che vedrai (1byte ciascuno infatti).
    Sotto alla MOV nel PDF vedrai infatti la PUSH e poi la POP.

    Le lettura esatta dei campi al momento evitala... te l'ho passato solo per farti vedere quali sono le operazioni possibili. Se ne sei interessato puoi guardare nelle pagine precedenti (avrai inoltre tante altre info sulla CPU; starei anche preparando un articolo su questo argomento ed argomenti ad esso correlati).

    Sorvolando tutto ciò, non vi è ragione per utilizzare una variabile (memoria) al posto di un registro. Un registro è molto più rapido essendo "nel processore".

    Il registro AX, non so se vi è stato spiegato, ma si chiama Accumulator (la A non è casuale, come la C di CX, essendo il "contatore"). Se noti, guardando sempre sul PDF, certe istruzioni specificano operazioni come "[SUB] Immediate from Accumulator", o nel caso della MOV "Accumulator to memory".
    Questo significa che il registro AX è pensato per essere utilizzato sia per assegnamenti (vedi appunto la MOV sopra citata) sia per i calcoli (vedi la SUB, ma il discorso è analogo per una ADD). E' inoltre il registro di default per quanto riguarda le divisioni (è assunto in maniera automatica, infatti alla DIV passi solo un operando, l'altro è AX).




    NOTA: guardando il pdf noterai ad esempio di non poter eseguire una CMP con due operandi mem.

    Puoi fare come spiegato da raxell.
    Comunque puoi scriverlo in questo modo anche:

    CODICE
    mov      al, a
     mov      cl, b
     
     cmp      al, cl
     jg       _greater
     jl       _less
     je       _equal

    _greater:
     sub      al, cl
     mov      cl, al
     jmp      _exit
    _less:
     sub      cl, al
     jmp      _exit
    _equal:
     mov      cl, 0
     
    _exit:


    Così facendo ad esempio evito l'utilizzo di un terzo registro (si bhe, lo si potrbbe tranquillamente utilizzare).

    La "migliore" potrebbe essere questa:

    CODICE
    mov      al, a
     mov      cl, b
     
     ; if al < cl
     cmp      al, cl
     jnb      _greater
     sub      cl, al
     jmp      _exit
     
     ; if al > cl
    _greater:
     cmp      al, cl
     jbe      _equals
     sub      al, cl
     mov      cl, al
     jmp      _exit

    _equals:
     mov      cl, 0
     
    _exit:


    Questo tipo di soluzione è quella di norma adottata da un compilatore (anche da un assembler che deve tradursi un codice assembly scritto magari non benissimo o comunque con sintassi più semplificate).

    La JNB significa "Jump if not below", la JBE "Jump if below or equal".
  14. .
    L'unico errore che vedo sta nel rivalutare nuovamente il registro FLAGS, utilizzando jns. La forma corretta per un IF è la seguente:

    CODICE
    cmp   a, b
    js      _minore
    dec   a
    jmp   _exit
    _minore:
    inc   a
    _exit:


    l'ho rapportato al tuo caso, ma la solfa è sempre quella: al termine del blocco IF metti un JMP che salta ad un etichetta. Tra l'etichetta precedente e l'ultima si trova l'ELSE.

    La cosa che cambierei è invece il modo in cui incrementi. Dovresti incrementare i registri e non la memoria (è molto più rapido, specie se utilizzi il registro AX).
  15. .
    CITAZIONE (Gregory_Powell @ 1/12/2015, 21:08) 
    In realtà ho skippato, e direi erroneamente, un po' di teoria smanioso di mettere mano sui codici! Quindi forse un ripassino su Classi, metodi ecc me lo devo fare!

    Il mettere "cose" fuori dal Main mi ha spiazzato perchè non l'avevo mai visto in un codice!

    E si non ho mai programmato prima,anzi l'interesse per l'informatica è nato da poco e inizialmente mi dedicavo pure a cose inutili rispetto a ciò che mi piace fare davvero (come voler diventare social media manager, per esempio!)

    Leggerò qualcosa dal libro e qualcosa di tuo e tornerò ad assillarti!
    Grazie mille!

    Sono concetti davvero fondamentali. I metodi - in altri linguaggi chiamati funzioni - esistono in tutti i linguaggi di programmazione. In Java poi il concetto di classe è fondamentale, come in tutti i linguaggi OOP. Quindi dedica pure il tempo necessario ai concetti iniziali, in quanto saranno fondamentali anche nella progettazione di un'applicazione. Senza poi contare che tra breve - deduco - il libro userà metodi, classi ed altri concetti che dovrai affrontare prima teoricamente, dal punto di vista concettuale. ;)

    Per qualsiasi domanda dovrai solo aprire un topic.
170 replies since 5/1/2009
.
Top