Mòdul 8

Pràctica 5: El motor del joc   
Tornar presentació tema
Pràctica 2 Pràctica 3 Pràctica 4 Pràctica 5 Pràctica 1 Pràctica 6 Pràctica 6  
<
     
     
  La Vida al Món  
     
  Finalment, aquesta és la pràctica en la qual, al final, el Joc ja es juga... Les classes i mètodes ja quedaran complets i, almenys pas a pas, ja podràs veure l'evolució dels Essers en el mon del Joc de la Vida.  
     
  Posar i treure éssers del món:    
     
  A la planificació de la pràctica 1 està previst que l'usuari de l'aplicació del Joc de la Vida pugui posar i treure éssers del món on vulgui i quan vulgui amb un simple clic de ratolí. Això implica que el MonVisible ha de ser sensible als clics del ratolí i que, per tant, cal afegir-li un MouseListener que els capti. De les dues possibilitats, fer que la classe MonVisible sigui ella mateixa el listener dels esdeveniments MouseEvent, o bé, crear un objecte java.awt.event.MouseAdapter per a aquesta finalitat (recorda el que se'n diu d'això a la pràctica 7 del mòdul 6) escolliràs la segona.  
     
Som-hi: al mètode constructor de la classe MonVisible afegeix-li això (no t'oblidis d'importar les classes java.awt.event.MouseAdapter i java.awt.event.MouseEvent!):  
     
    /**
     * Mètode constructor per a objectes de la classe MonVisible.
     * @param finestra La Finestra que conté aquest MonVisible
     */
    public MonVisible(Finestra finestra) { // constructor
        this.finestra=finestra;
        setPreferredSize(finestra.getDimensionsMonVisible());

        MouseAdapter mouseAdapter=new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                // Feina per fer: definir què passa quan hi ha
                // un click.

            }
        };
        addMouseListener(mouseAdapter);
}
 
     
  Ara el MonVisible ja és sensible als clics del ratolí i ja només cal establir què passa quan hi ha un click sobre ell, és a dir, només cal posar el contingut del mètode public void mouseClicked(MouseEvent e) de l'objecte mouseAdapter que s'acaba de crear.  
     
  Com que el mètode public void mouseClicked(MouseEvent e) de l'objecte mouseAdapter ha estat definit just en el moment de crear aquest objecte, en escriure el codi d'aquest mètode, les variables de la classe MonVisible no són directament manipulables i cal obtenir-les resseguint el camí pel qual vé el MouseEvent e:  
     
 
  1. Determinació de l'objecte MonVisible en el qual s'ha originat l'esdeveniment. El mètode a fer servir és el getComponent() de la classe java.awt.event.ComponentEvent (de la qual és filla la classe MouseEvent), que et tornarà l'objecte java.awt.Component (la classe MonVisible és filla de JPanel i, per tant, de java.awt.Component):

    MonVisible monVisible=(MonVisible)e.getComponent();

    El casting per passar el java.awt.Component que ens torna el mètode a MonVisible és imprescindible!

  2. En quina Finestra està aquest MonVisible?

    Finestra finestra=monVisible.finestra;

  3. Quin Mon mou les coses en aquest MonVisible?

    Mon mon=finestra.getMon();

  4. A quines coordenades s'ha esdevingut el MouseEvent e?

    int posX=e.getX();
    int posY=e.getY();

  5. A quina cel·la del mon corresponen aquestes coordenades?

    int ampladaEssers=Constants.dimensionsEsser.width;
    int alturaEssers=Constants.dimensionsEsser.height;
    int x=posX/ampladaEssers;
    int y=posY/alturaEssers;
 
  Ara ja està tot a punt: primer fes que canviï l'estat de l'Esser que és just en aquesta posició del mon:  
     
 
mon.canviaEstatViuOMort(x,y);
 
     
  i, després, repinta el MonVisible per tal que es vegi el canvi:  
     
 
