Gama Network Presents:


Creating Games using J2ME
By David Fox
Gamasutra
September 17, 2001
URL:
http://www.gamasutra.com/features/20010917/fox_01.htm

With the advent of Java 2 Micro Edition (J2ME) on mobile phones, it is now possible to create mobile games that aren't all that crappy!

Now, don't get me wrong. Some creative folks <http://www.jamdat.com/html/games.html> have devised truly interesting games using the Wireless Application Protocol (WAP). But that's sort of like building a skyscraper out of Popsicle sticks-in the end the simple text-pushing protocol just won't cut muster if you want responsive, graphically rich games. Gladiator, one of the most popular WAP games, is really nothing more than a rock-paper-scissors clone!
For several years now, phone manufacturers have had native hardware capable of supporting decent games. Nokia's Snake game <http://www.nokia.com/snake/> is probably the most notable example. But mobile phone developer's kits are often proprietary. And downloading, installing, sharing, and porting these games is an all-but impossible task.

J2ME Overview


The Java 2 Micro Edition (J2ME) is a true-to-its-word subset of the standard Java libraries. J2ME consists of:

While various J2ME profiles can run on anything from advanced set-top boxes to tiny chips on smart credit cards, for the sake of this article we will focus MIDlets-applets in the CLDC configuration written to the MIDP profile. Note that you can run MIDlets on your Palm as well. Check out this page <http://developer.java.sun.com/developer/products/wireless/midp/articles-palm/convert/>, which has full instructions on converting MIDlet packages and deploying them on the Palm.

Games on Phones?


Mobile phones are a bold new platform for gaming. Today, there are more than 600 million mobile-phone users worldwide. Mobile phone users generally tend to be affluent, educated, and they often have lots of time on their hands. In the United States, for example, people spend 50-percent more time commuting than any other country (Yankee Group). This is the perfect time to pull out a mobile phone and play some quick games.

In the near future, we will likely see micro devices become even smaller and specialized. Phones the size of earplugs, voice-activated assistants on wristwatches, and smart chips on credit cards are all becoming a reality.

Unsurprisingly, games are keeping up and even helping to lead this paradigm. While it may seem silly to try to achieve a rich, meaningful immersion on a tiny 100x100 pixel screen, there's one thing mobile phone games give you that even the best consoles can't provide: They're always with you, and can be played anywhere you go. This not only means that games can now be more convenient, but wholly new types of games can be designed that take advantage of new lifestyles.

The Joy of Java

Most every major mobile phone and handheld device manufacturer immediately realized the potential of J2ME: If Java were to be placed on the gadget, then hundreds of thousands of developers would immediately be able to create applications and add value. Furthermore, because it's Java, a program written for one device would be able to run on another device with little or no modifications. That certainly makes more sense that trying to force developers to learn a native language and API in order to create programs for your phone.
And so, most every major mobile phone manufacturer joined with Sun to create something called the CLDC: The Connected, Limited Device Configuration, along with the MIDP: The Mobile Information Device Profile. Mobile phone manufacturers have definitely embraced Java in a way that not even PC manufacturers and browser makers have. Java is clearly the future platform of choice for mobile devices, and an ideal platform for mobile games.

The Bad News
Programming games for handheld devices is a total pain. The screen is miniscule—160 by 160 on the Palm, and much less on mobile phones. The memory is limited—the Palm only gives you 2 to 8 Meg, depending on the model. You have little dynamic memory to play around with—most mobile phones only give you 32K, the Palm has a 96K heap limit. There's limited network connectivity, with a teeny, tiny 9.6 kilobit per second bandwidth. And the processor itself is often hundreds of times slower than your average desktop.

Programming in a language like Java only adds a layer onto this hard-to-digest cake. Sun's Kilobyte Virtual Machine (KVM) gets its name because it takes up a few kilobytes of space. But even those few bytes are wasteful. The KVM also usurps a fair bit of memory. A native application is the size and power it is, asking for nothing more. In addition, Java classes have the added overhead of a slow startup. While startup is going on, the KVM is going through the class, allocating heap space and verifying the bytecode.

Additionally, a Java programmer is restricted to the capabilities of the virtual machine. Because Java must work on a wide variety of devices, it is written for the lowest common denominator.

For example, here are some big stumbling blocks:

