viernes, 6 de marzo de 2015

Programación en red en MIDP

OBJETIVOS
En la clase de hoy vamos a ver el soporte que da MIDP a la programación en red. Como hemos visto en la clase teórica, en el CLDC de J2ME se rediseñaron las clases para entrada/salida y para el soporte a  conexiones de red de J2SE, definiendo lo que se denomina CLDC Generic Connection Framework.

CLDC Generic Connection Framework define una serie de interfaces para dar soporte a la variedad de tipos de conexiones que nos podemos encontrar en dispositivos móviles, pero no implementa ninguna de ellas, es en los perfiles donde se debe realizar esta implementación. En el perfil con el que estamos trabajando, MIDP, se debe dar soporte obligatoriamente a conexiones HTTP, a través de la implementación del interfaz HttpConnection.

Para la práctica final no necesitais hacer uso de conexiones de red, por lo que es muy recomendable realizar
esta práctica para que completeis vuestros conocimientos sobre MIDP.



 CLDC Generic Connection Framework
En el CLDC Generic Connection Framework, todas las conexiones se crean utilizando el método estático open de la clase Connector.
Si no se produce ningún error, este método devuelve un objeto que implementa una de las interfaces definidas en el CLDC Generic Connection Framework.

Estas interfaces están contenidas en el paquete javax.microedition.io y son:
  • El interfaz Connection es el tipo básico de conexión. Esta conexión sólo puede abrirse y cerrarse.

  • El interfaz InputConnection
    representa un dispositivo desde el que se pueden leer datos. Proporciona
    el método openInputStream
    que devuelve un stream de entrada para la conexión.


  • El interfaz OuputConnection representa un dispositivo en el que se pueden escribir datos.
    Proporciona el método openOutputStream que devuelve un stream de salida para la conexión.

  • El interfaz StreamConnection  que combina la conexiones de entrada y de salida anteriores.

  • El interfaz ContectConnection  que es una subinterfaz de StreamConnection. Proporciona acceso
    a información de algunos meta datos proporcionados en un conexión HTTP.

  • El interfaz StreamConnectionNotified que espera a que se establezca una conexión. Devuelve un
    StreamConnection a través del cual puede establecerse un enlace de comunicación bidireccional.

  • El interfaz DatagramConnection que representa el destino de un datagrama.
El método open de la clase Connector tiene la siguiente sintaxis, Connector.open(String);

donde el parámtero String tiene el formato "protocol:address;parameters". Por ejemplo:
  • Conexión HTTP:
    Connector.open("http://www.it.uc3m.es/pervasive");
    
  • Conexión Datagrama:
    Connector.open("datagram://address:port#");
    
  • Conmunicación con puerto serie:
    Connector.open("comm:0;baudrate=9600");
    
  • Acceso a ficheros:
    Connector.open("file:/miFichero.txt");
    

El objetivo de tener esta sintaxis, es abstraer al programador de las diferencias que existen entre los diferentes protocolos, de manera que la mayoría del código de una aplicación no se modifica cuando se cambia el protocolo que se está usando.

IMPORTANTE: Los ejemplos de conexiones anteriores son sólo ilustrativos. CLDC en sí mismo no proporciona ninguna implementación de ningún protocolo, éstas deben de proporcionarse a nivel de perfil. Además un determinado perfil no tiene porque implementar todos los protocolos, así MIDP proporciona una implementación del protocolo HTTP, pero en general no implementa ni sockets, ni datagramas (aunque algunos fabricantes pueden incorporarlas).

En el siguiente apartado veremos el interfaz HttpConnection propocionada en MIDP.



 HttpConnection
HTTP puede implementarse utilizando protocolos IP (como TCP/IP) o protocolos no-IP (como WAP o i-mode), por este motivo se seleccionó como protocolo para comunicaciones de red en MIDP. Todas las implementaciones de MIDP deben soportarlo, garantizando de esta manera la portabilidad de las aplicaciones, que utilizan este protocolo, entre diferentes dispositivos.

En MIDP se define un nuevo interfaz dentro de la jerarquía del CLDC Generic Connection Framework, el interfaz HttpConnection para el soporte a conexiones HTTP. Este interfaz extiende del interfaz ContentConnection.