monVisible.repaint();
 
     
  El codi que omple el mètode public void mouseClicked(MouseEvent e) de l'objecte mouseAdapter és, doncs:  
     
    /**
     * Mètode constructor per a objectes de la classe MonVisible.
     * @param finestra La Finestra que conté aquest MonVisible
     */
    public MonVisible(Finestra finestra) { // constructor
        this.finestra=finestra;
        setPreferredSize(finestra.getDimensionsMonVisible());
        MouseAdapter mouseAdapter=new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                
MonVisible monVisible=(MonVisible)e.getComponent();
                Finestra finestra=monVisible.finestra;
                Mon mon=finestra.getMon();
                int posX=e.getX();
                int posY=e.getY();
                int ampladaEssers=Constants.dimensionsEsser.width;
                int alturaEssers=Constants.dimensionsEsser.height;
                int x=posX/ampladaEssers;
                int y=posY/alturaEssers;
                mon.canviaEstatViuOMort(x,y);
                monVisible.repaint();
            }
        };
        addMouseListener(mouseAdapter);
}
 
     
Ja pots compilar, crear un objecte Finestra i, a clic de ratolí, posar i treure éssers del món:  
     
 
 
     
  L'extermini:    
     
  Ara estàs en dispòsició de fer que el botó Extermini faci el que ha de fer i, a més, veure-ho: es tracta, doncs, d'escriure-li codi al mètode public void actionPerformed(ActionEvent evt) de l'objecte ActionListener construït en crear el JButton "Extermini". És ben senzill: a la classe Mon hi tens el mètode public void ompleMon (), que omple el món d'Essers morts; el crides i després repintes el monVisible per veure'n els canvis:  
     
    // Col·locació del botó "Extermini". No cal crear-lo abans
    afegeixBoto(panelBotons,"Extermini",
                new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {

                        mon.ompleMon();
                        monVisible.repaint();
                    }
                                     });
 
     
  Compilació i prova:  
     
 
 
     
  Pas a pas...    
     
  Arriba el moment de fer que el joc jugui! De moment, només pas a pas: el procés continu queda per a la pràctica següent.  
     
  Comença per fer actiu el botó "Pas a pas". De la mateixa manera que amb el botó "Extermini", cal omplir amb codi el mètode public void mouseClicked(MouseEvent e) de l'objecte mouseAdapter del JButton "Pas a pas". El codi és ben semblant:  
     
    // Col·locació del botó "Pas a pas"
    botoPasAPas=new JButton(); // Creem el botó
    afegeixBoto(botoPasAPas,panelBotons,"Pas a pas",
                new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {

                        mon.pas();
                        monVisible.repaint();
                    }
                                     });
 
     
  Naturalment, això implica que el mètode public void pas () ha de ser present a la classe Mon (no hi era encara!). Posa'l, encara que no tingui codi a diintre:  
     
    /**
     * Fa un pas en l'evolució d'aquest Mon.
     */

    public void pas () {
        // Feina per fer: definir què fa
    }
 
     
  i ara ja et pots oblidar del botó "Pas a pas" (ja funciona, però no fa res perquè el mètode al qual crida, public void pas (), no fa res!) i concentrar-te en jugar el joc amb el codi d'aquest mètode.  
     
 