Additionally, the Anfyteam in Italy has put together a detailed list of gaming and graphics-related stuff that MIDP is missing <http://www.anfyteam.com/dev/j2me/midpimage.html>.

Due to all the restrictions on a small device, there is no way of fitting in a just-in-time compiler (JIT). This means code will run quite slowly. In fact, while playing around with some casual tests, a Java 2 Micro Edition (J2ME) application runs about three to eight times slower than a native Palm application written in a compiled language such as C.

If you want to get some idea of the graphics speed of J2ME, the Anfyteam has created a great benchmark called Amark. Download it here <http://www.anfyteam.com/dev/j2me/amark12.zip>.

So is J2ME like war - good for absolutely nothing?

The Good News

First, some very good news: Java-enabled mobile phones embed the KVM right on the chipset. That means there are no memory hits, quick startup times, and quicker processing times.
Another interesting development is that several brands of Java phones offer neat extension APIs that let you access special, native features. Motorola and Nokia, for example, have announced game APIs that allow for audio, animations, transparency, and better graphics.
Additionally, it's important to remember that Java is perhaps the easiest modern language to develop in. With garbage collection of old objects and lack of memory allocation and pointers, it's a great way to create quick prototypes and develop them out into full apps. The object-oriented nature of Java makes it easy to maintain, making sweeping code changes with a little modification to a superclass.

Writing a native handheld application requires countless hours of debugging, emulating, testing, deploying, redebugging, and packaging. Write to a wrong memory location, and you can easily fry the machine. Waste a little memory on a desktop or server application, and nobody will bat an eye. But when dealing with a tiny space like the Palm, every bad byte hurts. Memory leaks in Java are possible, but they are much easier to avoid.

Perhaps the most compelling argument for using Java is that - you guessed it - you can write once and run every-damn-where. Theoretically, an application written for a Motorola cell-phone can also run on Nokia, an Ericsson, a Siemens, or a Palm. The same code could even be compiled and run as an applet in a Web browser, as an application on a million-dollar server machine, in your car's dashboard, or—eventually—in a Java-powered neural link to your own brain!

Creating a Midlet


If you have any experience creating Java applications or applets, then programming in J2ME won't seem like such a stretch. The steps are basically the same:

  1. Write your program
  2. Compile it
  3. Preverify it
  4. Package It
  5. Test It
  6. Debug It
  7. Release it!

The only thing that should set off your mental alarms is step number three—preverification. This may sound weird and complicated, but is actually quite easy. The purpose of preverification, theoretically, is to go through your bytecode and set hints up so that the actual verification of bytecode on the Palm will happen much more quickly, saving you valuable startup time.

There are many development environments that do all the compiling, preverification, and packaging for you. Metrowerk's Code Warrior has a J2ME plug-in, and Borland's JBuilder has a Handheld Express add-on. In addition, Nokia, Siemens, RIM, Zucotto, and Motorola all offer special SDKs and IDEs for J2ME development. See the resources list at the end of this article for more info.

Sun's own J2ME Wireless Toolkit is free, complete, really easy, and available for Windows, Linux, and Solaris. It also comes with a bunch of source code, including sample games such as Snake, Sokoban, a Tile Sliding game, Pong, and Star Cruiser.

Sun's own J2ME Wireless Toolkit is free, complete,
really easy, and available for Windows, Linux, and
Solaris.

We'll be using the Wireless Toolkit throughout this article. Download it here <http://java.sun.com/products/j2mewtoolkit/>.

Note that in order to run the Wireless Toolkit, you'll need Java itself (JDK1.3 or better), which has all the engines and libraries necessary to compile code. If you don't already have the JDK, you can grab it here <http://java.sun.com/j2se/1.3/download-windows.html>. Be sure to install it per directions, with all the proper settings for classpaths and paths.

Write Your Program

Just like an applet is based on the java.applet.Applet calss, class, a MIDLet always extends javax.microedition.midlet.

A MIDlet handles basic event handling and can be thought of as the one and only "window" that your application runs within.

Enough talk. Let's look at some code. Let's pass up the wonderful cliché of a nice Hello World program and create a full single-player Tic-Tac-Toe game instead.

Writing the Midlet

