jueves, diciembre 30, 2010

Java - Tetris con Java ME


"Si usa algún código del siguiente tutorial, den el icono de ME GUSTA del Facebook que se encuentra en su mano derecha, para que se vuelva Seguidor del Blog y también comentenos que tal les pareció el tutorial"

1. Entorno

  • JDK 1.6.0 update 22
  • Netbeans 6.9.1.

2. Introducción


Este tutorial presenta de una forma amena, gráfica y fácil los pasos para la creación de una aplicación (juego, programa, etc…) en J2ME. Desde la instalación de los componentes necesarios, hasta la ejecución y trabajo con los mismos, en las siguientes lecciones aprenderás a moverte fácilmente por una herramienta de desarrollo Java como es NetBeans. Obviaremos lo que es y la explicación del profile MIDP para J2ME, puesto que existen multitud de artículos en Internet que lo explican claramente. Lo que no abundan son ejemplos concretos sobre una herramienta RAD para el desarrollo en J2ME, que es lo que vamos a tratar aquí. El fin es conseguir, en pocos pasos, la familiaridad con estas herramientas para que fácilmente puedas ir desarrollando tus propias aplicaciones.  Como es habitual en todos mis tutoriales, nos apoyaremos sobre todo en imágenes, que es la forma mas visual, didáctica y fácil de aprender. Recordar que este ejemplo es solamente para caso practico, ya que falta agregar mas cosas, como subir la velocidad, score, etc. Que ya queda a su criterio para que lo mejoren.

3. Desarrollo



3.1. Creando el Proyecto

Debemos de crear un proyecto del tipo Java ME y Mobile Application desde Netbeans














Como nombre al proyecto le pondremos "Tetris"














Después que le ponemos el nombre a nuestro proyecto, nos saldrá un formulario como el que sigue. Y seleccionamos la opción por defecto, debido a que ahí solamente seleccionamos que tipo de celular vamos a ejecutar la aplicación















3.2. Creación de clases

Debemos añadir a nuestro proyecto 5 clases y una imagen donde se va a mostrar las figuras del Tetris si es que no la conocen (pero es de forma opcional). La distribución de las clases se mostrara a continuación



















3.2.1. Clase Rejilla

Sirve para dibujar el limite donde se van a mostrar las figurar de Tetris.

package TetrisMidlet;

//@author hwongu

public class Rejilla {

    static final int VACIA = 0;
    static final int BLOQUE = 1;
    static final int PIEZA = 2;
    private int anchura;
    private int altura;
    private int[][] celdas;

    public Rejilla(int w, int h) {
        anchura = w;
        altura = h;
        celdas = new int[anchura][altura];
        initRejilla();
    }

    public int getAnchura() {
        return anchura;
    }

    public int getAltura() {
        return altura;
    }

    public void assignTipoCelda(int x, int y, int valor) {
        celdas[x][y] = valor;
    }

    public int getTipoCelda(int x, int y) {
        return celdas[x][y];
    }

    public void initRejilla() {
        int i, j;

        for (i = 0; i < anchura; i++) {
            for (j = 0; j < altura; j++) {
                celdas[i][j] = VACIA;
            }
        }
        // Añadimos los muros exteriores
        for (i = 0; i < anchura; i++) {
            //celdas[i][0]=BLOQUE;
            celdas[i][altura - 1] = BLOQUE;
        }
        for (j = 1; j < altura - 1; j++) {
            celdas[0][j] = BLOQUE;
            celdas[anchura - 1][j] = BLOQUE;
        }
    }

    boolean copiaFiguraEnRejilla(Figura fig) {
        Elemento elemento;
        boolean valorDevuelto = false;

        for (int i = 0; i < fig.cantidadElmentos(); i++) {
            elemento = fig.getElementoPos(i);
            if (elemento.getFila() + fig.getYOrigen() < 4) {
                valorDevuelto = true;
            }
            celdas[elemento.getColumna() + fig.getXOrigen()][elemento.getFila() + fig.getYOrigen()] = PIEZA;
        }
        return valorDevuelto;
    }