Que, a cada pas, un Esser neixi, sobrevisqui o mori depèn del nombre d'Essers veïns vius que té en un cert moment. Això implica que necessites un mètode que et compti el nombre de veïns vius que un cert Esser, el de la posició x,y, té. El dibuix següent mostra les posicions d'aquests vuit veïns:

 
     
 
 
     
  i el codi del mètode,en principi, és aquest:  
     

    /**
     * Recompte dels veïns vius de l'Esser a la posició x,y.
     * @param x la columna d'elMon en la qual és aquest Esser
     * @param y la fila d'elMon en la qual és aquest Esser

     * @return en nombre de veïns vius d'aquest Esser
     */
    private int quantsVeinsVius (int x,int y) {
        int quants=0;
            if (elMon[x-1][y-1].getEsViu()) {
                quants++;
            }
            if (elMon[x][y-1].getEsViu()) {
                quants++;
            }
            if (elMon[x+1][y-1].getEsViu()) {
                quants++;
            }
            if (elMon[x-1][y].getEsViu()) {
                quants++;
            }
            if (elMon[x+1][y].getEsViu()) {
                quants++;
            }
            if (elMon[x-1][y+1].getEsViu()) {
                quants++;
            }
            if (elMon[x][y+1].getEsViu()) {
                quants++;
            }
            if (elMon[x+1][y+1].getEsViu()) {
                quants++;
            }
        return quants;
    }

 
     
  Com ja veus, es tracta de preguntar a cadascun dels veins de l'Esser a la posició x,y si són vius o morts i, en cas que un d'ells sigui viu, incrementar en 1 la variable quants, que és la que retorna el mètode.  
     
  Hi ha, però un inconvenient: què passa quan el mètode pregunta pels veïns a l'esquerra d'un Esser que és a la columna 0? què passa quan el mètode pregunta pels veïns a la dreta d'un Esser que és a la columna dimensionsMon.width-1 (l'última de la dreta)? què passa quan el mètode pregunta pels veïns de sobre d'un Esser que és a la fila 0? què passa quan el mètode pregunta pels veïns de sota d'un Esser que és a la columna dimensionsMon.height-1 (la de baix de tot)? La resposta és simple: estaríem demanant per Essers que no són a la matriu elMon i el sistema treuria una excepció ArrayIndexOutOfBoundsException ("Excepció d'Index de la Matriu Fora de Límits"). Què fer? Hi ha diverses solucions per a això i te'n proposem una:  
     
  Enlloc de demanar per l'Esser a la posició p,q, es tracta de demanar per l'Esser a la posicio p%dimensionsMon.width,q%dimensionsMon.height. Amb això s'aconsegueix que, si, per exemple, p sobrepassa el límit de la dreta, és a dir, si p>dimensionsMon.width, es va a buscar la posició p%dimensionsMon.width, la qual és, segur, abans del límit p%dimensionsMon.width. El mètode quantsVeinsVius() adequadament reformat quedarà així:  
     

    /**
     * Recompte dels veïns vius de l'Esser a la posició x,y.
     * @param x la columna d'elMon en la qual és aquest Esser
     * @param y la fila d'elMon en la qual és aquest Esser
     * @return en nombre de veïns vius d'aquest Esser
     */
    private int quantsVeinsVius (int x,int y) {
        int quants=0;

        int lDreta=Constants.dimensionsMon.width;
        int lBaix=Constants.dimensionsMon.height;
            if (elMon[(x-1+lDreta)%lDreta]
                     [(y-1+lBaix)%lBaix].getEsViu()) {
                quants++;
            }
            if (elMon[x%lDreta]
                     [(y-1+lBaix)%lBaix].getEsViu()) {
                quants++;
            }
            if (elMon[(x+1)%lDreta]
                     [(y-1+lBaix)%lBaix].getEsViu()) {
                quants++;
            }
            if (elMon[(x-1+lDreta)%lDreta]
                     [y%lBaix].getEsViu()) {
                quants++;
            }
            if (elMon[(x+1)%lDreta][y%lBaix].getEsViu()) {
                quants++;
            }
            if (elMon[(x-1+lDreta)%lDreta]
                     [(y+1)%lBaix].getEsViu()) {
                quants++;
            }
            if (elMon[x%lDreta][(y+1)%lBaix].getEsViu()) {
                quants++;
            }
            if (elMon[(x+1)%lDreta][(y+1)%lBaix].getEsViu()) {
                quants++;
            }
        return quants;
    }

 
     
  Ara ja toca començar a posar codi al mètode pas(). Car repassar, un a un, tots els Essers del mon i preguntar quants veïns té cadascú. El repàs es fa per columnes i, a cada columna, per files:  
     
    /**
     * Fa un pas en l'evolució d'aquest Mon.
     */
    public void pas () {

        int lDreta=Constants.dimensionsMon.width;
        int lBaix=Constants.dimensionsMon.height;
            for (int x=0;x<lDreta;x++) {
                    for (int y=0;y<lBaix;y++) {
                        int numVeinsVius=quantsVeinsVius(x,y);
                        // Feina per fer: què fer quan ja se sap
                        // el nombre de veïns

                    }
            }
    }
 
     
  És el moment d'aplicar les regles del Joc de La Vida:
  1. Un ésser mort que tingui, exactament, tres veïns vius, passa a ser un ésser viu.

  2. Un ésser viu amb 2 o 3 veins vius roman viu; en cas contrari (cap o un veí viu o 4 o més veïns vius) mor per aïllament o per superpoblació.
 
     
    /**
     * Fa un pas en l'evolució d'aquest Mon.
     */
    public void pas () {
        int lDreta=Constants.dimensionsMon.width;
        int lBaix=Constants.dimensionsMon.height;
            for (int x=0;x<lDreta;x++) {
                    for (int y=0;y<lBaix;y++) {
                        int numVeinsVius=quantsVeinsVius(x,y);

                        Esser esser=elMon[x][y];
                        boolean esViu=esser.getEsViu();
                            if (esViu) {
                                   if (numVeinsVius==2 ||
                                       numVeinsVius==3) {
                                       //en l'estat següent és viu
                                   } else {
                                       //en l'estat següent és mort
                                   }
                            } else {
                                   if (numVeinsVius==3) {
                                       //en l'estat següent és viu
                                   } else {
                                       //en l'estat següent és mort
                                   }
                            }
                    }
            }
    }
 
     
 