The heart of every MIDlet is the startApp() method. It kicks the MIDlet into action and should set up all the components or other objects you are interested in. Every MIDlet must also contain a pauseApp() and destroyApp() methods, which are called if and when a user chooses to either pause or quit your program.

Graphically speaking, the actual screen on your mobile phone or other devices is a Display object. Every MIDlet has one and only one Display object, which you can access using getDisplay(this).

The Display
A Display can consist of either a Screen object or a Canvas. Every Screen has an optional title (usually at the top) and Ticker (usually running along the bottom). Specific types of Screens include:

Getting Graphical

But let's get real, here. Most game developers want to customize components, capture user input, and create more detailed graphics. So pretty much every game will need something called a Canvas.

A Canvas must define the paint() method. The Graphics object allows for simple renderings using familiar Java methods such as drawArc(), drawImage(), drawLine(), drawRect(), drawString(), fillRect().

Note that images need to be in the PNG format, and color images are supported on phones that have color screens. You could create an image as follows:

Image house = Image.createImage("/house.png");

You should always create your own Canvas subclass. For example:

class SillyCanvas extends Canvas
{
     public void paint(Graphics g)
     {
        g.drawRect(1,1,getWidth()-2,getHeight()-2);
        g.setFont(Font.getFont(Font.FACE_MONOSPACE,
           Font.STYLE_PLAIN,Font.SIZE_SMALL));
        g.setColor(0);
        g.drawString("Hello World", getWidth() / 2, 0,
           Graphics.HCENTER | Graphics.TOP);
        g.drawImage(house, 10,30, g.TOP|g.LEFT);
     }
}

This Canvas contains a rectangle bordering the entire screen with the string "Hello World" at the top, horizontal center. A house image is also drawn at the (10,30) coordinate.
You could drop this canvas into the Display by creating it in your MIDlet:

private SillyCanvas aCanvas = new SillyCanvas();

And then, in the MIDlet's startApp() method or anywhere else that made sense, you could set the current Display to show the special canvas:

theDisplay.setCurrent(aCanvas);

Note that a mobile phone's Display can only show one Screen or Canvas at a time. A typical application starts with a List as a main menu of choices, branching into other types of Screens depending on the user's selection.

Animating
The typical way of animating with MIDP is to use double buffering:

  1. Have your main Canvas class draw a special offscreen image.
  2. Create a separate Thread class that contains that offscreen image instance. Draw any animations or other graphics to the offscreen image within the threaded class.
  3. Call repaint() on the MIDlet's main Canvas. Use the sleep() method within an endless loop to achieve slower or faster frame rates.
Double buffering in this way guarantees that your animation will occur at the same rate no matter which phone your game is running on.

Note that many phones automatically double-buffer your graphics for you. You can check whether double buffering is supported within a Canvas using the isDoubleBuffered() method.

You may want to check the isDoubleBuffered() method and only create an offscreen Image if double buffering is not supported, otherwise you can leave it as null. You can then change the graphics context within your paint method appropriately:

if( offscreen != null ){
          g = offscreen.getGraphics();
     }

Rolling your own double buffering can make for smoother animations, but copying images can very slow and memory-intensive. Also, some systems repaint the screen slower than the image can be copied-these phones will show graphic flickering.

Be aware that the refresh rate of most mobile phone screens is much slower than you may be used to for PC game programming. Frame rates of 4 fps are common... if you're lucky.

Creating Tic Tac Toe

Tic Tac Toe MIDlet
running on the
Motorola i85s

So let's create a quick and dirty Tic Tac Toe MIDlet would create a form and add it to the display using Display's setCurrent() method.

Our TicTacToe game will consist of a 3x3 array of characters. Each square in the grid will be assigned a number, corresponding to the position on the phone's keypad. When you hit a number, you put either an X or an O in the array, depending on whose turn it is. So we won't even use any graphics. We'll simply use the Canvas to draw the array, as a few rows of Strings. The game continues as so until somebody wins. [Code Listing: Tic Tac Toe]

If all goes well, you'll wind up with a fresh, happy TicTacToe.class file. To test the application out, Sun has been kind enough to release a nice emulator. Just hit the Build button in the Wireless Toolkit. This will create the necessary Java classes. Your classes
will also be automatically preverified and packaged into JAR and JAD files.

You can now hit the Run button. You can choose which Device you want to emulate by playing with the Device pull-down menu.

