Diciembre 12, 2004

[J2ME] Aplicación de ejemplo: un dado

Empezamos a trabajar en serio, desarrollando una aplicación muy sencillita sobre la que iremos añadiendo sucesivas mejoras. Se trata de un dado

Dos capturas:

Cap1.jpg Cap2.jpg

La aplicación no es nada del otro mundo. Muestra un dado, de manera que cada vez que el usuario presiona el botón de disparo, o la tecla "5", se genera un número aleatorio entre 1 y 6, y se muestra la cara del dado correspondiente.

También hay una pantalla de "acerca de", que muestra información sobre la aplicación

Comencemos. He encapsulado la generación del número aleatorio en una clase ( DiceController.java ). Aquí está el código:

package net.designnation.j2me.dice;

import java.util.Random;

public class DiceController implements IDiceActions{
 private static int LOWER_LIMIT = 1;
 private static int UPPER_LIMIT = 6;
 	
 private Random random;
 	
 DiceController( )
 {
  random = new Random( );
 }
 	
 public int rollDice( )
 {
  return 1+ Math.abs( random.nextInt() % UPPER_LIMIT );
 }
}

Esta clase implementa un interfaz ( IDiceActions ):

package net.designnation.j2me.dice;

public interface IDiceActions 
{
 int rollDice( );
}

¿Por qué ese interfaz?. Porque esa será la referencia a esta clase que tendrá el programa. Pero eso lo veremos más tarde.

Las aplicaciones J2ME deben construirse sobre una clase que extienda de MIDlet. Esa clase ( al menos en este ejemplo ) tendrá la responsabilidad de manejar la interacción con los botones de alto nivel ( los soft buttons, asignados por el sistema ). pero en este ejemplo, además, queremos manejar la entrada utilizando un botón más ( el de disparo ), por lo que necesitaremos acceder al manejo de teclado a bajo nivel. Eso lo haremos a través de otra clase, que extenderá de Canvas.

el código de la clase "principal":

package net.designnation.j2me.dice;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class Dice extends MIDlet implements IExitActions, CommandListener
{
 private Display display;
 private DiceCanvas diceCanvas;
 	
 private Command exitCommand 	= new Command("Exit", Command.EXIT, 1);
 private Command backCommand 	= new Command( "Back", Command.BACK, 2 );
 private Command aboutCommand 	= new Command( "About", Command.SCREEN, 5 );
 	
 protected static String APP_NAME = "Dice";
 	
 	
 public Dice() 
 {
  super();
  		
  diceCanvas = new DiceCanvas( );
  
  diceCanvas.addCommand( exitCommand );
  diceCanvas.addCommand( aboutCommand );
  diceCanvas.setCommandListener( this );
 }
 
 protected void startApp() throws MIDletStateChangeException 
 {
  display = Display.getDisplay( this );
  display.setCurrent( diceCanvas );
 }
 
 protected void pauseApp() 
 {
 }
 
 protected void destroyApp( boolean arg0 ) 
 {
  notifyDestroyed( );
 }
 	
 public void exitApp( )
 {
  destroyApp( true );
 }
 	
 public void commandAction (Command c, Displayable d) 
 {
  if (c == exitCommand )
  {
   exitApp( );
  }
  if( c == aboutCommand )
  {
   HelpScreen helpScreen = new HelpScreen( );
   helpScreen.addCommand( backCommand );
   helpScreen.setCommandListener( this );
   display.setCurrent( helpScreen );
  }
  if( c == backCommand )
  {
   display.setCurrent( diceCanvas );
  }
 }	
}

Esta clase hereda de MIDlet ( es obligatorio ), y por tanto, implementará tres métodos ( startApp, pauseApp, destroyApp ), que deberán ser sobreescritos. También he creado tres comandos ( exitCommand, aboutCommand, backCommand ), que son los que serán asignados a los soft buttons

Cuando se instancia esta clase, se crea también una instance de DiceCanvas ( más tarde la veremos con más calma, pero es una clase que extiende de Canvas ). También se registra como listener para los comandos.

public Dice() 
{
 super();
 		
 diceCanvas = new DiceCanvas( );
 
 diceCanvas.addCommand( exitCommand );
 diceCanvas.addCommand( aboutCommand );
 diceCanvas.setCommandListener( this );
}

Cuando comienza la ejecución de la aplicación ( y por tanto, se ejecuta el método startApp ), se hace que el display sea ocupado por el canvas

display = Display.getDisplay( this );
display.setCurrent( diceCanvas );

La interacción de alto nivel está manejada por el método commandAction. Cuando el usuario selecciona "about", se crea una instancia de HelpScreen, y se la hace ocupar el display

if( c == aboutCommand )
{
 HelpScreen helpScreen = new HelpScreen( );
 helpScreen.addCommand( backCommand );
 helpScreen.setCommandListener( this );
 display.setCurrent( helpScreen );
}