Ara es planteja el problema de com guardar aquesta informació, una informació per a cada Esser, fins que s'hagin repassat tots els Essers del mon: no pots matar directament un Esser perquè això falsejaria la informació quant al nombre de veïns dels Essers que li són veïns! Els canvis efectius d'estat no es poden fer fins que hagis repassat tots els Essers del mon!

 
     
  Hi ha diverses solucions a això. Una te la proposem ara mateix i una altra en un exercici més avall.  
     
  El que et proposem és que defineixis una matriu d'Essers, de les mateixes dimensions de la matriu elMon, i hi guardis els Essers en el seus estats futurs:  
     
    /**
     * Fa un pas en l'evolució d'aquest Mon.
     */
    public void pas () {
        int lDreta=Constants.dimensionsMon.width;
        int lBaix=Constants.dimensionsMon.height;
        Esser[][] elMonProvisional=new Esser[lDreta][lBaix];
            for (int x=0;x<lDreta;x++) {
                    for (int y=0;y<lBaix;y++) {
                        int numVeinsVius=quantsVeinsVius(x,y);
                        Esser esser=elMon[x][y];
                        boolean esViu=esser.getEsViu();
                            if (esViu) {
                                   if (numVeinsVius==2 ||
                                       numVeinsVius==3) {

                                       elMonProvisional[x][y]=
                                           new Esser(true);
                                   } else {
                                       elMonProvisional[x][y]=
                                           new Esser(false);
                                   }
                            } else {
                                   if (numVeinsVius==3) {

                                       elMonProvisional[x][y]=
                                           new Esser(true);
                                   } else {

                                       elMonProvisional[x][y]=
                                           new Esser(false);
                                   }
                            }
                    }
            }
       
 // Ara només falta passar aquesta informació a elMon
    }
 
     
  I, finalment, cal copiar la matriu elMonProvisional sobre la matriu elMon, i el mon ja és en un nou estat!  
     
    /**
     * Fa un pas en l'evolució d'aquest Mon.
     */
    public void pas () {
        int lDreta=Constants.dimensionsMon.width;
        int lBaix=Constants.dimensionsMon.height;
        Esser[][] elMonProvisional=new Esser[lDreta][lBaix];
            for (int x=0;x<lDreta;x++) {
                    for (int y=0;y<lBaix;y++) {
                        int numVeinsVius=quantsVeinsVius(x,y);
                        Esser esser=elMon[x][y];
                        boolean esViu=esser.getEsViu();
                            if (esViu) {
                                   if (numVeinsVius==2 ||
                                       numVeinsVius==3) {
                                       elMonProvisional[x][y]=
                                           new Esser(true);
                                   } else {
                                       elMonProvisional[x][y]=
                                           new Esser(false);
                                   }
                            } else {
                                   if (numVeinsVius==3) {
                                       elMonProvisional[x][y]=
                                           new Esser(true);
                                   } else {
                                       elMonProvisional[x][y]=
                                           new Esser(false);
                                   }
                            }
                    }
            }
            for (int x=0;x<lDreta;x++) {
                    for (int y=0;y<lBaix;y++) {
                        elMon[x][y]=elMonProvisional[x][y];
                    }
            }
    }
 
     
 