If you happen to actually own an i85s phone, you can plug it into your PC and use the phone's linking software to install the MIDlet directly (the JAR and the JAD file in the \J2mewtk\apps\TicTacToe directory) into the phone's memory. This is similar to the way you might install an application on the Palm Pilot.

In the very near future, Motorola and other mobile phone manufacturers may make it easy for users to download MIDlets right to their phones, on demand.

[Code Listing: Tic Tac Toe]

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
 
public class TicTacToe extends MIDlet implements CommandListener {
 
    // The main display elements
    private Display theDisplay;
    private TicTacToeCanvas canvas;
 
    // Keep track of winner
    private String winner = "";
    private boolean gameOver = false;
 
    // The game state
    private int whoseturn = 0;
    private char board[][] = { {'1' , '2' , '3'} ,
                               {'4' , '5' , '6'} ,
                               {'7' , '8' , '9'} };
 
    // Command
    static final Command exitCommand = new Command("Exit", Command.STOP, 1);
 
    public TicTacToe()
    {
        // Create the main Display
        theDisplay = Display.getDisplay(this);
    }
 
 
    class TicTacToeCanvas extends Canvas
    {
       TicTacToeCanvas()
       {
       }
 
       public void paint(Graphics g)
       {
           int width = getWidth();
           int height = getHeight();
           g.setColor( 0, 0, 0 );
           g.fillRect( 0, 0, width, height );
           g.setColor( 255, 255, 255);
           String r1 = board[0][0]+"|"+board[0][1]+"|"+board[0][2]+"\n";
           String r2 = board[1][0]+"|"+board[1][1]+"|"+board[1][2]+"\n";
           String r3 = board[2][0]+"|"+board[2][1]+"|"+board[2][2];
           g.drawString("TIC TAC TOE",15,0, Graphics.LEFT|Graphics.TOP);
           g.drawString(r1,30,15, Graphics.LEFT|Graphics.TOP);
           g.drawString(r2,30,30, Graphics.LEFT|Graphics.TOP);
           g.drawString(r3,30,45, Graphics.LEFT|Graphics.TOP);
           if (winner != "")
           {
                   g.drawString("WINNER:
"+winner,0,60,Graphics.LEFT|Graphics.TOP);
           }
           else
           {
                   g.drawString("TURN: Player
"+(whoseturn+1),0,60,Graphics.LEFT|Graphics.TOP);
           }
       }
 
       // Check for keyboard entry
       protected void keyPressed(int keyCode) {
        // Fill in the X and Os
         if (keyCode >= 49 && keyCode <= 57 && !gameOver)
         {
            int x = 0, y = 0;
            keyCode -= 49;
            if (keyCode < 3)
            {
               x = keyCode;
               y = 0;
            } 
            else if (keyCode < 6)
            {
               x = keyCode-3;
               y = 1;
            } 
            else
            {
               x = keyCode-6;
               y = 2;
            } 
            // If this slot is open...
          if (board[y][x] != 'X' && board[y][x] != 'O')
           {
             if (whoseturn == 0)
              board[y][x] = 'X';
             else
               board[y][x] = 'O';
 
             // Next player's turn
             whoseturn = 1-whoseturn;
 
             checkForWinner();
 
             // repaint
             canvas.repaint();
            }
         }
       }
    }
 
    protected void startApp() throws MIDletStateChangeException
    {
       canvas = new TicTacToeCanvas();
       
       // Add the exit command
       canvas.addCommand(exitCommand);
       canvas.setCommandListener(this) ;
 
       theDisplay.setCurrent(canvas);
    }
 
    protected void pauseApp() { }
 
    public void destroyApp(boolean unconditional)
    {
    }
 
    // Handle events.
    public void commandAction(Command c, Displayable d)
    {
      if (c == exitCommand)
      {
        destroyApp(false);
        notifyDestroyed();
      }
    }
 
    private void checkForWinner()
    {
        int i,j;
        int numXConsec = 0;   // Number of consecutive Xs
        int numOConsec = 0;   // Number of consecutive Os
 
        boolean tie=true;
 
        // Check for a tie
        for (i=0; i<3; i++)
        {
               for (j=0; j<3; j++)
                       if (board[i][j] != 'X' || board[i][j] != 'O')
                       {
                               tie = false;
                               break;
                       }
        }
        if (tie)
        {
               GameOver(-1);
               return;
        }
       
        // Check Horizontally first
        for (i=0; i<3; i++)
        {
               numXConsec = numOConsec = 0;
               for (j=0; j<3; j++)
               {
                       if (board[i][j] == 'X')
                               numXConsec++;
                       else if (board[i][j] == 'O')
                               numOConsec++;
                       else
                               break;
               }
               if (numXConsec > 2)
               {
                       GameOver(0);
                       return;
               }
               else if (numOConsec > 2)
               {
                       GameOver(1);
                       return;
               }
        }
       
        // Check Vertically
        for (i=0; i<3; i++)
        {
               numXConsec = numOConsec = 0;
               for (j=0; j<3; j++)
               {
                       if (board[j][i] == 'X')
                       {
                               numXConsec++;
                       }
                       else if (board[j][i] == 'O')
                               numOConsec++;
                       else
                               break;
               }
               if (numXConsec > 2)
               {
                       GameOver(0);
                       return;
               }
               else if (numOConsec > 2)
               {
                       GameOver(1);
                       return;
               }
        }
       
        // Finally Check both diagnals
        if ( ((board[0][0] == board[1][1]) && (board[1][1] == board[2][2]) &&
(board[2][2] == 'X')) ||
        ((board[0][2] == board[1][1]) && (board[1][1] == board[2][0]) &&
(board[2][0] == 'X')) )
        {
               GameOver(0);
               return;
        }
        if ( ((board[0][0] == board[1][1]) && (board[1][1] == board[2][2]) &&
(board[2][2] == 'O')) ||
        ((board[0][2] == board[1][1]) && (board[1][1] == board[2][0]) &&
(board[2][0] == 'O')) )
        {
               GameOver(1);
               return;
        }
    }
 
    private void GameOver(int p)
    {
       if (p == -1)
           winner = "Tie!";
       else if (p == 0)
           winner = "Player 1!";
       else
           winner = "Player 2!";
       gameOver = true;
    }
}

Networking

Of course the real fun of having an application on a mobile phone is networking. The mobile games that are most likely to be successful will involve lots of multiplayer ability, bringing players together in ways that nobody has ever experienced before.

The MIDP specification makes it extremely easy to pass data back and forth. Many phones support Datagrams, though they are not guaranteed to work on every device. The most common and easy method of MIDP network communication, of course, is using HTTP.
Every Motorola i85s phone, for example, has its own static IP address. Having one phone communicate with another is only a matter of simple peer-to-peer networking.

In addition, the phone can connect to any outside server machine. This server can be used as a gateway for pretty much any type of game traffic or other network communication imaginable. For instance, a gateway can be set up to access an entire database of sports scores, and then stream only the latest requested scores to a MIDlet.

To connect to a server, simply have the MIDlet use the Connector class:

Datagram dgram = dc.newDatagram(message, msglength,
     "datagram://www.myserver.com:9000");
dc.send(dgram);
dc.close();

The remote "server" could, of course, be another device. Just create an endless loop that listens to a port and waits for some data:

DatagramConnection dc = ( DatagramConnection)Connector.open
     ("datagram://:"+receiveport);
while (true)
{
     dgram = dc.newDatagram(dc.getMaximumLength());
     dc.receive(dgram);
     reply = new String(dgram.getData(), 0,dgram.getLength());
}

Since the Datagram protocol is standard, one could write a dastardly-simple server component in Java Standard Edition (or any other language), running on any PC. For instance:

DatagramSocket receiveSocket = null;
DatagramPacket receivePacket = new DatagramPacket(bytesReceived, bytesReceived.length);
receiveSocket.receive(receivePacket);

A simple game can be written with relative ease. For instance, NumberPick.java is a MIDlet that allows a user to pick a number between 0 and 9. It then sends this number to a server (at a fictitious machine with the host name of myserver.com) Note that for the sake of testing, it's a good idea to set the server to 'localhost' - that way you can run the server and the client from the comfort of your desktop machine.

[Code Listing: Number Picker]

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.util.*;
 
public class NumberPick extends MIDlet implements CommandListener 
{
    // This hostname may be a server or the IP of another phone
    static final String hostname = "myserver.com";
    static final int sendport         = 9000;
    static final int receiveport      = 9001;
 