    boolean seChoca(Figura fig, int direccion) {
        Elemento elemento;
        for (int i = 0; i < fig.cantidadElmentos(); i++) {
            elemento = fig.getElementoPos(i);
            if (direccion == Figura.ABAJO) {
                if (celdas[elemento.getColumna() + fig.getXOrigen()][elemento.getFila() + fig.getYOrigen() + 1] != VACIA) {
                    return true;
                }
            } else if (direccion == Figura.IZQUIERDA) {
                if (celdas[elemento.getColumna() + fig.getXOrigen() - 1][elemento.getFila() + fig.getYOrigen()] != VACIA) {
                    return true;
                }
            } else if (direccion == Figura.DERECHA) {
                if (celdas[elemento.getColumna() + fig.getXOrigen() + 1][elemento.getFila() + fig.getYOrigen()] != VACIA) {
                    return true;
                }
            }
        }
        return false;
    }

    int primeraFilaLlena(int desdeFila) {
        boolean encontrada = false;

        while (desdeFila < altura - 1 && !encontrada) {
            if (filaLlena(desdeFila)) {
                encontrada = true;
            } else {
                desdeFila++;
            }
        }
        if (encontrada) {
            return desdeFila;
        } else {
            return -1;
        }
    }

    int ultimaFilaVacia(int fvacia) {
        boolean encontrada = false;

        while ((fvacia < altura - 1) && !encontrada) {
            if (!filaVacia(fvacia)) {
                encontrada = true;
            } else {
                fvacia++;
            }
        }
        return fvacia - 1;
    }

    boolean filaVacia(int fila) {
        int i = 1;
        boolean vacia = true;
        while ((i < anchura - 1) && vacia) {
            if (celdas[i][fila] != VACIA) {
                vacia = false;
            }
            i++;
        }
        return vacia;
    }

    boolean filaLlena(int fila) {
        int i = 1;
        boolean llena = true;
        while ((i < anchura - 1) && llena) {
            if (celdas[i][fila] != PIEZA) {
                llena = false;
            }
            i++;
        }
        return llena;
    }

    void eliminarFilasLlenas() {
        boolean hayfilasllenas = true;
        int filavacia = ultimaFilaVacia(0);
        int filallena = 0;

        while (hayfilasllenas) {
            filallena = primeraFilaLlena(filallena);
            if (filallena >= 0 && filallena < altura) {
                for (int i = filallena - 1; i >= filavacia; i--) {
                    if (i == -1) {
                        for (int j = 0; j < anchura; j++) {
                            celdas[j][0] = VACIA;
                        }
                    } else {
                        copiarFilas(i, i + 1);
                    }
                }
                filavacia++;
                filallena++;
            } else {
                hayfilasllenas = false;
            }
        }
    }

    void copiarFilas(int forigen, int fdestino) {
        for (int i = 0; i < anchura; i++) {
            celdas[i][fdestino] = celdas[i][forigen];
        }
    }
}

3.2.2. Clase Elemento

Muestra en que posición se encuentra la figura que se esta moviendo o ya esta dentro de la rejilla.

package TetrisMidlet;

//@author hwongu

public class Elemento {

    private int fila;
    private int columna;

    public Elemento(int f, int c) {
        fila = f;
        columna = c;
    }

    public int getFila() {
        return fila;
    }

    public int getColumna() {
        return columna;
    }

    public void setFila(int valor) {
        fila = valor;
    }

    public void setColumna(int valor) {
        columna = valor;
    }
}

3.2.3. Clase Figura

Esta clase es la encargada de dibujar la figura que deseamos mostrar.

package TetrisMidlet;

import java.util.Vector;

//@author hwongu

public class Figura {

    static final int IZQUIERDA = 0;
    static final int DERECHA = 1;
    static final int ABAJO = 2;
    static final int ARRIBA = 3;
    static java.util.Random rand = new java.util.Random();
    private Vector elements;
    private int xorigen;
    private int yorigen;

    public Figura(int fila0, int fila1, int fila2, int fila3) {
        elements = new Vector();
        agregarFigura(0, fila0);
        agregarFigura(1, fila1);
        agregarFigura(2, fila2);
        agregarFigura(3, fila3);
    }