Ahora, echémosle un vistazo a DiceCanvas:

package net.designnation.j2me.dice;

import java.io.IOException;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

public class DiceCanvas extends Canvas 
{
 private IDiceActions dice;
 	
 DiceCanvas(  )
 {
  dice = new DiceController( );
  		
 }
 protected void paint( Graphics g ) 
 {
  Font font = null;
  	    
  Image diceIcon = null;
  	    
  g.setColor( 0xffffff );
  g.fillRect( 0, 0, getWidth( ), getHeight( ) );
  g.setColor(0);	    
  	    
  font = Font.getFont( Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_SMALL );
  g.setFont( font );	    
  	    
      
  try
  {
   String imageName = "/resources/icon"+ dice.rollDice( ) + ".png";
   	    	
   diceIcon = Image.createImage( imageName );
   	    	
  }
  catch (IOException e) {
   throw new RuntimeException ("Unable to load diceIcon: "+e);
  }
  
  g.drawImage( diceIcon, getWidth( )/ 2,
   	30,
  	Graphics.TOP | Graphics.HCENTER );
          
  g.drawString ("Press fire / 5", getWidth () / 2, getHeight () -30, 
  	Graphics.HCENTER | Graphics.BASELINE);
  
  g.drawString ("to roll the dice", getWidth () / 2, getHeight () -10, 
  	Graphics.HCENTER | Graphics.BASELINE);
 }
 	
 public void keyReleased( int keyCode )
 {
  if ( getGameAction( keyCode ) == FIRE )
  {
   repaint( );
  }
 }
}

¿Recuerdas el interfaz que implementaba DiceController?. La referencia a DiceController en DiceCanvas está tipeada como IDiceActions, por tanto DiceCanvas sólo sabe que hay un método llamado rollDice que le va a devolver un integer, pero no sabe a qué clase pertenece ese método.

private IDiceActions dice;
	
DiceCanvas(  )
{
 dice = new DiceController( );
}

El método paint está heredado de Canvas, y se ejecuta cada vez que queremos refrescar el display. Por tanto, es donde escribimos el texto y presentamos los iconos del dado. Simplemente, cargamos un icono diferente dependiendo del valor que nos devuelva rollDice( ). Con cada refresco se borra el display completo, lo cual no es nada eficiente, pero eso cambiará en la siguiente versión.

try
{
 String imageName = "/resources/icon"+ dice.rollDice( ) + ".png";
 	    	
 diceIcon = Image.createImage( imageName );
}

También manejamos las pulsaciones de teclado, utilizando el método keyReleased

public void keyReleased( int keyCode )
{
 if ( getGameAction( keyCode ) == FIRE )
 {
  repaint( );
 }
}

Y finalmente, hay una pantalla de "acerca de", que extiende de Form, y que contiene el icono del programa y un texto informativo sobre la aplicación

package net.designnation.j2me.dice;

import java.io.IOException;

import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.ImageItem;


public class HelpScreen extends Form
{
     
 HelpScreen( )
 {
  super( "About "+ Dice.APP_NAME );
          
  try {
   ImageItem logo = new ImageItem( "", 
   Image.createImage( "/resources/icon.png"),
         ImageItem.LAYOUT_CENTER | ImageItem.LAYOUT_NEWLINE_BEFORE 
         | ImageItem.LAYOUT_NEWLINE_AFTER, "" );
               
   append( logo );
  }
  catch (IOException e) {
   throw new RuntimeException ("Unable to load Image: "+e);
  }
            
  append("You can roll the dice using the fire button or the '5' key\n\nDeveloped by Cesar Tardaguila\nIcons by Celia Carracedo\n\nhttp://www.design-nation.net/en");
 }
 
}

Es un Form muy simple, que contiene un icono y un texto. Nada especial

Hay varias cosas que tienen que mejorarse. Lo primero de todo, la aplicación pesa bastante ( sobre 18 Kb ), por los iconos. Por eso, en la siguiente versión, intentaré dibujar los iconos por código. También intentaré añadir un poco de animación, para intentar hacer el resultado un poco "flashero", así que habrá que manejar Threads. Y también habrá que mejorar el refresco de pantalla.

Queda mucho por hacer, pero al menos ya hay un punto de partida.

Si te quieres bajar la aplicación:

http://www.design-nation.net/j2me/dice.jad

http://www.design-nation.net/j2me/dice.jar

El código fuente y los iconos:

http://www.design-nation.net/j2me/dice_source.zip

Yo sólo lo he podido probaar en mi N-Gage, por lo que si lo pruebas en algún otro dispositivo, y encuentras algún problema, por favor, házmelo saber.

Y ya es suficiente para un domingo, ¿ no ?

Escrito por Cesar Tardaguila en: Diciembre 12, 2004 11:55 PM | TrackBack
Comentarios