    static boolean gotResult = false;
 
    private Display myDisplay;
 
    Receive receiveThread = new Receive();
 
    private Alert responseAlert;
 
    private Form searchwait;
    private Form pickscreen;
 
    TextField pickedNumber;
    private Command PICK = new Command("Pick One", Command.OK, 1);
    private Command OK = new Command("Try Again", Command.OK, 1);
 
    public NumberPick()
    {
    }
 
    public void startApp()
    {
       // Set up the Pick A Number screen
       pickscreen = new Form("Pick A Number");
       pickedNumber = new TextField("Between 0 and 9:","",1, TextField.NUMERIC);
       pickscreen.append(pickedNumber);
       pickscreen.addCommand(PICK);
       pickscreen.setCommandListener(this);
 
       // Set up a Searching, Please Wait screen
       searchwait = new Form("Searching...");
       searchwait.append(new StringItem("Wait","Please wait..."));
       searchwait.addCommand(OK);
       searchwait.setCommandListener(this);
 
       // Set up a Response Alert screen
       responseAlert = new Alert("And Your Guess Is...");
       responseAlert.setTimeout(Alert.FOREVER);
 
       myDisplay = Display.getDisplay(this);
       myDisplay.setCurrent(pickscreen);
 
        // Start the Listening thread
        receiveThread.start();
    }
 