    public static Figura nuevaFigura() {
        Figura fig = null;
        int tipoFigura = rand.nextInt(7);
        if (tipoFigura == 0) {
            fig = new Figura(
                    0x0000,
                    0x0FF0,
                    0x0FF0,
                    0x0000); 
        } else if (tipoFigura == 1) {
            fig = new Figura(
                    0x0F00,
                    0x0F00,
                    0x0FF0,
                    0x0000); 
        } else if (tipoFigura == 2) {
            fig = new Figura(
                    0x00F0,
                    0x00F0,
                    0x0FF0,
                    0x0000); 
        } else if (tipoFigura == 3) {
            fig = new Figura(
                    0x0000,
                    0x0F00,
                    0xFFF0,
                    0x0000);
        } else if (tipoFigura == 4) {
            fig = new Figura(
                    0x0F00,
                    0x0F00,
                    0x0F00,
                    0x0F00);
        } else if (tipoFigura == 5) {
            fig = new Figura(
                    0x0F00,
                    0x0FF0,
                    0x00F0,
                    0x0000);
        } else if (tipoFigura == 6) {
            fig = new Figura(
                    0x00F0,
                    0x0FF0,
                    0x0F00,
                    0x0000);
        }
        fig.xorigen = 3;
        fig.yorigen = 0;

        return fig;
    }

    private void agregarFigura(int fila, int valor) {
        if ((valor & 0xF000) > 0) {
            elements.addElement(new Elemento(fila, 0));
        }
        if ((valor & 0x0F00) > 0) {
            elements.addElement(new Elemento(fila, 1));
        }
        if ((valor & 0x00F0) > 0) {
            elements.addElement(new Elemento(fila, 2));
        }
        if ((valor & 0x000F) > 0) {
            elements.addElement(new Elemento(fila, 3));
        }
    }

    public int cantidadElmentos() {
        return elements.size();
    }

    public Elemento getElementoPos(int pos) {
        return (Elemento) elements.elementAt(pos);
    }

    public int getXOrigen() {
        return xorigen;
    }

    public int getYOrigen() {
        return yorigen;
    }

    public void mueve(int direccion) {
        int c, d, i, j;
        if (direccion == ABAJO) {
            yorigen++;
            ;
        } else if (direccion == IZQUIERDA) {
            xorigen--;
        } else if (direccion == DERECHA) {
            xorigen++;
        }
    }

    public void rotar(Rejilla rej) {
        Elemento elemento;
        int x, y, i;
        boolean sepuederotar = true;
        Vector newelements = new Vector();

        i = 0;
        while (i < cantidadElmentos() && sepuederotar) {
            elemento = getElementoPos(i);
            x = elemento.getColumna();
            y = elemento.getFila();
            if (rej.getTipoCelda(y - 1 + getXOrigen(), -x + 3 + getYOrigen()) != Rejilla.VACIA) {
                sepuederotar = false;
            } else {
                newelements.addElement(new Elemento(-x + 3, y - 1));
            }
            i++;
        }
        if (sepuederotar) {
            elements = newelements;
        }
    }
}

3.2.4. Clase Mueve

Esta clase es la encargada de mover la figura, acá utilizamos hilos para si es que en algún caso ustedes quieren agregar la opción de pausa lo puedan implementar, también en esta clase se muestra la velocidad con la cual bajan las figuras, les dejo para su investigación para que puedan subirle el nivel de velocidad conformen ganen en el juego.

package TetrisMidlet;

//@author hwongu

public class Mueve implements Runnable {

    private int delay;
    private boolean continuar = true;
    private boolean suspendFlag = true;
    private TetrisMidlet tetrisMidlet;

    public Mueve(TetrisMidlet tetris, int nivel) {
        tetrisMidlet = tetris;
        delay = actualizaRetardo(nivel);
        Thread t = new Thread(this);
        t.start();
    }

    public void run() {
        try {
            while (continuar) {
                synchronized (this) {
                    while (suspendFlag) {
                        wait();
                    }
                }
                Thread.sleep(delay);
                if (!tetrisMidlet.getRejilla().seChoca(tetrisMidlet.getFigura(), Figura.ABAJO)) {
                    tetrisMidlet.getFigura().mueve(Figura.ABAJO);
                    if (tetrisMidlet.getCanvas() != null) {
                        tetrisMidlet.getCanvas().repaint();
                    }
                } else {
                    boolean valor = tetrisMidlet.getRejilla().copiaFiguraEnRejilla(tetrisMidlet.getFigura());
                    tetrisMidlet.getRejilla().eliminarFilasLlenas();
                    if (tetrisMidlet.getCanvas() != null) {
                        tetrisMidlet.getCanvas().repaint();
                    }
                    if (!valor) {
                        tetrisMidlet.nuevaFigura();
                    } else {
                        System.out.println("He llegado al final");
                        continuar = false;
                    }
                }
            }// end while(continuar)
        } catch (InterruptedException e) {
            System.out.println("Hilo MueveSerpiente interrumpido");
        }
    }

    
     // Detiene momentaneamente la ejecución de la hebra, haciendo que la Figura actual
     //quede parada.
     synchronized public void suspender() {
        tetrisMidlet.getCanvas().repaint();
        suspendFlag = true;
    }