El protocolo HTTP es un protocolo a nivel de aplicación del tipo petición/respuesta, en el que los parámetros de una petición deben establecerse antes de que se envíe la petición. La conexión puede estar en uno de los siguientes tres posibles estados:
  • "Setup": No se ha establecido todavía la conexión.
  • "Connected": Se ha establecido la conexión, la petición se ha enviado y se está esperando por una respuesta.
  • "Closed": La conexión se ha cerrado.
En el interfaz HttpConnection se proporcionan una serie de métodos que pueden invocarse en cada uno de los estados anteriores:

En el estado "setup" se pueden invocar los siguientes métodos:
  • setRequestMethod(String method) que establece el método de la petición, que puede ser POST,
    GET o HEAD.
    El método por defecto es GET y si el método no es HTTP o la implementación de HTTP está recortada y no lo soporta, se produce una IOException.
  • setRequestProperty(String key, String value) que permite indicar el valor de algunas propiedades HTTP antes de enviar la petición, la propiedad que se quiere establecer se indica en el parámetro key y su valor se establece en el parámetro value.
Por ejemplo, en el código siguiente se crea una conexión HTTP a la URL http://www.it.uc3m.es/pervasive y se indica que el método de la petición es POST, y que la propiedad HTTP User-Agent tiene el valor Profile/MIDP-1.0 Configuration/CLDC-1.0:
HttpConnection c = (HttpConnection)Connector.open("http://www.it.uc3m.es/pervasive");
c.setRequestMethod(HttpConnection.POST);
c.setRequestProperty("User-Agent, "Profile/MIDP-1.0 Configuration/CLDC-1.0");

La transición entre el estado "setup" y el estado "connected" se produce cuando se invoca algún método que precisa enviar o recibir datos al servidor con el que se establece la conexión. Algunos de los métodos que proporciona la implementación del interfaz HttpConnection y que provocan esta transición, son:
Cuando la conexión está abierta (se ha pasado al estado de "connected"), se pueden invocar los siguientes métodos:
A continuación se muestra un ejemplo de código en el que se utiliza la implementación de HttpConnection para leer el contenido de la página http://www.it.uc3m.es/celeste/docencia/cr/hola.txt y mostrarselo al usuario (MIDletHTTPExample.java):

import java.io.*;
import javax.microedition.midlet.*;
import javax.microedition.io.*;
import javax.microedition.lcdui.*;
 
/**
 * Un ejemplo de MIDlet para visualizar el contenido de una URL
 * utilizando la implementación del interfaz HttpConnection.
 */
 
public class MIDletHTTPExample extends MIDlet {
 
  private Display display;
  private String url = "http://www.it.uc3m.es/celeste/docencia/cr/hola.txt";
 
  public MIDletHTTPExample() {
    display = Display.getDisplay(this);
  }
 
  /**
   * Método startApp()
   */
  public void startApp() {
    // Llama al método download para descargar el contenido de la URL
    try {
      download(url);
    } catch(IOException e) {
      System.out.println("IOException: " + e);
    }
  }
 
  private void download (String url) throws IOException {
    StringBuffer b = new StringBuffer();
    InputStream is = null;
    HttpConnection c = null;
    TextBox t = null;
 
    try {
      long len = 0 ;
      int ch = 0;
 
      // Abre una conexión del tipo HttpConnection
      c = (HttpConnection)Connector.open(url);
      
      // Obtiene un stream de entrada, para leer el contenido de la url
      // con la que establece la conexión
      is = c.openInputStream();
      
      // Lee hasta que se cierra la conexión
      while ((ch = is.read()) != -1) {
        b.append((char)ch);
      }
      
      // Se contruye un TextBox con el contenido de la URL
      t = new TextBox("Hola...", b.toString(), 1024, 0);
 
    } finally {
      // Se cierra tanto el stream de entrada
      if (is != null)
        is.close();
      // Se cierra la conexión
      if (c != null) 
        c.close();
    }  
    
    // Se visualiza el TextBox en el Display
    display.setCurrent(t);
  }
  
   /**
    * pauseApp
    */
   public void pauseApp() {
   }
 
   /**
    * destroyApp
    */
   public void destroyApp(boolean unconditional) {
   }
}
 
Ejercicio 1:Partiendo del código del ejemplo anterior realizad un MIDlet que solicite al usuario una URL y una etiqueta HTML, y le muestre como resultado el texto modificado por esa etiqueta.

Ejercicio 2:Repetid el ejercicio anterior, pero además de visualizar el texto modificado por la etiqueta HTML, almacenad su contenido en un Record Store, de forma que el usuario de forma "off-line" pueda visualizar el resultado de sus consultas (por lo tanto, se debe de almacenar: URL solicitada, etiqueta HTML indicada y texto modificado por la etiqueta HTML en la URL indicada).

RMS: Almacenamiento en MIDP



 OBJETIVOS
MIDP define una sencilla base de datos orientada a registros que permite almacenar a las aplicaciones datos de forma persistente. Esta base se denomina Record Management System (RMS).
La práctica de hoy tiene como objetivo aprender los conceptos básicos para el manejo de esta base datos, a través del API que nos ofrece MIDP. Todas las clases relacionadas con RMS están contenidas en el paquete javax.microedition.rms.
Esta práctica os debe servir para enfrentaros con dos partes de la práctica final: almacenar el estado de una partida, para que sea posible parar y reanudar un partida (opciones del menú Stop y Restart) y para mantener el registro de mejores puntuaciones (opción Records).


 RecordStore
El mecanismo básico de almacenamiento de RMS es denominado record store. Un record store es un  conjunto de registros, y un registro es un byte array de datos de tamaño variable. Un record store está  representado por un objeto de la clase RecordStore.
Existen reglas importantes sobre los record store:
  1. El nombre de un record store consiste en una combinación de hasta 32 caracteres (sensible a las mayúsculas).

  2. Los record stores creados por MIDlets de un mismo MIDlet suite están almacenados en el mismo espacio de nombres, y por lo tanto, pueden compartir y ver sus contenidos.

  3. Los record stores creados por MIDlets en un MIDlet suite, no son accesibles para los MIDltes de otros MIDlets suite.

  4. El nombre de un record store debe ser único en un MIDlet suite.

Operaciones con un Record Store

La clase RecordStore proporciona los siguientes métodos para crear, abrir, cerrar y borrar un record store:
Cada record store mantiene al menos un campo de cabecera. Si no existe espacio suficente para almacenar la cabecera, el record store no se creará y se producirá una RecordStoreFullException. Si ocurre otro tipo de problema, como que el nombre del record store es demasiado largo, o el record store está corrupto, se produce una RecordStoreException.

A continuación, se puede ver un sencillo ejemplo de creación de un RecordStore (RecordStoreTest1.java).

import javax.microedition.midlet.*;
import javax.microedition.rms.*;
 
public class RecordStoreTest1 extends MIDlet {
    public RecordStoreTest1() { 
    }
    public void startApp() throws MIDletStateChangeException {
        RecordStore rs=null;
        try {
     rs = RecordStore.openRecordStore("file1",true);
            System.out.println("record store file1 is opened.");
        }catch(Exception e){
            System.out.println("Error: "+e.getMessage());
        }
        finally{
            //close the record store
            try {
         rs.closeRecordStore();
                System.out.println("record store file1 is closed");
            }catch (Exception e){
                System.out.println("Error: "+e.getMessage());
            }
        }
        destroyApp(true);
        notifyDestroyed();
    }
 
    /**
     * Pause the MIDlet
     */
    public void pauseApp() {
    }
 
    /**
     * Called by the framework before the application is unloaded
     */
    public void destroyApp(boolean unconditional) {
    }
}

Records


Los elementos de un record store se denominan records. Los records se representan por arrays de bytes y se identifican univocamente mediante recordIDs (valores enteros que comienzan en 1). Los records pueden se añadidos, borrados, leidos y modificados a través de los siguientes métodos, proporcionados por el API:


A continuación podeis ver un ejemplo sencillo, que partiendo del primer ejemplo, añade dos records nuevos y borra uno de ellos (RecordStoreTest3.java).

import javax.microedition.midlet.*;
import javax.microedition.rms.*;
 
public class RecordStoreTest3 extends MIDlet {
    public RecordStoreTest3() { 
    }
    public void startApp() throws MIDletStateChangeException {
        RecordStore rs=null;
        try {
     rs = RecordStore.openRecordStore("file1",true);
            byte data[]= new byte[4];
            for(int j=0; j<2; j++) {
                int i=rs.getNextRecordID();
                data[0] = (byte)((i >> 24) & 0xff);
                data[1] = (byte)((i >> 16) & 0xff);
                data[2] = (byte)((i >> 8) & 0xff);
                data[3] = (byte)(i & 0xff);
                System.out.println("record "+ rs.addRecord(data,0,4)+" is added.");
            }
  try {
             rs.deleteRecord(2);
             System.out.println("record 2 is deleted.");
  }catch(InvalidRecordIDException e) {
   System.out.println("record 2 does not exist");
  }
        }catch(Exception e){}
        finally{
            //close the record store
            try {
         rs.closeRecordStore();
            }catch (Exception e){}
        }
        destroyApp(true);
        notifyDestroyed();
    }
 
    /**
     * Pause the MIDlet
     */
    public void pauseApp() {
    }
 
    /**
     * Called by the framework before the application is unloaded
     */
    public void destroyApp(boolean unconditional) {
    }
}

La cabecera de un Record Store

Los record stores al igual que los ficheros mantienen una cabecera con información propia:

El API nos proporciona además la siguiente información adicional sobre los records store, aunque no está almacenada en la cabecera:

El interfaz RecordListener

A través del interfaz RecordListener se pueden monitorizar cambios en record stores. Se pueden gestionar tres tipos de eventos, a través de los métodos:
La clase RecordStore proporciona los siguientes métodos para añadir o borrar un RecordListener:
NOTA:Un objeto RecordStore puede tener varios RecordListeners registrados.


 RecordEnumeration
Después de que los records se borren, los recordIds de los records que están almacenados en un record store no son consecutivos. Por lo tanto, realizar un recorrido secuencial para obtener todos los records almacenados no es la manera más eficiente de hacerlo.

Para ello se proporciona la clase RecordEnumeration, que aunque en el API de MIDP se presenta como un interfaz, toda implementación de MIDP debe proporcionar una implementación de ella.

Un RecordEnumeration es parecido a un lista doblemente enlazada, en la que cada nodo representa un recordId. Un RecordEnumeration mantiene una secuencia lógica de recordIds de los records almacenados en un record store.

Ejercicio:Analizad el interfaz y realizad un pequeño programa en el que obteneis secuencialmente el contenido de los records en un record store. Cread después un nuevo programa que utilice la implementacion de RecordEnumeration y comprobad la diferencia.


 RecordFilter y RecordComparator
El interfaz RecordFilter define un único método public boolean matches(byte candidate[]) que nos permite definir un criterio para seleccionar un record en un record store que cumpla una determinada condición, de tal forma que si la cumple, se devuelve true y si no false.

El interfaz RecordComparator nos proporciona una forma sencilla de clasificar records. Define una única
función int compare(byte[] rec1, byte[] rec2), que puede devolver únicamente los siguientes constantes:
Por ejemplo, una manera de clasificarlos sería por fecha de creación. Así al implentar esta función se devolvería PRECEDES si rec1 se ha creado antes de rec2, FOLLOWS si se ha creado después y EQUIVALENT si se han creado simultaneamente.



Programación de juegos para móviles con J2ME Parte XII Final

Código Fuente

M1945.java

package mygame;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;
import java.io.*;

public class M1945 extends MIDlet implements CommandListener {

    private Command exitCommand, playCommand, endCommand;
    private Display display;
    private SSCanvas screen;

    public M1945() {
        display=Display.getDisplay(this);
        exitCommand = new Command("Salir",Command.SCREEN,2);
        playCommand = new Command("Jugar",Command.CANCEL,2);
        endCommand = new Command("Salir",Command.SCREEN,2);

        screen=new SSCanvas();

        screen.addCommand(playCommand);
        screen.addCommand(exitCommand);
        screen.setCommandListener(this);
    }

    public void startApp() throws MIDletStateChangeException {
        display.setCurrent(screen);
    }

    public void pauseApp() {}

    public void destroyApp(boolean unconditional) {}

    public void commandAction(Command c, Displayable s) {
        if (c == exitCommand) {
            if (screen.isPlaying()) {
                screen.quitGame();
            } else {
                destroyApp(false);
                notifyDestroyed();
            }
        }

        if (c == playCommand && !screen.isPlaying()) {
            // Play!!!
            new Thread(screen).start();
        }
    }
}

class SSCanvas extends Canvas implements Runnable {
    private int score,sleepTime,cicle,lives,shield;
    private int indice_in, indice, xTiles, yTiles;
    private boolean playing,fireOn=false;
    private boolean done;
    private int deltaX,deltaY;
    private Hero hero=new Hero(1);
    private Enemy[] enemies=new Enemy[7];
    private Bullet[] aBullet=new Bullet[7];
    private Sprite intro=new Sprite(1);
    private Explode[] explode=new Explode[7];
    private Sprite[] tile=new Sprite[5];

    // Mapa del juego
    int map[] ={ 1,1,1,1,1,1,1,
                1,1,1,1,1,1,1,
                1,2,1,1,1,1,1,
                1,1,1,4,1,1,1,
                1,1,1,1,1,1,1,
                1,1,3,1,2,1,1,
                1,1,1,1,1,1,1,
                1,4,1,1,1,1,1,
                1,1,1,1,3,1,1,
                1,1,1,1,1,1,1,
                1,4,1,1,1,1,1,
                1,1,1,3,1,1,1,
                1,1,1,1,1,1,1,
                1,1,1,1,1,1,1,
                1,2,1,1,1,1,1,
                1,1,1,4,1,1,1,
                1,1,1,1,1,1,1,
                1,1,3,1,2,1,1,
                1,1,1,1,1,1,1,
                1,4,1,1,1,1,1};

    public SSCanvas() {
        // Cargamos los sprites
        hero.addFrame(1,"/hero.png");
        intro.addFrame(1,"/intro.png");

        // Iniciamos los Sprites
        hero.on();
        intro.on();
    }

    void start() {
        int i;
        playing = true;
        sleepTime = 50;
        hero.setX(getWidth()/2);
        hero.setY(getHeight()-20);
        deltaX=0;
        deltaY=0;
        cicle=0;
        xTiles=7;
        yTiles=8;
        indice=map.length-(xTiles*yTiles);
        indice_in=0;
        score=0;
        lives=3;
        shield=0;

        // Inicializar enemigos
        for (i=1 ; i<=6 ; i++) {
            enemies[i]=new Enemy(2);
            enemies[i].addFrame(1,"/enemy1.png");
            enemies[i].addFrame(2,"/enemy2.png");
            enemies[i].off();
        }

        // Inicializar balas
        for (i=1 ; i<=6 ; i++) {
            aBullet[i]=new Bullet(2);
            aBullet[i].addFrame(1,"/mybullet.png");
            aBullet[i].addFrame(2,"/enemybullet.png");
            aBullet[i].off();
        }

        // Inicializamos los tiles
        for (i=1 ; i<=4 ; i++) {
            tile[i]=new Sprite(1);
            tile[i].on();
        }

        tile[1].addFrame(1,"/tile1.png");
        tile[2].addFrame(1,"/tile2.png");
        tile[3].addFrame(1,"/tile3.png");
        tile[4].addFrame(1,"/tile4.png");

        // Inicializamos explosiones
        for (i=1 ; i<=6 ; i++) {
            explode[i]=new Explode(6);
            explode[i].addFrame(1,"/explode1.png");
            explode[i].addFrame(2,"/explode2.png");
            explode[i].addFrame(3,"/explode3.png");
            explode[i].addFrame(4,"/explode4.png");
            explode[i].addFrame(5,"/explode5.png");
            explode[i].addFrame(6,"/explode6.png");
        }
    }

    void computeEnemies() {
        int freeEnemy,i;
        Random random = new java.util.Random();

        // Creamos un enemigo cada 20 ciclos
        if (cicle%20 == 0) {
            freeEnemy=0;
            // Buscar un enemigo libre
            for (i=1 ; i<=6 ; i++) {
                if (!enemies[i].isActive()) {
                    freeEnemy=i;
                }
            }
            // Asignar enemigo si hay una posición libre
            // en el array de enemigos
            if (freeEnemy != 0) {
                enemies[freeEnemy].on();
                enemies[freeEnemy].setX((Math.abs(random.nextInt()) % getWidth()) + 1);
                enemies[freeEnemy].setY(0);
                enemies[freeEnemy].setState(1);
                enemies[freeEnemy].setType((Math.abs(random.nextInt()) % 2) + 1);
                enemies[freeEnemy].init(hero.getX());
            }
        }
        // Mover los enemigos
        for (i=1 ; i<=6 ; i++) {
            if (enemies[i].isActive()) {
                enemies[i].doMovement();
            }
            // Mirar si la nave salió de la pantalla
            if ((enemies[i].getY() > getHeight()) || (enemies[i].getY() < 0)) {
                enemies[i].off();
            }
        }
    }

    void computeBullets() {
        int freeBullet,theEnemy,i,j;

        // Crear disparo del jugador
        freeBullet=0;
        if (fireOn) {
            // Buscar un disparo libre
            for (i=1 ; i<=6 ; i++) {
                if (!aBullet[i].isActive()) {
                    freeBullet=i;
                }
            }
            if (freeBullet !=0) {
                aBullet[freeBullet].on();
                aBullet[freeBullet].setX(hero.getX());
                aBullet[freeBullet].setY(hero.getY()-10);
                aBullet[freeBullet].setOwner(1);
            }
        }

        // Crear disparo de enemigos
        freeBullet=0;
        theEnemy=0;

        for (i=1 ; i<=6 ; i++) {
            if (enemies[i].getType() == 1 && enemies[i].isActive() && 
                enemies[i].getY() > getHeight()/2 && enemies[i].getY() < 
                (getHeight()/2)+5) {
                
                // Buscar un disparo libre
                for (j=1 ; j<=6 ; j++) {
                    if (!aBullet[j].isActive()) {
                        freeBullet=j;
                        theEnemy=i;
                    }
                }

                if (freeBullet !=0) {
                    aBullet[freeBullet].on();
                    aBullet[freeBullet].setX(enemies[theEnemy].getX());
                    aBullet[freeBullet].setY(enemies[theEnemy].getY()+10);
                    aBullet[freeBullet].setOwner(2);
                }
            }
        }


        // Mover los disparos
        for (i=1 ; i<=6 ; i++) {
            if (aBullet[i].isActive()) {
                aBullet[i].doMovement();
            }

            // Mirar si el disparo salió de la pantalla
            if ((aBullet[i].getY() > getHeight()) || (aBullet[i].getY() <= 0)) {
                aBullet[i].off();
            }
        }

    }

    void computeExplodes() {
        int i;

        for (i=1 ; i<=6 ; i++) {
            explode[i].doMovement();
        }
    }

    void doScroll() {
        // movimiento del scenario (scroll)
        indice_in+=2;
        if (indice_in>=32) {
            indice_in=0;
            indice-=xTiles;
        }

        if (indice <= 0) {
            // si llegamos al final, empezamos de nuevo.
            indice=map.length-(xTiles*yTiles);
            indice_in=0;
        }
    }

    void createExplode(int posx, int posy) {
        int freeExplode,i;
        freeExplode=0;

        // Buscar una explosión libre
        for (i=1 ; i<=6 ; i++) {
            if (!explode[i].isActive()) {
                freeExplode=i;
            }
        }

        if (freeExplode !=0) {
            explode[freeExplode].setState(1);
            explode[freeExplode].on();
            explode[freeExplode].setX(posx);
            explode[freeExplode].setY(posy);
        }
    }

    void checkCollide() {
        int i,j;
        boolean collision;
        collision=false;

        // Colisión heroe-enemigo
        for (i=1 ; i<=6 ; i++) {
            if (hero.collide(enemies[i]) && enemies[i].isActive() && shield == 0) {
                createExplode(hero.getX(),hero.getY());
                createExplode(enemies[i].getX(),enemies[i].getY());
                enemies[i].off();
                collision=true;
            }
        }

        // Colisión heroe-disparo
        for (i=1 ; i<=6 ; i++) {
            if (aBullet[i].isActive() && hero.collide(aBullet[i]) && 
                aBullet[i].getOwner() != 1 && shield == 0) {
                createExplode(hero.getX(),hero.getY());
                aBullet[i].off();
                collision=true;
            }
        }

        // colisión enemigo-disparo
        for (i=1 ; i<=6 ; i++) {
            if (aBullet[i].getOwner() == 1 && aBullet[i].isActive()) {
                for (j=1 ; j<=6 ; j++) {
                    if (enemies[j].isActive()) {
                        if (aBullet[i].collide(enemies[j])) {
                            createExplode(enemies[j].getX(),enemies[j].getY());
                            enemies[j].off();
                            aBullet[i].off();
                            score+=10;
                        }
                    }
                }
            }
        }

        if (collision == true) {
            lives--;

            // poner heroe al estado inicial
            hero.setX(getWidth()/2);
            hero.setY(getHeight()-20);

            // Durante 20 ciclos nuestra nave será inmune
            shield=20;

            if (lives <= 0) {
                playing=false;
            }
        }

        if (shield > 0)
            shield--;
    }

    void computePlayer() {
        // actualizar posición del avión
        if (hero.getX()+deltaX>0 && hero.getX()+deltaX
            hero.getY()+deltaY>0 && hero.getY()+deltaY
            hero.setX(hero.getX()+deltaX);
            hero.setY(hero.getY()+deltaY);
        }
    }

    void quitGame() {
        playing = false;
    }

    boolean isPlaying() {
        return playing;
    }

    public void run() {
        start();

        while (playing) {
            // Actualizar fondo de pantalla
            doScroll();
            // Actualizar posición del jugador
            computePlayer();
            // Actualizar posición de los enemigos
            computeEnemies();
            // Actualizar balas
            computeBullets();
            // Actualizar explosiones
            computeExplodes();
            // Comprobar colisiones
            checkCollide();
            // Contador de ciclos
            cicle++;
            // Actualizar pantalla
            repaint();
            serviceRepaints();
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
        }

        // Repintamos la pantalla
        // para mostrar pantalla de presentación
        repaint();
        serviceRepaints();
    }

    public void keyReleased(int keyCode) {
        int action=getGameAction(keyCode);

        switch (action) {
            case FIRE:
                fireOn=false;
                break;
            case LEFT:
                deltaX=0;
                break;
            case RIGHT:
                deltaX=0;
                break;
            case UP:
                deltaY=0;
                break;        
            case DOWN:
                deltaY=0;
                break;
        }
    }

    public void keyPressed(int keyCode) {
        int action=getGameAction(keyCode);

        switch (action) {
            case FIRE:
                fireOn=true;
                break;
            case LEFT:
                deltaX=-5;
                break;
            case RIGHT:
                deltaX=5;
                break;
            case UP:
                deltaY=-5;
                break;
            case DOWN:
                deltaY=5;
                break;
        }
    }

    public void paint(Graphics g) {
        int x=0,y=0,t=0;
        int i,j;
        
        g.setColor(255,255,255);
        g.fillRect(0,0,getWidth(),getHeight());
        g.setColor(200,200,0);

        g.setFont(Font.getFont(Font.FACE_PROPORTIONAL,Font.STYLE_BOLD,Font.SIZE_MEDIUM));

        if (playing == false) {
            intro.setX(getWidth()/2);
            intro.setY(getHeight()/2);
            intro.draw(g);
        } else {
            // Dibujar fondo
            for (i=0 ; i
                for (j=0 ; j
                    t=map[indice+(i*xTiles+j)];
                    // calculo de la posición del tile
                    x=j*32;
                    y=(i-1)*32+indice_in;
                    // dibujamos el tile
                    tile[t].setX(x);
                    tile[t].setY(y);
                    tile[t].draw(g);
                }
            }

            // Dibujar enemigos
            for (i=1 ; i<=6 ; i++) {
                if (enemies[i].isActive()) {
                    enemies[i].setX(enemies[i].getX());
                    enemies[i].setY(enemies[i].getY());
                    enemies[i].draw(g);
                }
            }

            // Dibujar el jugador
            if (shield == 0 || shield%2 == 0) {
                hero.setX(hero.getX());
                hero.setY(hero.getY());
                hero.draw(g);
            }

            // Dibujar disparos
            for (i=1 ; i<=6 ; i++) {
                if (aBullet[i].isActive()) {
                    aBullet[i].setX(aBullet[i].getX());
                    aBullet[i].setY(aBullet[i].getY());
                    aBullet[i].draw(g);
                }
            }

            // Dibujar explosiones
            for (i=1 ; i<=6 ; i++) {
                if (explode[i].isActive())
                    explode[i].draw(g);
            }
                g.drawString(" "+score,getWidth()-20,20,Graphics.HCENTER|Graphics.BOTTOM);
                g.drawString(" "+lives,20,20,Graphics.HCENTER|Graphics.BOTTOM);
        }
    }
}

Sprite.java

package mygame;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;
import java.io.*;

class Sprite {

    private int posx,posy;
    private boolean active;
    private int frame,nframes;
    private Image[] sprites;

    // constructor. 'nframes' es el número de frames del Sprite.
    public Sprite(int nframes) {
        // El Sprite no está activo por defecto.
        active=false;
        frame=1;
        this.nframes=nframes;
        sprites=new Image[nframes+1];
    }

    public void setX(int x) {
        posx=x;
    }

    public void setY(int y) {
        posy=y;
    }

    int getX() {
        return posx;
    }

    int getY() {
        return posy;
    }

    int getW() {
        return sprites[nframes].getWidth();
    }

    int getH() {
        return sprites[nframes].getHeight();
    }

    public void on() {
        active=true;
    }

    public void off() {
        active=false;
    }

    public boolean isActive() {
        return active;
    }

    public void selFrame(int frameno) {
        frame=frameno;
    }

    public int frames() {
        return nframes;
    }

    // Carga un archivo tipo .PNG y lo añade al sprite en
    // el frame indicado por 'frameno'
    public void addFrame(int frameno, String path) {
        try {
            sprites[frameno]=Image.createImage(path);
        } catch (IOException e) {
            System.err.println("Can`t load the image " + path + ": " + e.toString());
        }
    }

    boolean collide(Sprite sp) {
        int w1,h1,w2,h2,x1,y1,x2,y2;

        w1=getW(); // ancho del sprite1
        h1=getH(); // altura del sprite1
        w2=sp.getW(); // ancho del sprite2
        h2=sp.getH(); // alto del sprite2
        x1=getX(); // pos. X del sprite1
        y1=getY(); // pos. Y del sprite1
        x2=sp.getX(); // pos. X del sprite2
        y2=sp.getY(); // pos. Y del sprite2

        if (((x1+w1)>x2)&&((y1+h1)>y2)&&((x2+w2)>x1)&&((y2+h2)>y1)) {
            return true;
        } else {
            return false;
        }
    }

    // Dibujamos el Sprite
    public void draw(Graphics g) {
        g.drawImage(sprites[frame],posx,posy,Graphics.HCENTER|Graphics.VCENTER);
    }
}

Enemy.java

package mygame;

import java.util.*;

class Enemy extends Sprite {

    private int type,state,deltaX,deltaY;

    public void setState(int state) {
        this.state=state;
    }

    public int getState(int state) {
        return state;
    }

    public void setType(int type) {
        this.type=type;
    }

    public int getType() {
        return type;
    }

    public void doMovement() {
        Random random = new java.util.Random();
        // Los enemigos de tipo 2 cambiaran su trayectoria
        // al alcanzar una posición determinada (pos. 50)
        if (type == 2 && getY() > 50 && state != 2) {
            // paso al estado 2 (movimiento diagonal)
            state = 2;
            if ((Math.abs(random.nextInt()) % 2) + 1 == 1) {
                deltaX=2; 
            } else {
                deltaX=-2; 
            }
        }
        // movemos la nave
        setX(getX()+deltaX);
        setY(getY()+deltaY);
    }

    public void init(int xhero) {
        deltaY=3;
        deltaX=0;

        if (type == 1) {
            if (xhero > getX()) {
                deltaX=2;
            } else {
                deltaX=-2;
            }
        }
    }

    // Sobrecarga del método draw de la clase Sprite
    public void draw (javax.microedition.lcdui.Graphics g) {
        selFrame(type);
        // llamamos al método 'draw' de la clase padre (Sprite)
        super.draw(g);
    }

    public Enemy(int nFrames) {
        super(nFrames);
    }
}

Bullet.java

package mygame;

class Bullet extends Sprite {
    private int owner;

    public Bullet(int nFrames) {
        super(nFrames);
    }

    public void setOwner(int owner) {
        this.owner=owner;
    }

    public int getOwner() {
        return owner;
    }

    public void doMovement() {
        // si owner = 1 el disparo es nuestro
        // si no, es del enemigo
        if (owner == 1) {
            setY(getY()-6);
        } else {
            setY(getY()+6);
        }
    }

    // Sobrecarga del método draw de la clase Sprite
    public void draw (javax.microedition.lcdui.Graphics g) {
        selFrame(owner);
        // llamamos al método 'draw' de la clase padre (Sprite)
        super.draw(g);
    }
}

Explode.java

package mygame;

class Explode extends Sprite {

    private int state;

    public Explode(int nFrames) {
        super(nFrames);
        state=1;
    }

    public void setState(int state) {
        this.state=state;
    }

    public int getState() {
        return state;
    }

    public void doMovement() {
        state++;

        if (state > super.frames())
            super.off();
    }

    // Sobrecarga del método draw de la clase Sprite
    public void draw (javax.microedition.lcdui.Graphics g) {
        selFrame(state);
        // llamamos al método 'draw' de la clase padre (Sprite)
        super.draw(g);
    }
}

Hero.java

package mygame;

class Hero extends Sprite {

    private int state;

    public void setState(int state) {
        this.state=state;
    }

    public int getState(int state) {
        return state;
    }

    public Hero(int nFrames) {
        super(nFrames);
    }
}