    public void pauseApp() 
    {
    }
 
    public void destroyApp(boolean unconditional)
    {
    }
 
   
    public void commandAction(Command c, Displayable d)
    {
        if (d == pickscreen && c == PICK)
        {
            String request = pickedNumber.getString();
           if ( !request.equals("") )
            {
              myDisplay.setCurrent(searchwait);
              sendPick(request);
           }
        }
    }
 
    void sendPick(String thepick)
    {
        // Now send the pick...
        byte[] message = new byte[1];
        System.arraycopy(thepick.getBytes(), 0, message, 0,1);
 
        DatagramConnection dc = null;
 
        String destAddr = "datagram://" + hostname + ":" + sendport;
 
        try {
            dc = (DatagramConnection)Connector.open(destAddr);
 
           // Create a datagram socket and send
            Datagram dgram= dc.newDatagram(message,1,destAddr);
            dc.send(dgram);
            dc.close();
        }
        catch (Exception e)
        {
            System.out.println("Connecting Exception: " + e.getMessage());
            if (dc != null) {
                try {
                    dc.close();
                }
                catch (Exception f)
                {
                   System.out.println("Exception Closing: " + f.getMessage());
                }
            }
        }
    }
 
    class Receive extends Thread
    {
        public void run()
        {
            doReceive();
        }
 
        void doReceive()
        {
            DatagramConnection dc = null;
            Datagram dgram;
 
            try
            {
                dc = (DatagramConnection)Connector.open("datagram://:"+receiveport);
 
                String reply;
 
                while (true)
                {
                    dgram = dc.newDatagram(dc.getMaximumLength());
                    dc.receive(dgram);
                    reply = new String(dgram.getData(), 0,dgram.getLength());
                    if (reply.equals("Y"))
                    {
                       responseAlert.setString("100% right!");
                       myDisplay.setCurrent(responseAlert,pickscreen);
                    }
                    else
                    {
                       myDisplay.setCurrent(responseAlert,pickscreen);
                       responseAlert.setString("Wrong as can be!");
                    }
                    try
                    {
                       Thread.sleep(500);
                    }
                    catch (Exception e)
                    { }
                }
 
            }
            catch (Exception e)
            {
                System.out.println("Exception: " + e.getMessage());
 
                if (dc != null) 
                {
                    try
                    {
                        dc.close();
 
                    }
                    catch (Exception f)
                    {
                        System.out.println("Exception: " + f.getMessage());
                    }
                }
            }
        }
     }
}

Running on the myserver.com machine is NumberServer.java, which simply sits there and waits for a message on port 9000. If the number is 4, then the server sends back a simple one-byte "Y" message. Otherwise it sends back an "N".

Maybe not as compelling a game as Unreal or Quake III, but it's a start.

[Code Listing: Number Server]

import java.io.*;
import java.net.*;
import java.lang.*;
 
public class NumberServer
{
     public static void main(String args[])
     {
       DatagramSocket receiveSocket = null;
       try
       {
         byte[] receiveBytes = new byte[1];
         InetAddress receiveAddr = InetAddress.getByName("localhost");
         int receivePort = 9000;
 
         receiveSocket = new DatagramSocket(receivePort, receiveAddr);
     
         while (true)
         {
           DatagramPacket receivePacket = new DatagramPacket(receiveBytes,               receiveBytes.length);
           receiveSocket.receive(receivePacket);
           String data = new String(receivePacket.getData(), 0,               receivePacket.getLength());
           byte[] reply = receivePacket.getData();
           String replyString = new String(reply);
           if (replyString.equals("4"))
               sendResultsBack(true, receivePacket.getAddress());
           else
               sendResultsBack(false, receivePacket.getAddress());
        }
       }
       catch (Exception e)
       {
          System.err.println("Exception " + e);
       }
       finally
       {
         if (receiveSocket != null)
         {
           try
           {
              receiveSocket.close();
           }
           catch (Exception e)
           {
             System.err.println("Exception " + e);
           }
         }
       }
     }
   
