ThinkGeek - Cool Stuff for Geeks and Technophiles

December 12, 2004

[J2ME] First Application: Dice

It's time to develop a MIDlet. I've started with a simple one, just a dice ( a small cube marked on each side with from one to six dots ), without any animation, but that just shows the basic architecture of a MIDlet, and a bit of low-level key management.

Two screenshots:

Cap1.jpg Cap2.jpg

The application is not very complex. It shows one side of the dice ( it's selected randomly ), and each time the user presses the fire button or the "5" key, it just generates a new random integer between 1 and 6, and shows the appropiate side of the dice.

There's also an "about" screen, that shows some info about the application.

I've separated ( just to improve encapsulation ) the generation of the random number into a class ( DiceController.java ). Here's the code:

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 );
 }
}

This class implements an interface ( IDiceActions ):

package net.designnation.j2me.dice;

public interface IDiceActions 
{
 int rollDice( );
}

Why does DiceController implements an interface?. Because that interface is the reference that the MIDlet will have of this class. But we'll see it later.

Every J2ME application must be built around a class that extends MIDlet. That class ( at least in this example ) will have the responsibility to manage all the high-level button handlers ( it will handle the main application buttons, those asigned to the phone's soft buttons ). But in this example, I want to track also some of the phone's keys ( fire and "5" ), so I will need also to write a class to manage them. And that class must be a Canvas.

So, let's take a look at the main class:

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 );
  }
 }	
}

This class extends MIDlet ( that's mandatory ), and like every midlet, it inherits three methods ( startApp, pauseApp, destroyApp ). I've also created three commands ( exitCommand, aboutCommand, backCommand ), that will be asigned to the soft buttons.

When this class is instantiated, it creates an instance of DiceCanvas ( we'll take a look at this class later, but it extends Canvas ). It also registers itself as a listener of the commands that are passed to DiceCanvas.

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

When the application starts ( and the method startApp is executed ), the canvas is setted as the current display.

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

The method commandAction handles the high-level interaction. When the user selects "about", it creates and instance of HelpScreen, and sets it as the current display.

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

Now, let's take a look at 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( );
  }
 }
}

Do you remember the interface implemented by DiceController?. Now, the reference to DiceController is typed IDiceActions, so DiceCanvas only knows that there's a method called rollDice that returns a number, but it doesn't know the class that implements it.

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

The paint method is inherited from Canvas, and is executed every time we want to refresh the display. So there, we draw the text and icons we want to show. The display is cleared with every refresh, which is not very efficient, but that will be changed in the next version. we just paint a different icon with every refresh, depending on the result of rollDice( ).

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

We also handle the keyboard input, through the method "keyReleased".

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

And finally, there's an "about" screen, which extends Form, and that will contain some info about the application.

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");
 }
 
}

It's just a simple Form, that contains an icon and a text. Nothing special.

There are some things that have to be improved. First of all, the whole application is a bit heavy ( about 18 Kb ), due to the icons used to represent the Dice. So, in the next version, I'll try to draw those icons using the drawing API. I'll also try to add some animation, just to make it as similar to flash as possible, so I'll have to introduce some Thread management. And the way the display is refreshed should be improved too.

There's still a lot of work to do, but now we have a starting point. But that will be in the next post.

If you want to download the application:

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

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

And the source code and icons:

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

I've just tested it on my Nokia N-gage, but if you test it on another device, please let me know if there was any problem

Enough for a Sunday!

Posted by Cesar Tardaguila Date: December 12, 2004 11:55 PM | TrackBack
Comments

Works fine on my sony ericsson z600.

Cheers for the tute... wanted to write a little j2me app for a while just hadn't the chance to look up the relevant specs...

Posted by: Tim Lucas en: December 13, 2004 02:08 AM

Thanks for the feedback, Tim.

Posted by: Cesar Tardaguila en: December 13, 2004 07:53 AM

Nice. I haven't tested it, but i can relate. I know i will dive into java at some point to do some development stuff. Great new step Cesar.

Posted by: mstyle en: December 19, 2004 01:06 PM

Hi Cesar,

This is a good application which would really help J2ME starters.

Even though its a small application but I can understand what kind of feelings you must be having...

I recently bought Nokia 6600 to do some Mobile R&D. My main focus is to work with FlashLite but I am also trying MIDLets & some small C++ programs....

It's really fun doing these :)

keep it up & all the best...

-abdul

Posted by: Abdul Qabiz en: January 6, 2005 03:22 PM

Thanks for all the kind comments.

I'm also trying to focus on FlashLite and J2ME in general, and I'd like to try some C++ also.

I also hope to hear from all of you ( and your related work to devices ) as soon as posible ;)

Posted by: Cesar Tardaguila en: January 6, 2005 08:55 PM

Do you remember the interface implemented by DiceController?. Now, the reference to DiceController is typed IDiceActions, so DiceCanvas only knows that there's a method called rollDice that returns a number, but it doesn't know the class that implements it.

what's the problem with referencing the class directly rather than the interface?

Posted by: newbie en: February 13, 2005 03:03 AM

It's not that referencing the class directly is a problem. But, referencing the interface, will allow to change the class, or implement a proxy or a factory to obtain the value of the dice.

So, it's a question of flexibility.

Posted by: Cesar Tardaguila en: February 13, 2005 09:12 AM

Hi there,

I'm going to give this a go using Nokia Developer's Suit for J2ME.

I'm an experienced VB developer, but new to Java/J2ME, but i'm finding it quite hard to get started...

Leon

Posted by: Leon en: February 18, 2005 02:38 PM