Compila, crea un objecte Finestra i...

 
     
 

el Joc De La Vida (pas a pas) ja funcionaaaaaaa!!!!!!!

 
     
Fer les coses d'una altra manera: un exercici    
     
  Potser sigui massa aparatós haver de construir tota una matriu elMonProvisional només per guardar-hi l'estat futur dels Essers del mon... Hi ha una altra tècnica que consisteix en que siguin els propis Essers els qui guardin aquesta informació (la seva pròpia sentència!). Comença per afegir a la classe Esser la variable de classe boolean estaraViu i un mètode setter i un altre mètode getter per a aquesta variable:  
     
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Color;

/**
 * La classe Esser representa un ésser dels que poblen el món
 * del "Joc de la Vida".
 *
 * @author (el vostre nom)
 * @version (un número de versió o la data)
 */
public class Esser {

    /**
     * L'estat de viu o mort d'aquest Esser.
     */
    private boolean esViu=false;

    
/**
     * L'estat futur de viu o mort d'aquest Esser.
     */

    
private boolean estaraViu=false;

    /**
     * Mètode constructor per a objectes de la classe Esser.
     * @param esViu l'estat de viu o mort d'aquest Esser.
     */
    public Esser(boolean esViu) { // constructor
        this.esViu=esViu;
    }

    /**
     * Obtenció de l'estat de viu o mort d'aquest Esser.
     */
    public boolean getEsViu () {
        return esViu;
    }

    /**
     * Determinació de l'estat de viu o mort d'aquest Esser.
     * @param esViu l'estat de viu o mort d'aquest Esser que
     * es vol determinar.
     */
    public void setEsViu (boolean esViu) {
        this.esViu=esViu;
    }


    /**
     * Obtenció de l'estat futur de viu o mort d'aquest Esser.
     */
    public boolean getEstaraViu () {
        return estaraViu;
    }

    /**
     * Determinació de l'estat de viu o mort d'aquest Esser.
     * @param esViu l'estat de viu o mort d'aquest Esser que
     * es vol determinar.
     */
    public void setEstaraViu (boolean estaraViu) {
        this.estaraViu=estaraViu;
    }

    /**
     * Mètode paint per a aquest Esser.
     * @param dimensions les dimensions en píxels d'aquest Esser.
     * @param posicio la posició en píxels d'aquest Esser.
     * @param un objecte Graphics2D.
     */
    public void paint (Dimension dimensions,
                       Dimension posicio,
                       Graphics2D g) {
        if (esViu) { // Només pintar-lo si és viu
            // Color vermell viu
            g.setColor(Color.RED.brighter());
            // Omplir un cercle una mica més petit
            g.fillOval(posicio.width+1,posicio.height+1,
                       dimensions.width-2,dimensions.height-2);
            // Color vermell fosc
            g.setColor(Color.RED.darker().darker().darker());
            // Envoltar-lo amb una circumferència
            g.drawOval(posicio.width+1,posicio.height+1,
                       dimensions.width-2,dimensions.height-2);
        }
    }

}

 
     
  Ara modifica el mètode pas() de la classe Mon  
     
    /**
     * Fa un pas en l'evolució d'aquest Mon.
     */
    public void pas () {
        int lDreta=Constants.dimensionsMon.width;
        int lBaix=Constants.dimensionsMon.height;
        Esser[][] elMonProvisional=new Esser[lDreta][lBaix];
            for (int x=0;x<lDreta;x++) {
                    for (int y=0;y<lBaix;y++) {
                        int numVeinsVius=quantsVeinsVius(x,y);
                        Esser esser=elMon[x][y];
                        boolean esViu=esser.getEsViu();
                            if (esViu) {
                                   if (numVeinsVius==2 ||
                                       numVeinsVius==3) {
                                      
 // posar nou codi
                                   } else {
                                      
 // posar nou codi
                                   }
                            } else {
                                   if (numVeinsVius==3) {
                                      
 // posar nou codi
                                   } else {
                                      
 // posar nou codi
                                   }
                            }
                    }
            }
        // posar nou codi
    }
 
     
  i, sense definir cap matriu elMonProvisional o semblant, aconsegueix que tot funcioni bé...