     // Send results back to the phone on port 9001
     public static void sendResultsBack(boolean rightanswer, InetAddress        sendAddr)
     {
       byte results[] = new byte[1];
       if (rightanswer)
         results[0] = (byte)'Y';
       else
         results[0] = (byte)'N';
       DatagramSocket sendSocket = null;
       int sendPort = 9001;
       try
       {
          sendSocket = new DatagramSocket();
          DatagramPacket sendPacket = new DatagramPacket(results,1, sendAddr,              sendPort);
          sendSocket.send(sendPacket);
       }
       catch (Exception e)
       {
         System.out.println("Exception " + e);
       }
       finally
       {
         if (sendSocket != null)
         {
           try
           {
              sendSocket.close();
           }
           catch (Exception e)
           {
             System.err.println("Exception " + e);
           }
         }
       }
     }
}

Final Tips

As you go forth into the world coding games more complicated and sophisticated than TicTacToe and NumberPick, be sure to remember that every byte counts! It's exceptionally easy to run out of memory on these phones.

Try to avoid Hashtables and Vectors, and recycle any objects you no longer need. For example, instead of creating two buttons on two separate screens, try to merely change the label on an existing button.

Also, avoid using costly operations like string concatenations. Use a StringBuffer instead. As for interface design, remember your audience and the limitation of the device. Use few, large, simple components that require as few keypad presses as possible.

While your application is running, you can sniff out the memory using

Runtime.getRuntime().freeMemory()

and

Runtime.getRuntime().totalMemory()

Remember to strategically garbage collect whenever resources fall too low using:

Runtime.getRuntime().gc()

Since the MIDlet classes only contain a basic set of graphic user interface controls, you might want to opt for a better library.

A site called Trantor in Germany offers a package known as kAWT <http://www.trantor.de/kawt/index.html>. This is a lightweight version of Java's AWT specially tailored for J2ME. There are versions for the Palm, as well as for MIDP. It allows your MIDlets to use standard Java widgets such as Panels and Containers and makes the MIDlet code truly upwardly compatible with applets. The only caveat is that kAWT will suck away an additional 27K or so of memory.

Finally, I highly recommend you use a code packer or obfuscator to compress your bytecode as much as possible. A good obfuscator such as IBM's jax <http://www.alphaworks.ibm.com/tech/JAX> can make your final application as much as 30 percent smaller!

Resources

More info about J2ME:
http://java.sun.com/j2me/

More info about the MIDP specifically:
http://java.sun.com/products/midp/

For some great tutorials and sample programs, check out:
http://webdev.apl.jhu.edu/~rbe/kvm/

The Java Mobile site has lots of articles, tips, and sampleapplications:
http://www.javamobile.org/

The Micro Java Network has all sorts of great articles and forums. Here are the games:
http://www.microjava.com/downloads/games

Visit Bill Day's J2ME Archive with tons of sample applications, links to IDEs and SDKs, and anything else J2ME related. There is also lots of source code:
http://www.billday.com/j2me/

http://www.gamasutra.com/features/20010917/fox_01.htm

Copyright 2003 CMP Media Inc. All rights reserved.