    //Reanuda el movimiento de la hebra. La Figura actual vuelve  a moverse.
    public synchronized void reanudar() {
        tetrisMidlet.getCanvas().repaint();
        suspendFlag = false;
        notify();
    }

    
    //Termina la ejecución de la hebra.
    public void parar() {
        continuar = false;
    }


    //Nos dice si la hebra está o no parada.
    synchronized public boolean getParado() {
        return suspendFlag;
    }

    
    //La siguiente función actualiza el retardo que espera la hebra
    //para mover la Figura actual. El nivel más lento será
    //el 0 (retardo 700) y el más rápido el 10 (retardo 50)
    private int actualizaRetardo(int nivel) {
        if (nivel > 10) {
            nivel = 10;
        } else if (nivel < 0) {
            nivel = 0;
        }
        return (400 - (nivel * 35));
    }
}

3.2.5. Clase MiCanvas

Clase donde se mostraran todos los elementos del tetris

package TetrisMidlet;

import javax.microedition.lcdui.*;

// @author hwongu
public class MiCanvas extends Canvas implements CommandListener {

    private TetrisMidlet tetris;
    private Command exitCommand;
    private int anchoCelda = -1;

    public MiCanvas(TetrisMidlet t) {
        try {
            setCommandListener(this);
            exitCommand = new Command("Exit", Command.EXIT, 1);
            addCommand(exitCommand);
            this.tetris = t;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void dibujaRejilla(Graphics g) {
        int i, j;
        Rejilla rejilla = tetris.getRejilla();
        int xoffset = (getWidth() - rejilla.getAnchura() * anchoCelda) / 2;
        for (i = 0; i < rejilla.getAnchura(); i++) {
            for (j = 0; j < rejilla.getAltura(); j++) {
                if (rejilla.getTipoCelda(i, j) == Rejilla.BLOQUE) {
                    g.setColor(0, 0, 0);
                    g.drawRect(xoffset + i * anchoCelda, j * anchoCelda, anchoCelda,
                            anchoCelda);
                } else if (rejilla.getTipoCelda(i, j) == Rejilla.PIEZA) {
                    g.setColor(255, 255, 0);
                    g.fillRect(xoffset + i * anchoCelda, j * anchoCelda, anchoCelda,
                            anchoCelda);
                    g.setColor(255, 0, 0);
                    g.drawRect(xoffset + i * anchoCelda, j * anchoCelda, anchoCelda,
                            anchoCelda);
                }
            }
        }
    }

    // Dibuja la Figura fig en el Graphics g pasado como par´ametro
    // (normalmente el asociado a este Canvas)
    // @param fig la Figura a dibujar
    // @param g el Graphics donde se dibujar´a
    public void dibujaFigura(Figura fig, Graphics g) {
        if (fig != null) {
            Elemento elemento;
            Rejilla rejilla = tetris.getRejilla();
            int xoffset = (getWidth() - rejilla.getAnchura() * anchoCelda) / 2
                    + fig.getXOrigen() * anchoCelda;
            int yoffset = fig.getYOrigen() * anchoCelda;
            for (int i = 0; i < fig.cantidadElmentos(); i++) {
                elemento = fig.getElementoPos(i);
                g.setColor(255, 255, 0);
                g.fillRect(xoffset + elemento.getColumna() * anchoCelda,
                        yoffset + elemento.getFila() * anchoCelda, anchoCelda,
                        anchoCelda);
                g.setColor(255, 0, 0);
                g.drawRect(xoffset + elemento.getColumna() * anchoCelda,
                        yoffset + elemento.getFila() * anchoCelda, anchoCelda,
                        anchoCelda);
            }
        }
    }

    public void paint(Graphics g) {
        if (anchoCelda == -1) {
            anchoCelda = Math.min(getWidth() / tetris.getRejilla().getAnchura(),
                    (getHeight() - 10) / tetris.getRejilla().getAltura());
        }
        g.setColor(255, 255, 255);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(0, 0, 0);
        g.translate(0, 12);
        dibujaRejilla(g);
        dibujaFigura(tetris.getFigura(), g);
        g.translate(0, -12);
    }

    protected void keyPressed(int keyCode) {
        if (keyCode == getKeyCode(LEFT)) {
            if (!tetris.getRejilla().seChoca(tetris.getFigura(),
                    Figura.IZQUIERDA)) {
                tetris.getFigura().mueve(Figura.IZQUIERDA);
                if (tetris.getCanvas() != null) {
                    tetris.getCanvas().repaint();
                }
            }
        } else if (keyCode == getKeyCode(RIGHT)) {
            if (!tetris.getRejilla().seChoca(tetris.getFigura(),
                    Figura.DERECHA)) {
                tetris.getFigura().mueve(Figura.DERECHA);
                if (tetris.getCanvas() != null) {
                    tetris.getCanvas().repaint();
                }
            }
        } else if (keyCode == getKeyCode(UP)) {
            tetris.getFigura().rotar(tetris.getRejilla());
            if (tetris.getCanvas() != null) {
                tetris.getCanvas().repaint();
            }
        } else if (keyCode == getKeyCode(DOWN)) {
            if (!tetris.getRejilla().seChoca(tetris.getFigura(),
                    Figura.ABAJO)) {
                tetris.getFigura().mueve(Figura.ABAJO);
                if (tetris.getCanvas() != null) {
                    tetris.getCanvas().repaint();
                }
            }
        }
    }

    protected void keyReleased(int keyCode) {
    }

    protected void keyRepeated(int keyCode) {
    }

    protected void pointerDragged(int x, int y) {
    }

    protected void pointerPressed(int x, int y) {
    }

    protected void pointerReleased(int x, int y) {
    }

    public void commandAction(Command command, Displayable displayable) {
        if (command == exitCommand) {
            javax.microedition.lcdui.Display.getDisplay(tetris).setCurrent(tetris.getForm());
        }
    }
}

3.3. Dibujando el Midlet

El Midlet esta conforma por Alert y por un Form, en este ultimo es donde se mostrara la opciones si vamos a jugar, ver el score, etc. Debemos elaborar el siguiente diseño















3.3.1. splashAlert

El "splashAlert" solo nos muestra una bienvenida al juego, el "okCommand" que se adiciona debe de dirigir hacia el form para que se muestre el formulario. Su diseño es el siguiente

















3.3.2. form

El "form" tiene el siguiente diseño
















Debemos de codificar el "okCommand1" para eso hacemos clic derecho en el "okCommand1" y seleccionamos la opcion "Go to source" y dentro de ese evento ponemos el siguiente codigo

public void commandAction(Command command, Displayable displayable) {                                               
        // write pre-action user code here
        if (displayable == form) {                                           
            if (command == exitCommand) {                                         
                // write pre-action user code here
                exitMIDlet();                                           
                // write post-action user code here
            } else if (command == okCommand1) {                                          
                // write pre-action user code here

                                           
                // write post-action user code here
                //Para q valla al boton
                if (choiceGroup.getSelectedIndex() == 0) {
                    System.out.println("Ha seleccionado Jugar");
                    javax.microedition.lcdui.Display.getDisplay(this).setCurrent(this.miCanvas);
                    inicializaJuego();
                    mueve.reanudar();
                } else if (choiceGroup.getSelectedIndex() == 1) {
                    System.out.println("Ha seleccionado Opciones");
                } else if (choiceGroup.getSelectedIndex() == 2) {
                    System.out.println("Ha seleccionado Ver records");
                }
            }                                           
        } else if (displayable == splashAlert) {
            if (command == okCommand) {                                         
                // write pre-action user code here
                switchDisplayable(null, getForm());                                           
                // write post-action user code here
            }                                                  
        }                                                
        // write post-action user code here
}                   

4. Ejemplo de la Aplicación


6 comentarios:

nice post. thanks.

HOLA OYE ME PODRIAS ENVIAR TU CODIGO COMLETO ESTOY HACIENDO LA APLIACION DEL TETRIS PERO NO ME CORRE Y QUISIERA SABER QUE ME FALTA BUENO GRACIAS

Ahí esta el ejemplo completo, quizás puede ser porque fue una versión antigua de netbeans. Te recomiendo que crees un proyecto nuevo y copies el código.

La esperanza de recibir alguna ayuda de www.programandoconcafe.com si se tiene alguna duda.